API
The Roam API provides chat and user management capabilities for building powerful integrations with your Roam Virtual Office.
OpenAPI Spec: chat-v1.json
See the Migration Guide for details on upgrading from v0.
Acting people and automated actors share one principal contract. See
Identity & Principals for userId
taxonomy, directory versus hydration behavior, and bot-loop guidance.
Base URL
https://api.ro.am/v1
API version
Within the /v1 generation, response and webhook shapes are pinned by a dated
version string (YYYY-MM-DD). The current default for new integrations is:
2026-08-20
Every REST response includes a Roam-Version header with the version that shaped
the body. To pin a request explicitly:
curl -H "Authorization: Bearer $ROAM_TOKEN" \
-H "Roam-Version: 2026-08-20" \
https://api.ro.am/v1/token.info
Each API client is created on the latest version available at that time and never advances automatically. Webhook delivery shapes are pinned at subscribe time. Full rules, resolution order, and support window: API Versioning.
Authentication
Every request carries a bearer token — an organization API key, an OAuth access token, or a Personal Access Token:
Authorization: Bearer YOUR_TOKEN
Create API clients in Roam Administration > Developer. See OAuth & Authentication for the authorization flows and Scopes for the permission catalog.
Authentication failures
Failed authentication returns HTTP 401 Unauthorized with a machine-readable
error catalog code. Distinguish these cases:
| Code | Meaning | Client action |
|---|---|---|
not_authed | No Authorization: Bearer token was sent | Attach a token and retry |
invalid_token | Token is unknown, malformed, or expired | Obtain a new token (refresh OAuth, create a new API key / PAT) |
token_revoked | Token is permanently unusable — e.g. the owning person was archived or deleted, or the API client was archived | Discard the token and re-authenticate; retrying with the same token will never succeed |
Responses with invalid_token or token_revoked also include:
WWW-Authenticate: Bearer error="invalid_token"
per RFC 6750. Use that header
(or the body error field) to stop retry loops on dead credentials.
Access Models
Every integration is backed by an API client, created in Roam Administration → Developer by a workspace admin. A client has one of two authorization types:
- API Key — A long-lived secret for server-to-server integrations that act as the app itself. No user authorization step.
- OAuth — A client ID and secret for apps that are authorized per install, by an admin (organization access) or by an individual user (personal access), and that can be revoked or uninstalled.
Personal Access Tokens are a third credential, created by users under User Settings → Developer when workspace policy allows, without registering an OAuth app.
Those credentials map onto the two access models Roam APIs support:
- Organization access — For admin-built integrations that operate across the entire workspace. The integration acts as an app with its own bot persona. Authenticate with an API Key or OAuth (admin consent).
- Personal access — For integrations that act on behalf of a specific user, seeing only that user's data and posting as the user's personal bot. Authenticate with OAuth or a Personal Access Token (user consent).
A single OAuth app can support both models. The access model is selected when the user authorizes the app.
See the Access Models guide for a full comparison, endpoint compatibility matrix, and guidance on choosing the right model.
Response shape
Successful JSON responses include "ok": true. Errors return
"ok": false with a machine-readable error code. List endpoints use
opaque cursor / nextCursor values — do not parse or construct them.
Full details: Responses and Errors.
{ "ok": true, "…": "…" }
{ "ok": false, "error": "invalid_token" }
Pagination
List endpoints return paginated results. The pagination style varies by endpoint:
Cursor-based pagination (most endpoints):
cursor: Opaque string from a previous response'snextCursorfield. Do not parse or construct cursors yourself.limit: Number of results per page. Default is typically 10, maximum is typically 100.
An invalid or expired cursor returns 400 with error: "invalid_cursor" — restart
pagination without a cursor.
Date-range pagination (some endpoints like meeting.list):
after: Return items after this date (RFC3339 or YYYY-MM-DD format)before: Return items before this date- When
afteris specified, results are returned in ascending order; otherwise descending.
Check individual endpoint documentation for specific pagination parameters and limits.
Rate Limiting
API requests are rate limited to prevent abuse.
Global (all credential types)
- Burst: 10 requests
- Sustained rate: 1 request per second
Personal Access Tokens (additional)
PATs also have a daily quota of 1,000 requests per day.
Every response — success and 429 — carries quota headers for the burst
bucket. Retry-After is sent only on 429.
A successful request looks like:
HTTP/1.1 200 OK
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1776556801
RateLimit-Policy: "burst";q=10;w=1
RateLimit: "burst";r=7
A burst rejection looks like:
HTTP/1.1 429 Too Many Requests
Retry-After: 10
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1776556810
RateLimit-Policy: "burst";q=10;w=1
RateLimit: "burst";r=0;t=10
{"ok":false,"error":"ratelimited"}
| Header | Meaning | Example |
|---|---|---|
X-RateLimit-Limit | Burst size | 10 |
X-RateLimit-Remaining | Tokens left in the burst bucket (0–10) | 7 |
X-RateLimit-Reset | Unix epoch seconds (UTC) when another burst token is available | 1776556801 |
Retry-After | Seconds to wait after a 429. Honor this over Reset. Typical burst wait is 10. | 10 |
RateLimit-Policy | IETF draft policy list (q = quota, w = window seconds) | "burst";q=10;w=1 |
RateLimit | IETF draft remaining (r). t (seconds until more quota) is present only when r is 0 | "burst";r=7 |
PATs add a "day" policy on the IETF headers for the daily quota.
There is no custom X-RateLimit-*-Day trio — X-RateLimit-* is always
the burst bucket.
RateLimit-Policy: "burst";q=10;w=1, "day";q=1000;w=86400
RateLimit: "burst";r=7, "day";r=847
Remaining is a hint from the appserver process that handled the request.
Replicas do not share counters, so a follow-up request may see a different
count. Retry-After on 429 is the authoritative wait.
Repeated abuse may enter a short tarpit (delayed responses) before further requests are dropped.
Best practices:
- Honor
Retry-Afteron429; do not retry sooner - Use
X-RateLimit-Remaining(orRateLimitr=) to slow down before you hit the ceiling - Batch operations where possible to reduce request count
- For PATs, stay well under the daily 1000 budget for long-running sync jobs
Common Error Responses
Errors return "ok": false and a machine-readable error catalog code — not a
free-text sentence. Branch on error rather than parsing prose.
{ "ok": false, "error": "invalid_parameter" }
Status codes follow these conventions:
| Status | Meaning |
|---|---|
400 Bad Request | Invalid request (malformed or missing parameters) |
401 Unauthorized | Missing or invalid credentials (see Authentication failures) |
403 Forbidden | Valid auth, but the token lacks the required scope or access to the target resource |
404 Not Found | Resource not found |
413 Payload Too Large | Request body or message text too large |
429 Too Many Requests | Rate limited (see Rate Limiting) |
500 Internal Server Error | Internal error |
Full envelope and missing_scope details: Responses and
Errors. The complete code catalog is in the
Error Codes guide.
Addressing
The API uses two addressing concepts:
- Chat (
chatId): A conversation (DM, multi-DM, or group chat). Use for retrieving history or posting to an existing conversation. - Address (
userId,groupId): A destination for new messages. UseuserIdto DM a user orgroupIdto post to a group chat.
When posting a message, you can use either a chatId (to continue an existing conversation) or a userId/groupId (to start or continue a conversation with that destination).
Replies & Threads
Messages can be replies to other messages. The API distinguishes between two types:
-
Thread replies (
threadTimestamp): In group chats, replies to a message create a thread. ThethreadTimestampfield contains the timestamp of the parent message that started the thread. All replies in that thread share the samethreadTimestamp. -
DM replies (
replyTimestamp): In DMs, replies reference the specific message being replied to viareplyTimestamp. This is a direct reply rather than a thread.
When receiving webhook events, check for threadTimestamp to identify thread replies. When posting a reply, include threadTimestamp to reply within an existing thread.
Endpoints Overview
Chat & Messaging
| Endpoint | Method | Description |
|---|---|---|
/chat.list | GET | List all accessible chats (DMs, MultiDMs, Group chats) |
/chat.post | POST | Send or schedule a message (text, Block Kit, or poll) to any chat, group, or user |
/chat.sendMessage | POST | Legacy — prefer /chat.post |
/chat.postEphemeral | POST | Post a private "only you can see this" message to one member of a chat |
/chat.scheduled.list | GET | List pending scheduled messages created via chat.post's sendAt |
/chat.scheduled.cancel | POST | Cancel a pending scheduled message before it sends |
/chat.startStream | POST | Start a streaming message; returns a stream ID |
/chat.appendStream | POST | Append a text chunk to an open stream |
/chat.stopStream | POST | Finalize a stream into a single persisted message |
/chat.update | POST | Edit a previously posted bot message |
/chat.delete | POST | Delete a previously posted bot message |
/chat.typing | POST | Show typing indicator to other participants |
/chat.history | GET | Retrieve message history for a chat |
/chat.search | POST | Full-text search over the user's accessible messages |
/chat.link.resolve | POST | Resolve a Roam chat deep link to a message reference |
/chat.link.create | POST | Create a shareable Roam link to a chat message |
/chat.unfurl | POST | Attach rich previews to links in an existing message |
/reaction.add | POST | Add emoji reaction to a message |
/reaction.remove | POST | Remove emoji reaction from a message |
/reaction.list | GET | List all reactions on a message |
/asset.create | POST | Create a file upload (JSON/MCP-friendly); attach via assetIds, or use purpose: "story" for story media |
/item.upload | POST | Upload a file (raw bytes) to attach to a message |
/story.post | POST | Post a story as the authenticated user (Personal tokens only) |
Groups & Group Chats
| Endpoint | Method | Description |
|---|---|---|
/group.list | GET | List accessible groups |
/groups.list | GET | Legacy — prefer /group.list (raw array response) |
/group.info | GET | Get group details by ID or name |
/group.create | POST | Create a new group chat |
/group.rename | POST | Rename an existing group |
/group.archive | POST | Archive a group |
/group.members | GET | List members in a group with roles |
/group.add | POST | Add members or admins to a group |
/group.join | POST | Join a public group as the calling identity |
/group.remove | POST | Remove members from a group |
Users
| Endpoint | Method | Description |
|---|---|---|
/user.list | GET | List members or hydrate explicit principal IDs |
/user.info | GET | Resolve a principal by ID or a member by email |
/userauditlog.list | GET | List user audit log entries |
/messageevent.export | POST | Export daily message archives (JSON Lines) |
Meetings
| Endpoint | Method | Description |
|---|---|---|
/conversation.list | GET | List conversations (meetings) with participants |
/meeting.list | GET | List meetings |
/recording.list | GET | Legacy — prefer /meeting.list / /meeting.info |
/meeting.info | GET | Get meeting details with summary, action items, and chapters |
/meeting.participants | GET | Paginate through meeting participants |
/meeting.transcript | GET | Get meeting transcript (JSON or WebVTT) |
/meeting.search | GET | AI-powered meeting search (Personal access only) |
/meeting.prompt | POST | Ask AI questions about a meeting transcript |
/meeting.shareLink | POST | Get (or create) a shareable link for a meeting |
/meeting.link.create | POST | Create a meeting link |
/meeting.link.info | POST | Get details for a meeting link |
/meeting.link.update | POST | Update a meeting link |
/calendar.event.create | POST | Create a calendar event with a Roam meeting link |
/calendar.list | GET | List events from the user's connected calendars |
/lobby.list | GET | List active lobbies |
/lobby.booking.list | GET | List bookings for a lobby |
Magicasts
| Endpoint | Method | Description |
|---|---|---|
/magicast.list | GET | List magicasts |
/magicast.info | GET | Get magicast details, transcript, and video |
/magicast.shareLink | POST | Get (or create) a shareable player link |
App Management
| Endpoint | Method | Description |
|---|---|---|
/token.info | GET | Get info about the current access token |
/token.revoke | POST | Revoke access token |
/webhook.list | GET | List webhook subscriptions |
/webhook.subscribe | POST | Create or update a webhook subscription (dotted event names) |
/webhook.unsubscribe | POST | Remove a webhook subscription |
/webhook.deliveries | GET | List failed deliveries |
Common Use Cases
Build a Chat Bot
Create an OAuth app with chat:read and chat:send_message scopes, configure a webhook URL to receive messages, then respond programmatically.
- Subscribe to
chat.messageevents - Use
/reaction.addto acknowledge receipt - Use
/chat.typingto show typing indicator - Use
/chat.postto send a response
Responding privately
To reply so that only one person sees it — validation errors, permission
warnings, "here's how to use this command" help — use
/chat.postEphemeral with the chatId and
the userId of the person to address. The recipient sees the message in the
shared chat under an "Only you can see this" header; nobody else sees
anything, and nothing is stored in history. Ephemeral messages are
best-effort and desktop/web-only — anything the recipient must durably
receive belongs in a DM (/chat.post with userIds) instead.
Have questions? Contact us via Team Roam Support Chat.
Authentication
- HTTP: Bearer Auth
Pass your API Key or OAuth access token as a Bearer token.
Example: Authorization: Bearer <token>
Security Scheme Type: | http |
|---|---|
HTTP Authorization Scheme: | bearer |
Terms of Service
https://ro.am/terms🗃️ Chat
23 items
🗃️ Users
4 items
🗃️ Groups
9 items
🗃️ Meetings
12 items
🗃️ Calendar
2 items
🗃️ Lobbies
2 items
🗃️ Magicasts
2 items
🗃️ App
2 items