Authorization: Bearer <key>. Things to know before you call:
- The version header matters. Every response is rendered against a dated API version, and the plugin pins
Roam-Version: 2026-08-25on every request so a body’s shape cannot change under it. Omitting the header would use the integration’s default, which Roam moves between releases. - Errors are labelled
text/plain. Roam’s error envelope is JSON but the response’s content type makes generic clients hand it over as a raw string. The plugin parses it, soRoamAPIError.codeis the catalog code such asinvalid_token— not a string containing the whole body. - Branch on the code, not the status. A 401 can be
not_authed,invalid_tokenortoken_revoked, and a 403 can bemissing_scopeorapproval_pending. A revoked token is permanent: discard it rather than retrying. Amissing_scopebody names the scopes that would work inneeded, any one of which is enough. chat.postcontent rules.text,itemsandasset_idscombine;blocksandpolleach stand alone. A body with no content, orblocks/pollmixed with anything else, is refused before the request.- Pass
sync: trueto get the messagetimestamp. Without it Roam posts in the background and the answer has onlychatId, so there is nothing to hand tochat.update,reactions.addor a thread reply. - A post needs one destination. Exactly one of
chat_id,group_idoruser_ids: the last one opens a direct conversation. - Scheduling a post returns a different shape. Passing
send_atreturnsscheduledMessageIdinstead of atimestamp. The time must be in the future, within 30 days and on a 15-minute UTC boundary, and cannot be combined withsync,poll,thread_keyorreply_timestamp. chat.historyneeds one target. Exactly one ofchat_id,group_idoruser_ids(the direct chat with those people); Roam refuses none or more than one.- Messages are edited, not replaced.
chat.updatetakes the chat and the messagetimestamp, and needstext(with optionalitemsorasset_ids) orblocks. A bot can edit only its own messages. - Paging is a cursor.
users.list,chat.list,chat.history,groups.listandmeetings.listanswer Roam’snextCursor, which the plugin returns asnext_cursorwithhas_more. Pass it back ascursorfor the next page;next_cursor: nullis the last page, and a call without a cursor starts at the first.reactions.listis not paged, andusers.listwithidsreturns every match in one answer.
- 12 typed API operations
Setup
1
Install
2
Add the plugin
corsair.ts
multiTenancy: true, scope calls with corsair.withTenant(id). See Quick start for KEK + Hub keys, and Multi-tenancy for account isolation.3
Choose authentication
- API Key (Recommended)
No setup required yet. When you make your first request as a tenant, Corsair prompts for the API key.More: API Key
4
Connect a tenant
Mint a connect link and send the tenant to it. Hub hosts the page and delivers the result to your app — see Connect / OAuth.
Example API calls
Read the last messages in a chatWhat’s next
API reference
Every
roam.api.* operation with input and output types.Connect / OAuth
createLink, Hub delivery, and tenant connect flows.
Use with agents
Expose this plugin’s operations as MCP tools.