Skip to main content
Version: 1.0

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:

CodeMeaningClient action
not_authedNo Authorization: Bearer token was sentAttach a token and retry
invalid_tokenToken is unknown, malformed, or expiredObtain a new token (refresh OAuth, create a new API key / PAT)
token_revokedToken is permanently unusable — e.g. the owning person was archived or deleted, or the API client was archivedDiscard 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's nextCursor field. 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 after is 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"}
HeaderMeaningExample
X-RateLimit-LimitBurst size10
X-RateLimit-RemainingTokens left in the burst bucket (010)7
X-RateLimit-ResetUnix epoch seconds (UTC) when another burst token is available1776556801
Retry-AfterSeconds to wait after a 429. Honor this over Reset. Typical burst wait is 10.10
RateLimit-PolicyIETF draft policy list (q = quota, w = window seconds)"burst";q=10;w=1
RateLimitIETF 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-After on 429; do not retry sooner
  • Use X-RateLimit-Remaining (or RateLimit r=) 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:

StatusMeaning
400 Bad RequestInvalid request (malformed or missing parameters)
401 UnauthorizedMissing or invalid credentials (see Authentication failures)
403 ForbiddenValid auth, but the token lacks the required scope or access to the target resource
404 Not FoundResource not found
413 Payload Too LargeRequest body or message text too large
429 Too Many RequestsRate limited (see Rate Limiting)
500 Internal Server ErrorInternal 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. Use userId to DM a user or groupId to 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. The threadTimestamp field contains the timestamp of the parent message that started the thread. All replies in that thread share the same threadTimestamp.

  • DM replies (replyTimestamp): In DMs, replies reference the specific message being replied to via replyTimestamp. 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

EndpointMethodDescription
/chat.listGETList all accessible chats (DMs, MultiDMs, Group chats)
/chat.postPOSTSend or schedule a message (text, Block Kit, or poll) to any chat, group, or user
/chat.sendMessagePOSTLegacy — prefer /chat.post
/chat.postEphemeralPOSTPost a private "only you can see this" message to one member of a chat
/chat.scheduled.listGETList pending scheduled messages created via chat.post's sendAt
/chat.scheduled.cancelPOSTCancel a pending scheduled message before it sends
/chat.startStreamPOSTStart a streaming message; returns a stream ID
/chat.appendStreamPOSTAppend a text chunk to an open stream
/chat.stopStreamPOSTFinalize a stream into a single persisted message
/chat.updatePOSTEdit a previously posted bot message
/chat.deletePOSTDelete a previously posted bot message
/chat.typingPOSTShow typing indicator to other participants
/chat.historyGETRetrieve message history for a chat
/chat.searchPOSTFull-text search over the user's accessible messages
/chat.link.resolvePOSTResolve a Roam chat deep link to a message reference
/chat.link.createPOSTCreate a shareable Roam link to a chat message
/chat.unfurlPOSTAttach rich previews to links in an existing message
/reaction.addPOSTAdd emoji reaction to a message
/reaction.removePOSTRemove emoji reaction from a message
/reaction.listGETList all reactions on a message
/asset.createPOSTCreate a file upload (JSON/MCP-friendly); attach via assetIds, or use purpose: "story" for story media
/item.uploadPOSTUpload a file (raw bytes) to attach to a message
/story.postPOSTPost a story as the authenticated user (Personal tokens only)

Groups & Group Chats

EndpointMethodDescription
/group.listGETList accessible groups
/groups.listGETLegacy — prefer /group.list (raw array response)
/group.infoGETGet group details by ID or name
/group.createPOSTCreate a new group chat
/group.renamePOSTRename an existing group
/group.archivePOSTArchive a group
/group.membersGETList members in a group with roles
/group.addPOSTAdd members or admins to a group
/group.joinPOSTJoin a public group as the calling identity
/group.removePOSTRemove members from a group

Users

EndpointMethodDescription
/user.listGETList members or hydrate explicit principal IDs
/user.infoGETResolve a principal by ID or a member by email
/userauditlog.listGETList user audit log entries
/messageevent.exportPOSTExport daily message archives (JSON Lines)

Meetings

EndpointMethodDescription
/conversation.listGETList conversations (meetings) with participants
/meeting.listGETList meetings
/recording.listGETLegacy — prefer /meeting.list / /meeting.info
/meeting.infoGETGet meeting details with summary, action items, and chapters
/meeting.participantsGETPaginate through meeting participants
/meeting.transcriptGETGet meeting transcript (JSON or WebVTT)
/meeting.searchGETAI-powered meeting search (Personal access only)
/meeting.promptPOSTAsk AI questions about a meeting transcript
/meeting.shareLinkPOSTGet (or create) a shareable link for a meeting
/meeting.link.createPOSTCreate a meeting link
/meeting.link.infoPOSTGet details for a meeting link
/meeting.link.updatePOSTUpdate a meeting link
/calendar.event.createPOSTCreate a calendar event with a Roam meeting link
/calendar.listGETList events from the user's connected calendars
/lobby.listGETList active lobbies
/lobby.booking.listGETList bookings for a lobby

Magicasts

EndpointMethodDescription
/magicast.listGETList magicasts
/magicast.infoGETGet magicast details, transcript, and video
/magicast.shareLinkPOSTGet (or create) a shareable player link

App Management

EndpointMethodDescription
/token.infoGETGet info about the current access token
/token.revokePOSTRevoke access token
/webhook.listGETList webhook subscriptions
/webhook.subscribePOSTCreate or update a webhook subscription (dotted event names)
/webhook.unsubscribePOSTRemove a webhook subscription
/webhook.deliveriesGETList 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.

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

Pass your API Key or OAuth access token as a Bearer token. Example: Authorization: Bearer <token>

Security Scheme Type:

http

HTTP Authorization Scheme:

bearer

Contact

Team Roam Support Chat:

URL: https://ro.am/support/contact-us

Terms of Service

https://ro.am/terms