REST
64 endpoints under /api/v1, bearer auth,
JSON in and out. Nothing to install and nothing to keep running.
Reach for it when your software is calling: a backend job, a cron, a webhook handler, a CI step.
Coommit Connect · v1
Most APIs hand back a record you have to imagine. This one hands back a room: boxes land on a live canvas while your team watches, and the meeting starts with the work already on the wall.
Two ways in
Both reach the same engine and obey the same permissions. The difference is who writes the call: your code, or a model.
64 endpoints under /api/v1, bearer auth,
JSON in and out. Nothing to install and nothing to keep running.
Reach for it when your software is calling: a backend job, a cron, a webhook handler, a CI step.
15 tools over streamable-http at /mcp,
with the same key. Claude Code, Claude Desktop and any MCP client discover them on connect.
Reach for it when a model is calling and you want it to choose the tool itself.
Quickstart
Every response below is real. Nothing here is a placeholder you have to substitute twice.
In Coommit, open Settings › Agent keys. Pick the scopes, and you get a
cmt_live_ secret once. A key acts as the account that made it, never above it:
if that account cannot see a room, neither can the key.
# read, do not type: -s keeps the key out of your shell history
read -rs COOMMIT_KEY && export COOMMIT_KEY
GET /me is the cheapest call in the API and the only one worth making first.
It tells you which account the key acts as, which is what every permission check below reads.
curl https://app.coommit.com/api/v1/me \
-H "Authorization: Bearer $COOMMIT_KEY"
{
"ok": true,
"account": {
"id": 4821,
"name": "Ada Lovelace",
"email": "ada@acme.com",
"image": null,
"plan": "2year",
"locale": "en",
"createdAt": "2026-07-02T09:14:00.000Z"
},
"customization": {
"screenShare": true,
"recording": true,
"transcription": true,
"chat": true,
"handsFree": true,
"youtube": true,
"gdrive": true,
"figma": true,
"browserBox": true,
"draw": true,
"diagrams": true,
"aiImages": true,
"echo": true,
"tasks": true
},
"key": {
"id": 17,
"name": "release-bot",
"scopes": [
"account:read",
"rooms:read",
"rooms:write"
]
}
}
Note the shape: the account is under account, and the key describes itself under
key, scopes included. That is the fastest way to check what a key was actually granted.
Pass actions to POST /rooms and the board exists before the room is opened.
Send the URL and the work is already on the wall. Up to 100 actions per call, applied in order.
curl -X POST https://app.coommit.com/api/v1/rooms \ -H "Authorization: Bearer $COOMMIT_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: sprint-24-review" \ -d '{ "name": "Sprint 24 review", "actions": [ { "type": "add_title", "html": "Sprint 24" }, { "type": "add_text", "html": "<p>3 shipped, 1 slipped.</p>" }, { "type": "add_task", "title": "Cut the release" }, { "type": "add_poll", "question": "Ship on Friday?", "options": ["Yes", "No", "Needs one more week"] } ] }'
You get the room id and its URL back. Anyone already inside watches the boxes land as the call runs.
Want to see what you made? GET /rooms/:roomId/screenshot returns the board as a PNG.
Built for things that loop
A script that runs twice should not invite anyone twice, and a model that misreads its own output should not be able to spend a balance. These are defaults, not options you remember to turn on.
Email invitations and scheduled calls run as simulations unless you explicitly send
dry_run: false. The simulated response shows exactly what would have been sent,
so a loop that misfires costs you nothing but a log line.
Send the header on any write and the key is claimed before the work starts. A duplicate
replays the original response with Idempotent-Replay: true. If the claim itself
cannot be made, the write is refused rather than risked twice.
Anything that costs money or reaches a person is capped on the account, not the key: image generation, invitations, scheduling, Commit runs. Minting more keys buys more throughput on reads, and none at all on spend.
Every call is recorded with its key, scope, route and outcome, including the ones that were turned away. A silent denial is indistinguishable from a hang, so nothing is denied silently.
Scopes
A key carries the subset you pick. A call outside it comes back 403 missing_scope
naming the one it wanted, so a narrow key fails loudly instead of doing something you did not ask for.
account:read
Read the account behind the key: profile, notifications, friends.
account:write
Update that profile and its notification settings.
rooms:read
Read rooms, boards, tasks, members, recaps and exports.
rooms:write
Create rooms, write to boards, move things, run Commit.
rooms:delete
Delete rooms. Kept apart from rooms:write on purpose, because most agents never need it.
invites:write
Invite people, by link or by email.
calls:write
Schedule, reschedule and cancel calls.
image:generate
Generate images onto a board. Spends credits.
brain:read
Read Echo's long-term memory for a room.
brain:write
Write and edit those memory notes.
Reference
Base URL https://app.coommit.com/api/v1.
This list is generated from the API's own discovery document, so it cannot describe a route that does not exist,
and it says exactly what the API says about itself. Fetch it yourself at any time:
GET /api/v1/ needs no auth. A few partner-tenant routes sit outside it and are
provisioned per agreement; every key that is not a tenant gets not_a_tenant from them.
Who the key belongs to, and the profile and notification settings attached to that account.
/me
account:read
The cheapest possible call, and the one to make first: it proves the key is live, and tells you which account it acts as. Every room route below authorises against that account, never against the key.
{
"ok": true,
"account": {
"id": 4821,
"name": "Ada Lovelace",
"email": "ada@acme.com",
"image": null,
"plan": "2year",
"locale": "en",
"createdAt": "2026-07-02T09:14:00.000Z"
},
"customization": {
"screenShare": true,
"recording": true,
"transcription": true,
"chat": true,
"handsFree": true,
"youtube": true,
"gdrive": true,
"figma": true,
"browserBox": true,
"draw": true,
"diagrams": true,
"aiImages": true,
"echo": true,
"tasks": true
},
"key": {
"id": 17,
"name": "release-bot",
"scopes": [
"account:read",
"rooms:read",
"rooms:write"
]
}
}
/me
account:write
Update profile (email cannot be changed)
Body
nameString?localeEn|fr?imageHttps url?/notifications
account:read
/notifications/:id/read
account:write
Mark one notification read.
/notifications/prefs
account:read
/notifications/prefs
account:write
Body
dashboardEnabledBool?inRoomEnabledBool?/notifications/read-all
account:write
A room is a persistent board plus the calls, tasks and memory that happened on it. Everything else on this page hangs off one.
/rooms
rooms:read
Every room the account is a member of, newest first. The field is `roomId`, and it is the `:roomId` the rest of this reference asks for.
Query
limit1-200.{
"ok": true,
"rooms": [
{
"roomId": "9f2c1a7e-4d3b-4c88-9a11-6e5f0b2d7c34",
"name": "Sprint 24 review",
"createdAt": "2026-08-26T10:02:11.000Z",
"coverImage": null,
"isTemporary": false,
"url": "/roomV3/9f2c1a7e-4d3b-4c88-9a11-6e5f0b2d7c34"
}
]
}
/rooms
rooms:write
Creates the room. Pass `actions` and it is created **already built**, so the board exists before anybody opens it. That is the difference between sending someone a link and sending them a room.
Body
nameString (required)templateIdString?friendIdsUserId[]?folderIdUuid?isTemporaryBool?actionsCanvas action[]? (create a fully built room in one call)/rooms/:roomId
rooms:read
Per-room metadata: folderId, memberCount, figmaEnabled.
/rooms/:roomId
rooms:delete
The room's creator only. A co-owner gets `creator_required`, because owning a room is not the same as having made it.
The room's CREATOR only. A co-owner gets creator_required.
/rooms/:roomId/cover
rooms:write
Editor+; https URL only (or null to clear)
Body
imageHttps url | null./rooms/:roomId/export
rooms:read
The whole board serialised to typed Markdown. The right call to feed a board back into a model as context.
Typed markdown serialization of the whole board.
Query
formatMarkdown./rooms/:roomId/leave
rooms:write
Member; a co-owner may leave while another owner remains; the creator must delete instead.
/rooms/:roomId/members
rooms:read
/rooms/:roomId/members/:userId/ban
rooms:write
Owner.
/rooms/:roomId/members/:userId/kick
rooms:write
Owner.
/rooms/:roomId/members/:userId/role
rooms:write
Owner.
Body
roleEditor|viewer./rooms/:roomId/members/:userId/unban
rooms:write
Owner.
/rooms/:roomId/move
rooms:write
Member; null folderId = back to root.
Body
folderIdUuid|null./rooms/:roomId/rename
rooms:write
Owner.
Body
nameString./rooms/batch-delete
rooms:delete
Creator-only per room; rooms you did not create count as failed.
Body
roomIdsUuid[] (max 500)/rooms/reorder
rooms:write
Per-user dashboard order.
Body
idsRoomId[] (max 2000)Read what is on the board, and write to it. One POST carries up to 100 actions and applies them live to everyone already in the room.
/rooms/:roomId/actions
rooms:write
The main write. Up to 100 actions in one call, applied live: anyone already in the room watches the boxes land. Returns `207` with a per-action result when some succeeded and some did not, so a partial build is never reported as a clean one.
Editor+ role; build/modify canvas content.
Body
actionsCanvas action[] (max 100)/rooms/:roomId/canvas
rooms:read
Everything currently on the board, as structured data rather than an image: boxes with their content and coordinates, connectors, tasks, polls, charts.
/rooms/:roomId/generate-image
image:generate
Text to image, straight onto the canvas. It spends the account owner's credits or their own model key, so it is capped per account rather than per key.
AI text-to-image onto the canvas; spends credits/BYOK; idempotent.
Body
promptString (required)aspectSquare|landscape|portrait?xInt?yInt?wInt?hInt?providerOpenai|gemini?/rooms/:roomId/screenshot
rooms:read
The board as a PNG, rendered by a real browser. Costs a headless session per call, which is why it has its own tight budget.
Rendered board as image/png (10/min per key; 503 when capture is unconfigured)
Query
width640-1920.height480-1080./rooms/:roomId/tasks
rooms:read
The room's tasks + task groups, without pulling the whole canvas.
/rooms/:roomId/upload-url
rooms:write
Hands back a signed upload URL. PUT the file to it, then place it on the board with `add_image`, `add_pdf`, `add_video` or `add_file` using the marker you get back.
Editor+; returns a signed GCS PUT URL. Upload a local file then add_image with the gcsMarker.
Body
contentTypeString.sizeBytesNumber.Schedule a call, read what was said, and turn a recap's proposals into real tasks.
/rooms/:roomId/calls
rooms:read
/rooms/:roomId/calls/:callId
calls:write
Editor+; reschedule (optimistic lock on expectedScheduledAt; moved calls email attendees)
Body
titleString?scheduledAtISO8601?durationMinutesInt?messageString?expectedScheduledAtISO8601?/rooms/:roomId/calls/:callId
calls:write
Editor+.
/rooms/:roomId/chat
rooms:write
Writes into the room's chat as the account, not as a bot.
Member; write a chat message as the account (30/min per key)
Body
textString (max 4096)/rooms/:roomId/commit
rooms:write
Runs the real Commit pipeline over the room, the same one the product runs, and takes about a minute. Someone has to be in the room for it to have anything to work with.
Editor+; run the REAL Commit pipeline (~1 min, needs someone in the room; 10/hr per account)
/rooms/:roomId/commit/approve-tasks
rooms:write
Turns those proposals into real tasks on the board, assigned to real people.
Editor+; turn Commit task proposals into real tasks (max 50)
Body
approvedTasks[{title, assignee?, assigneeUserId?, assigneeUserIds?, groupId?, expiresAt?}]/rooms/:roomId/history
rooms:read
Query
dateYYYY-MM-DD?/rooms/:roomId/push-recap
rooms:write
Owner; push the latest recap to the owner's Slack/Notion (already pushed → {ok, already:true})
Body
targetSlack|notion./rooms/:roomId/recordings
rooms:read
/rooms/:roomId/recordings/:recId/transcript
rooms:read
Full transcript + AI summary of one recording.
/rooms/:roomId/schedule
calls:write
Puts a call on the calendar and emails the attendees, so it simulates by default too.
Manage role; dry_run defaults TRUE.
Simulates unless you send dry_run: falseBody
titleString.scheduledAtISO8601.durationMinutesInt?attendeesEmail[]?dry_runBool (default true)/rooms/:roomId/summaries
rooms:read
Recaps of past calls, each with the task proposals Commit extracted. A proposal with a null `tasksApprovedAt` is still waiting for a decision.
Each summary carries recapId/callId/report/taskProposals/tasksApprovedAt. Pending proposals (tasksApprovedAt null) can be approved via commit/approve-tasks.
/rooms/:roomId/summaries/:summaryId
rooms:write
Editor+; edit a saved recap.
Body
summaryString (required)nextObjectiveString|null?Echo's long-term notes for a room. Sensitive by nature, so they sit behind their own opt-in scopes.
/rooms/:roomId/brain
brain:read
Read-only Echo long-term memory / Brain notes (sensitive, opt-in scope)
Query
limit1-100./rooms/:roomId/brain
brain:write
Editor+; create an Echo memory note.
Body
titleString?content_markdownString (required)/rooms/:roomId/brain/:noteId
brain:write
Editor+; edit a note.
Body
titleString?content_markdownString?/rooms/:roomId/brain/:noteId
brain:write
Manage role; soft-delete a note.
Links, email invitations and the queue of people asking to get in. Anything that sends a real email simulates by default.
/friends
account:read
Returns {accepted, received, sent}.
/friends/:friendshipId/accept
account:write
/friends/:friendshipId/decline
account:write
Also unfriends an accepted row.
/friends/invite-to-coommit
invites:write
Dry_run defaults TRUE; sends a real signup-invitation email.
Simulates unless you send dry_run: falseBody
emailString.dry_runBool (default true)/friends/request
account:write
Body
queryEmail or numeric userId./rooms/:roomId/access-requests
rooms:read
Manage role; pending requests.
/rooms/:roomId/access-requests/:requestId
rooms:write
Manage role.
Body
actionApprove|reject./rooms/:roomId/email-invite
invites:write
Sends a real email, so it **simulates unless you pass `dry_run: false`**. The simulated response tells you exactly what would have been sent.
Editor+; dry_run defaults TRUE; sends a real email.
Simulates unless you send dry_run: falseBody
emailString.dry_runBool (default true)/rooms/:roomId/invite
invites:write
Returns a shareable link. Nobody is emailed, so there is no `dry_run` to think about here.
Manage role; returns a shareable LINK (no email)
How the dashboard is organised, and the saved boards a new room can be built from.
/folders
rooms:read
/folders
rooms:write
Body
nameString.colorString?/folders/:folderId
rooms:write
Rooms inside fall back to the root.
/folders/:folderId/color
rooms:write
Body
colorOne of the folder palette, or null./folders/:folderId/rename
rooms:write
Body
nameString./folders/reorder
rooms:write
Body
idsFolderId[] in the new order./rooms/:roomId/save-template
rooms:write
Owner.
Body
nameString.descriptionString?/templates
rooms:read
Saved room templates (ids to pass to POST /rooms)
The key owner's follow-ups across every room they belong to.
/tasks
rooms:read
Cross-room, and the reason it exists: an agent asking "what did I commit to this week" should not have to walk every room to find out.
Cross-room: the key owner's tasks over every room they are a member of (their meeting follow-ups)
Query
assigneeMe (default) | any.statusOpen (default) | done | all.limit1-300.Canvas actions
Every action is { "type": "…", …args }. Batch up to 100 into one
POST /rooms/:roomId/actions and they apply in order, live, to everyone in the room.
Write on the board
Bring things in
Live data
Work to do
Structure
Edit what is there
[
{
"type": "add_title",
"html": "Sprint Review"
},
{
"type": "add_text",
"html": "<p>Agenda…</p>"
},
{
"type": "add_task",
"title": "Ship the API"
},
{
"type": "add_poll",
"question": "Ship on Friday?",
"options": [
"Yes",
"No",
"Needs one more week"
]
},
{
"type": "add_chart",
"variant": "pie",
"title": "Budget split",
"slices": [
{
"label": "Engineering",
"value": 60
},
{
"label": "Design",
"value": 25
},
{
"label": "Marketing",
"value": 15
}
]
},
{
"type": "insert_diagram",
"kind": "flowchart",
"labels": [
"Start",
"Review the PR",
"Approved?",
"Merge",
"Request changes",
"Done"
]
}
]
MCP
One endpoint, one key, 15 tools. There is no session to establish and nothing to expire: the agent key is the whole handshake.
claude mcp add coommit --transport http https://app.coommit.com/mcp \
--header "Authorization: Bearer $COOMMIT_KEY"
| Tool | What it does |
|---|---|
| coommit_get_skill | The full Coommit Connect skill document: every canvas action, every box type, and the build method. The tool to read before building. |
| coommit_list_rooms | The rooms on the key owner's account, most recent first. |
| coommit_get_room | One room's metadata, members and upcoming calls, in a single call. |
| coommit_get_canvas | The whole board: boxes, connectors, tasks, task groups, draw strokes. |
| coommit_get_history | Chat and live-call transcripts for one day, defaulting to the latest active one. |
| coommit_get_summaries | Meeting recaps for a room, most recent first. |
| coommit_my_tasks | The follow-ups delegated to the key owner across every room they belong to. |
| coommit_create_room | Create a room, optionally from a template, in a folder, with people invited and the canvas already seeded. Retry-safe. |
| coommit_build_room | Echo's Room Builder: describe the room and the AI designs and fills a brand-new board. Spends the account's own model key or credits. |
| coommit_canvas_actions | Apply up to 100 canvas actions to a room. |
| coommit_invite | Invite people: a shareable link, or real emails when you pass addresses. Dry run by default. |
| coommit_schedule_call | Put a call in a room's calendar. Dry run by default, because attendees get reminder emails when it is real. |
| coommit_commit | Run the real meeting-recap pipeline: a summary, a structured report and proposed tasks. |
| coommit_list_templates | The 10 native room templates plus the account's saved ones. |
| coommit_screenshot | Render the live board to a PNG. Look at your work after every build. |
Rate limits
Reads are generous because they are cheap. Anything that spends money or reaches a person is counted on the account, so more keys never buy more of it.
| Calls | Budget | Counted | Why |
|---|---|---|---|
| Reads | 120 / minute | per key | Everything that only returns JSON. |
| Room writes | 40 / hour | per key | Creating rooms, canvas actions, renames, moves. |
| Chat messages | 30 / minute | per key | A looping script must not be able to flood a room. |
| Board screenshots | 10 / minute | per key | Each one spends a real headless browser session. |
| Invitations | 30 / hour | per account | Reaches a human inbox. |
| Call scheduling | 30 / hour | per account | Reaches a human calendar. |
| Image generation | 20 / hour | per account | Spends credits, so keys cannot multiply it. |
| Commit runs | 10 / hour | per account | Runs the full pipeline for about a minute. |
Errors
Errors come back as { "error": "code", "message": "…" }. The code is stable and safe
to branch on; the message is written for whoever reads the log.
| Status | Code | What happened | What to do |
|---|---|---|---|
| 401 | missing_token | No Authorization header. | Send Authorization: Bearer cmt_live_…. |
| 401 | invalid_key | The key is unknown, revoked or expired. | Mint a new one in Settings → Agent keys. |
| 402 | trial_expired | The account's access has ended, so writes are off. | The account owner upgrades; reads keep working. |
| 403 | missing_scope | The key is valid but was not granted this scope. | The response names the scope in required_scope. Re-issue the key with it. |
| 403 | room_access_denied | The account behind the key is not a member of that room. | Add the account to the room, or use a room it belongs to. |
| 403 | editor_role_required | Member, but read-only on this room. | Raise the role to editor. |
| 403 | creator_required | Deleting a room is the creator's call, not a co-owner's. | Ask the creator, or leave the room instead. |
| 409 | idempotency_in_progress | The same Idempotency-Key is still running. | Wait and retry the same key rather than minting a new one. |
| 409 | idempotency_key_reused | That key was already used for a different action. | Use one key per logical write. |
| 429 | rate_limited | Over the budget for that class of call. | Back off. The response names the scope that ran out. |
| 503 | idempotency_unavailable | The key could not be reserved, so the write was refused rather than risked twice. | Retry shortly. retryable: true says so explicitly. |
One status is worth knowing on its own: a batch of canvas actions where some
succeeded and some did not returns 207 with a result per action.
A partial build is never reported as a clean one.
Scopes are granted one at a time, keys are revocable, and the first call costs nothing.