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.
| Action | Endpoint |
|---|---|
| Set or replace a bubble | POST /user.statusBubble.set |
| Read a bubble | GET /user.statusBubble.get |
| Clear a bubble | POST /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โ
| Credential | Who you can target | Scopes |
|---|---|---|
| Organization API key / org OAuth | An active user in the workspace | user:write.statusBubble to set/clear; user:read.statusBubble to get |
| Personal OAuth | The token owner | Same scopes |
| Personal Access Token | The token owner | Scope 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โ
| Code | Status | When |
|---|---|---|
missing_parameter | 400 | Missing userId or text |
invalid_parameter | 400 | Invalid user identifier, blank or overlong text, or integer TTL outside 300โ86400 |
invalid_json | 400 | Malformed JSON or wrongly typed input, including a fractional TTL |
invalid_arguments | 400 | expiresAt supplied to set |
missing_scope | 403 | OAuth/API key lacks the required scope |
access_denied | 403 | Personal token targets someone else |
user_not_found | 404 | Unknown, archived, or cross-account user |