Skip to main content

Will Return / Out of Roam

Roam already has a first-class absence field — Will Return Today (clock icon, same day) and Out of Roam (calendar icon, multi-day). Members set it from the Exit menu. user.status.set / .clear write that same field so an HRIS or calendar integration can keep Roam in sync.

This is not external activity. External activity is a short-lived glow + emoji with a 60-minute TTL (a live phone call). Absences belong here: they persist until returnTime or .clear, and they do not paint a glow or lock the office.

There is no status: "out_of_office" enum. Check-in (checkedIn / checkedOut) stays read-only. Sending status on .set is rejected.

ActionEndpoint
Record an absencePOST /user.status.set
Clear itPOST /user.status.clear
Read it backGET /user.info?expand=status (needs user:read.status)
Listenuser.status.update webhook

Two modes

willReturn.outOfRoam chooses the product surface. Omitted on .set defaults to true.

Out of RoamWill Return Today
outOfRoamtrue (default)false (must send explicitly)
returnTimeFuture, at most 2 yearsFuture, less than 10 hours
DesktopCalendar icon on their officeClock icon
Check-in / check-outSurvives. They can be checkedIn with Out of Roam still setCleared
Elapsed returnTimeServer sweep drops it within a minuteSame

reason is optional, at most 128 Unicode code points ("On vacation", "Sick leave", "Out to lunch").

A user can be checkedIn and have willReturn (multi-day Out of Roam). Key off the presence of the object, not status.

Authorization

CredentialWho you can targetScopes
Organization API key / org OAuthAny active member in the workspaceuser:write.status to set/clear. user:read.status to read via user.info / webhooks
Personal OAuthThe token owner onlySame scopes — personal-mode OAuth is not exempt
Personal Access TokenThe token owner onlyWrite scope check is skipped. Targeting anyone else returns 403 access_denied. user:read.status is still required to read presence

Unknown, archived, or cross-account users return 404 user_not_found.

Last writer wins with the desktop client: a later Exit-menu save overwrites the API value, and a later .set overwrites the desktop value. There is one row per person, not a stack per integration.

Identify the user

userId accepts a bare UUID, a tagged U-… ID, or an ASCII email — the same convention as group.create members. HR systems that only have an email do not need a UUID lookup table or a prior user.info call. Looking a member up by an address you already have does not require user:read.email. The response still returns the canonical UUID userId.

Set an absence

curl -X POST https://api.ro.am/v1/user.status.set \
-H "Authorization: Bearer $ROAM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "ada@example.com",
"willReturn": {
"returnTime": "2026-09-22T09:00:00Z",
"reason": "On vacation",
"outOfRoam": true
}
}'

The 200 body echoes the written willReturn (with outOfRoam defaulted) plus the user's current status. .set does not check them out, does not enable Do Not Disturb, and does not change status.

Same-day leave:

"willReturn": {
"returnTime": "2026-09-16T18:00:00Z",
"reason": "Out to lunch",
"outOfRoam": false
}

Re-posting .set replaces the previous absence. There is no heartbeat and no TTL — keep returnTime accurate, or .clear when they come back early.

Read it back

.set is not a substitute for user.info. To see what colleagues see:

curl -H "Authorization: Bearer $ROAM_TOKEN" \
"https://api.ro.am/v1/user.info?id=0cc74785-e31e-4403-aa5e-0cc7c1897e66&expand=status"

willReturn is omitted without expand=status, without user:read.status, and after returnTime has elapsed. user.list?expand=status is the same shape per member.

A user.status.update webhook fires on .set, .clear, desktop edits, check-in/out, and elapsed sweep. Subscribe with user:read.status. The payload's data.willReturn is present only while a future absence is set.

Clear

curl -X POST https://api.ro.am/v1/user.status.clear \
-H "Authorization: Bearer $ROAM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"userId": "ada@example.com"
}'

Response is 204 No Content, including when nothing was set. This removes Out of Roam as well as Will Return Today. Call it from your "returned to work" / "leave cancelled" webhook.

Errors

CodeStatusWhen
missing_parameter400userId, willReturn, or willReturn.returnTime missing
invalid_parameter400userId is not a UUID/tagged ID/email, returnTime in the past or > 2 years, Will Return Today ≥ 10 hours out, reason over 128 code points
invalid_arguments400status sent on .set
missing_scope403OAuth / API key lacks user:write.status
access_denied403Personal token targeting someone other than the owner
user_not_found404Unknown, archived, or cross-account user

Example: HRIS leave sync

  1. On leave.approved (vacation, sick, parental, public holiday), POST /user.status.set with the employee's email in userId, returnTime = first working moment after the leave, reason from the leave type, and outOfRoam: true (or omit it).
  2. If the leave is extended, .set again with the new returnTime. Last writer wins.
  3. On leave.cancelled or an early return, POST /user.status.clear with that same userId. Treat 204 as success even if they already cleared it in the desktop client.

Do not heartbeat this field with user.activity.set. A 10–60 minute glow is the wrong product for a week of vacation, and stacking per integration would fight the single absence the rest of the office sees.