Coommit Connect  ·  v1

One call, and the board is already built.

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.

64REST endpoints
41Canvas actions
15MCP tools
10Scopes
POST /api/v1/rooms

Two ways in

Pick the one that matches who is calling.

Both reach the same engine and obey the same permissions. The difference is who writes the call: your code, or a model.

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.

MCP

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

Three steps to a room somebody can open.

Every response below is real. Nothing here is a placeholder you have to substitute twice.

Mint a key, and only the scopes you need

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.

shell
# read, do not type: -s keeps the key out of your shell history
read -rs COOMMIT_KEY && export COOMMIT_KEY

Prove the key is live

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
curl https://app.coommit.com/api/v1/me \
  -H "Authorization: Bearer $COOMMIT_KEY"
200 OK
{
  "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.

Create the room already built

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
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

An agent retries. The API assumes it.

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.

dry_run: true

Anything that reaches a human simulates first

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.

Idempotency-Key

A retried write executes once

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.

per account

Ten keys cannot multiply the spend

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.

audit

Refusals are logged as loudly as successes

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

10 scopes, granted one at a time.

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.

4 routes
account:write

Update that profile and its notification settings.

7 routes
rooms:read

Read rooms, boards, tasks, members, recaps and exports.

16 routes
rooms:write

Create rooms, write to boards, move things, run Commit.

24 routes
rooms:delete

Delete rooms. Kept apart from rooms:write on purpose, because most agents never need it.

2 routes
invites:write

Invite people, by link or by email.

3 routes
calls:write

Schedule, reschedule and cancel calls.

3 routes
image:generate

Generate images onto a board. Spends credits.

1 route
brain:read

Read Echo's long-term memory for a room.

1 route
brain:write

Write and edit those memory notes.

3 routes

Reference

The whole public surface, read from the API itself.

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.

Account

Who the key belongs to, and the profile and notification settings attached to that account.

GET /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.

200 OK
{
  "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"
    ]
  }
}
POST /me account:write

Update profile (email cannot be changed)

Body

nameString?
localeEn|fr?
imageHttps url?
GET /notifications account:read
POST /notifications/:id/read account:write

Mark one notification read.

GET /notifications/prefs account:read
POST /notifications/prefs account:write

Body

dashboardEnabledBool?
inRoomEnabledBool?
POST /notifications/read-all account:write

Rooms

A room is a persistent board plus the calls, tasks and memory that happened on it. Everything else on this page hangs off one.

GET /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.
200 OK
{
  "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"
    }
  ]
}
POST /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)
GET /rooms/:roomId rooms:read

Per-room metadata: folderId, memberCount, figmaEnabled.

DELETE /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.

POST /rooms/:roomId/cover rooms:write

Editor+; https URL only (or null to clear)

Body

imageHttps url | null.
GET /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.
POST /rooms/:roomId/leave rooms:write

Member; a co-owner may leave while another owner remains; the creator must delete instead.

GET /rooms/:roomId/members rooms:read
POST /rooms/:roomId/members/:userId/ban rooms:write

Owner.

POST /rooms/:roomId/members/:userId/kick rooms:write

Owner.

POST /rooms/:roomId/members/:userId/role rooms:write

Owner.

Body

roleEditor|viewer.
POST /rooms/:roomId/members/:userId/unban rooms:write

Owner.

POST /rooms/:roomId/move rooms:write

Member; null folderId = back to root.

Body

folderIdUuid|null.
POST /rooms/:roomId/rename rooms:write

Owner.

Body

nameString.
POST /rooms/batch-delete rooms:delete

Creator-only per room; rooms you did not create count as failed.

Body

roomIdsUuid[] (max 500)
POST /rooms/reorder rooms:write

Per-user dashboard order.

Body

idsRoomId[] (max 2000)

Canvas

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.

POST /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)
GET /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.

POST /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?
GET /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.
GET /rooms/:roomId/tasks rooms:read

The room's tasks + task groups, without pulling the whole canvas.

POST /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.

Calls & recaps

Schedule a call, read what was said, and turn a recap's proposals into real tasks.

GET /rooms/:roomId/calls rooms:read
PATCH /rooms/:roomId/calls/:callId calls:write

Editor+; reschedule (optimistic lock on expectedScheduledAt; moved calls email attendees)

Body

titleString?
scheduledAtISO8601?
durationMinutesInt?
messageString?
expectedScheduledAtISO8601?
DELETE /rooms/:roomId/calls/:callId calls:write

Editor+.

POST /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)
POST /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)

POST /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?}]
GET /rooms/:roomId/history rooms:read

Query

dateYYYY-MM-DD?
POST /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.
GET /rooms/:roomId/recordings rooms:read
GET /rooms/:roomId/recordings/:recId/transcript rooms:read

Full transcript + AI summary of one recording.

POST /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: false

Body

titleString.
scheduledAtISO8601.
durationMinutesInt?
attendeesEmail[]?
dry_runBool (default true)
GET /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.

PATCH /rooms/:roomId/summaries/:summaryId rooms:write

Editor+; edit a saved recap.

Body

summaryString (required)
nextObjectiveString|null?

Echo memory

Echo's long-term notes for a room. Sensitive by nature, so they sit behind their own opt-in scopes.

GET /rooms/:roomId/brain brain:read

Read-only Echo long-term memory / Brain notes (sensitive, opt-in scope)

Query

limit1-100.
POST /rooms/:roomId/brain brain:write

Editor+; create an Echo memory note.

Body

titleString?
content_markdownString (required)
PATCH /rooms/:roomId/brain/:noteId brain:write

Editor+; edit a note.

Body

titleString?
content_markdownString?
DELETE /rooms/:roomId/brain/:noteId brain:write

Manage role; soft-delete a note.

Invitations & access

Links, email invitations and the queue of people asking to get in. Anything that sends a real email simulates by default.

GET /friends account:read

Returns {accepted, received, sent}.

POST /friends/:friendshipId/accept account:write
POST /friends/:friendshipId/decline account:write

Also unfriends an accepted row.

POST /friends/invite-to-coommit invites:write

Dry_run defaults TRUE; sends a real signup-invitation email.

Simulates unless you send dry_run: false

Body

emailString.
dry_runBool (default true)
POST /friends/request account:write

Body

queryEmail or numeric userId.
GET /rooms/:roomId/access-requests rooms:read

Manage role; pending requests.

POST /rooms/:roomId/access-requests/:requestId rooms:write

Manage role.

Body

actionApprove|reject.
POST /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: false

Body

emailString.
dry_runBool (default true)
POST /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)

Folders & templates

How the dashboard is organised, and the saved boards a new room can be built from.

GET /folders rooms:read
POST /folders rooms:write

Body

nameString.
colorString?
DELETE /folders/:folderId rooms:write

Rooms inside fall back to the root.

POST /folders/:folderId/color rooms:write

Body

colorOne of the folder palette, or null.
POST /folders/:folderId/rename rooms:write

Body

nameString.
POST /folders/reorder rooms:write

Body

idsFolderId[] in the new order.
POST /rooms/:roomId/save-template rooms:write

Owner.

Body

nameString.
descriptionString?
GET /templates rooms:read

Saved room templates (ids to pass to POST /rooms)

Tasks

The key owner's follow-ups across every room they belong to.

GET /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

41 things you can put on a board, or do to one.

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

  • add_title
  • add_text
  • add_markdown
  • add_shape
  • add_stroke

Bring things in

  • add_image
  • add_pdf
  • add_video
  • add_file
  • add_link
  • add_youtube
  • add_browser
  • add_figma
  • navigate_browser

Live data

  • add_poll
  • edit_poll
  • set_poll_state
  • add_chart
  • edit_chart_slice
  • add_calendar
  • add_goal

Work to do

  • add_task
  • update_task
  • delete_task
  • add_task_group
  • rename_task_group
  • delete_task_group

Structure

  • add_connector
  • edit_connector
  • delete_connector
  • insert_diagram
  • insert_template
  • tidy_canvas

Edit what is there

  • edit_box
  • move_box
  • resize_box
  • style_box
  • duplicate_box
  • delete_box
  • clear_canvas
  • clear_drawing
the API's own canonical examples
[
  {
    "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

The same account, handed to a model.

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
claude mcp add coommit --transport http https://app.coommit.com/mcp \
  --header "Authorization: Bearer $COOMMIT_KEY"
ToolWhat it does
coommit_get_skillThe full Coommit Connect skill document: every canvas action, every box type, and the build method. The tool to read before building.
coommit_list_roomsThe rooms on the key owner's account, most recent first.
coommit_get_roomOne room's metadata, members and upcoming calls, in a single call.
coommit_get_canvasThe whole board: boxes, connectors, tasks, task groups, draw strokes.
coommit_get_historyChat and live-call transcripts for one day, defaulting to the latest active one.
coommit_get_summariesMeeting recaps for a room, most recent first.
coommit_my_tasksThe follow-ups delegated to the key owner across every room they belong to.
coommit_create_roomCreate a room, optionally from a template, in a folder, with people invited and the canvas already seeded. Retry-safe.
coommit_build_roomEcho'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_actionsApply up to 100 canvas actions to a room.
coommit_inviteInvite people: a shareable link, or real emails when you pass addresses. Dry run by default.
coommit_schedule_callPut a call in a room's calendar. Dry run by default, because attendees get reminder emails when it is real.
coommit_commitRun the real meeting-recap pipeline: a summary, a structured report and proposed tasks.
coommit_list_templatesThe 10 native room templates plus the account's saved ones.
coommit_screenshotRender the live board to a PNG. Look at your work after every build.

Rate limits

Budgets sized to what a call costs.

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.

CallsBudgetCountedWhy
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

Every refusal names itself.

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.

StatusCodeWhat happenedWhat 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.

Give an agent a key and see what it builds.

Scopes are granted one at a time, keys are revocable, and the first call costs nothing.