Skip to main content

Status bubbles

A status bubble is the short thought bubble on a person's assigned office seat. Use these endpoints to update the same message that the person can edit on the map.

ActionEndpoint
Set or replace a bubblePOST /user.statusBubble.set
Read a bubbleGET /user.statusBubble.get
Clear a bubblePOST /user.statusBubble.clear

Each person has one bubble per workspace. API and UI updates replace that shared bubble, regardless of who wrote it. Clearing removes the current bubble, including one set in the UI or by another integration. Setting a bubble again refreshes its expiration.

Status bubbles do not change check-in, Will Return, or Do Not Disturb. For absences, use Will Return / Out of Roam. For call indicators, badges, and seat glows, use External activity.

Authorizationโ€‹

CredentialWho you can targetScopes
Organization API key / org OAuthAn active user in the workspaceuser:write.statusBubble to set/clear; user:read.statusBubble to get
Personal OAuthThe token ownerSame scopes
Personal Access TokenThe token ownerScope check is skipped

The existing user:read.status and user:write.status scopes do not grant access to thought bubbles.

userId accepts a bare UUID, tagged U-โ€ฆ ID, or ASCII email. Responses return the canonical UUID. Identifying a person by email does not require user:read.email. The workspace comes from the token.

Set a bubbleโ€‹

curl -X POST https://api.ro.am/v1/user.statusBubble.set \
-H "Authorization: Bearer $ROAM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"userId":"ada@example.com","text":"At lunch ๐ŸŽ","ttlSeconds":3600}'

Text is trimmed and must contain 1โ€“20 Unicode code points after trimming. Emoji sequences and combining marks may contain several code points even when they look like one character. Blank text is rejected; use .clear to remove a bubble.

ttlSeconds is optional. Omit it (or pass null) for the same 24-hour expiration used by the UI. To expire sooner, pass an integer from 300 through 86400. Durations below 5 minutes or above 24 hours are rejected. expiresAt is a response field; sending it to .set is rejected.

The 200 response contains the saved text and server-stamped expiration, in RFC3339 format with millisecond precision:

{
"userId": "0cc74785-e31e-4403-aa5e-0cc7c1897e66",
"statusBubble": {
"text": "At lunch ๐ŸŽ",
"expiresAt": "2026-09-22T17:00:00.123Z"
}
}

The bubble is stored even if the person has no assigned seat. It appears when an office seat is assigned while the bubble is still live. Updates reach the map asynchronously.

Get or clear a bubbleโ€‹

curl --get https://api.ro.am/v1/user.statusBubble.get \
-H "Authorization: Bearer $ROAM_TOKEN" \
--data-urlencode 'userId=ada@example.com'

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

Get returns the same shape as set. If there is no live bubble, it returns 200 with "statusBubble": null. Expired bubbles are omitted immediately, even before storage cleanup runs. Clear returns 204 No Content, including when there is no bubble to clear.

Automatic removal from the map can take up to about a minute after expiration. Use .clear to remove a bubble sooner; the API sends that update immediately.

Errorsโ€‹

CodeStatusWhen
missing_parameter400Missing userId or text
invalid_parameter400Invalid user identifier, blank or overlong text, or integer TTL outside 300โ€“86400
invalid_json400Malformed JSON or wrongly typed input, including a fractional TTL
invalid_arguments400expiresAt supplied to set
missing_scope403OAuth/API key lacks the required scope
access_denied403Personal token targets someone else
user_not_found404Unknown, archived, or cross-account user