Skip to main content
The company board is the durable half of a team — a coordinator’s transcript scrolls away, the card that says who holds what does not. These are the routes a person, a script or a dashboard reads and writes it through; the agents themselves use the board_read and board_write tools, against the same rows. A card’s assignee is an agent’s name, and assigning a todo card with no open blocker is what starts that agent on it on the next tick of the board’s clock — from these routes exactly as from the tool.

Addressing a board

A board is addressed by its brd_ id or by the agt_ id of the agent that owns it — the coordinator. That is not a convenience. A board is minted on first use, one per agent, and a board with no cards has never told anyone its brd_ id — so on a brand-new team the agent id is the only reference a caller can have. One board per agent — unique (org_id, agent_id) — so POST /v1/boards is an ensure, not a create that can conflict. It is also not the only way a board comes into being: the first read or write against the agt_ spelling mints one too. Every agent on the owner’s current roster reads and writes this board through the tools; a roster member has no board of its own to address here unless its own multiagent.board is on.
There is no delete call. A card is never removed: a finished card is the record of what the team was asked to do, and a task that vanishes takes the answer to “what happened here” with it. Move it to done instead.
Every route here is gated by agents:read for reads and agents:write for writes: a board belongs to an agent, and the scope grammar is fixed.

The board object

string
Unique id (brd_…).
string
Always board.
string
The agent that owns the board (agt_…). One board per agent.
object
How many cards stand in each column: { "todo": n, "doing": n, "blocked": n, "done": n }. All four keys are always present, so 0 and “absent” never read the same. This is a real aggregate over the board, not the length of a page — which is the one fact a page of cards cannot state, because a page is truncated and a count is not.
string
Creation timestamp.

The card object

string
Unique id (crd_…). Derived rather than minted — from the board and the tool call for a card an agent wrote, from the board and the key for a card created here with one — so a retried write collides with the row it already made rather than duplicating it.
string
Always board_card.
string
The board this card is on (brd_…).
string
Short label, 1–200 characters.
string
The brief — what was asked, written by whoever created the card.
string | null
What the holder wrote back — what was done and what is left. A member cannot see another member’s conversation, so this is the hand-off; board_write update writes it, and a done must carry one.
string
One of todo, doing, blocked, done. Fixed — not configurable columns. done is terminal for the agents’ tool; this route can still change it.
string | null
An agent’s name, not an id. null is unclaimed. A todo card with an assignee and no open blocker is due to be worked: the board’s clock starts a session of that agent on it.
string[]
The crd_ ids this card waits on. A blocked card with an empty list is stuck on the world. When every card in the list reaches done, this card is promoted back to todo — on the next read, and on the next tick.
string | null
The session (ses_…) the board’s clock started on this card, or null. Set when the assignee is woken; cleared when the card is reassigned or healed back to todo, so the new hand is woken in turn. Read-only: it is not accepted in any request body.
string | null
The last done or blocked status the board’s owner was told about in a digest, or null. A card whose status differs from this is unreported and goes into the owner’s next digest. Read-only.
integer
How many sessions the board’s clock has started for this card. A card is started at most three times; the next time it would be due it is parked blocked for the owner instead. A schedule’s standing card is the exception: the clock never starts it — its own fire does — so the count climbs with the schedule and the cap does not apply while the card is the schedule’s.
string
Creation timestamp.
string
Last modification timestamp.

The comment object

string
Unique id (bcm_…).
string
Always board_comment.
string
The card commented on (crd_…).
string
Who wrote it, as an id the server derived — never a name the caller supplied. A person’s comment carries their usr_ or key_ principal; a member’s, written through board_write, carries the ses_ id of the thread. The prefix is what lets a reader tell an agent’s comment from a person’s.
string
The comment text.
string
Creation timestamp.

Ensure a board

POST /v1/boards → the board object. 201 Created when this call minted the row, 200 OK when it found one already there.
string
required
The agent that owns the board (agt_…). 404 not_found if this organization has no such agent.
Calling it twice is not a conflict. There is one board per agent, so the second call answers the same board and the status code is the only difference between the two outcomes — a caller that does not care ignores it and reads the same board either way.

Get a board

GET /v1/boards/{id} → the board object. {id} is a brd_ id or the owning agt_ id. This is the read that answers a board’s own id, its owner and how much work stands on it. The cards routes answer cards.

Read an agent’s board

GET /v1/agents/{id}/board → the agent’s cards, in column order (todo, doing, blocked, done) and then oldest first. Mints the board if the agent has none, so a team that has not started answers an empty page rather than a 404. This is the owner’s board: a roster member has no board of its own, and its cards are on its coordinator’s. The response is the standard pagination envelope of card objects.

List a board’s cards

GET /v1/boards/{id}/cards → the same cards, addressed by board reference. {id} is a brd_ id or the owning agt_ id. Same envelope, same column order.

Add a card

POST /v1/boards/{id}/cards → 201 Created with the card object — or, under a key the board has seen before, 200 OK with the card that key already made.
string
required
1–200 characters.
string
The brief, up to 8192 characters.
string
todo (default), doing, blocked or done. A card created with a non-empty blocked_by starts blocked unless this says otherwise.
string | null
An agent’s name, up to 128 characters, and it must be one a live agent in this organization carries — a name nobody answers to is refused (see Refusals). Defaults to null. With an assignee and no open blocker, the card is due and the agent is started on it on the next tick.
string[]
Up to 50 crd_ ids this card waits on.
string
An idempotency key, 1–128 characters, unique within the board. The card’s id is derived from it, so a second POST under the same key answers the existing card with 200 OK and writes nothing — the rest of the body is ignored on a replay. Without it every request writes its own card. This is what lets a template seed a board on every apply without doubling its cards.
The replay answers the card as it stands now — here already claimed and being worked — not as the request described it. The status code is how a seeding script tells created from unchanged.

Get one card

GET /v1/boards/{id}/cards/{cid} → the card object. A card that is not on the named board answers 404 not_found, even if the id exists elsewhere in your organization.

Update a card

PATCH /v1/boards/{id}/cards/{cid} → 200 OK with the updated card object. Every field of the create body but key is accepted and every one is optional, so a move is {"status": "doing"} and nothing else.
string
1–200 characters.
string
Replaces the brief, up to 8192 characters.
string
Replaces the holder’s note, up to 8192 characters — the same field a member’s board_write update writes.
string
todo, doing, blocked or done. No race guard: a card in done can be moved here. A move to done still needs a note — on the call or already on the card.
string | null
Hand the card to an agent by name — one a live agent in this organization carries, or the write is refused (see Refusals) — or null to unclaim it. Omitting the key leaves the current holder alone; sending null does not. Writing this field clears woken_session_id, so a todo card handed to someone new is due again and the new assignee is started on it on the next tick.
string[]
Replaces the list of crd_ ids this card waits on, up to 50.
This is an operator’s override, and it is deliberately more powerful than the agents’ own board_write. A person has the card on screen and outranks the claim guards, which exist only to stop two members stepping on each other — so this route never answers a refusal, and this is the only way to retitle or re-block a card after it is created.

Refusals

The routes on this page fail the ordinary ways — 400 invalid_request for a body the schema rejects, 404 not_found for a board or card that is not there. The PATCH refuses exactly one move: { status: "done" } with no note on the call and none on the card is 409 state_conflict, its message opening with missing_note. Both the POST and the PATCH also refuse an assignee that is not a live agent’s name in the organization: 400 validation_failed, param: "assignee", its message opening with unknown_member. The clock joins assignee to a live agent, so a card handed to a name nobody carries would never be due and never worked — it would sit in todo reading “waiting to start” indefinitely. Sending null unclaims and is never refused. The check happens at the write: an agent renamed or deleted afterwards leaves the card holding a name nothing answers to, which the board shows as a stalled card. Every other compare-and-set guard lives in the agents’ board_write, where a lost race comes back as a named tool result rather than an error: They are described in full on the board page. An operator who needs the same outcome the tool refused — reopening a done card, say — has the PATCH above.

Read a card’s comments

GET /v1/boards/{id}/cards/{cid}/comments → the card’s comments, oldest first, in the standard pagination envelope. A card’s comments are a conversation, so they are not reversed the way a feed is. A cid that is not on the named board answers 404 not_found, the same guard the card read sits behind.

Comment on a card

POST /v1/boards/{id}/cards/{cid}/comments → 201 Created with the comment object.
string
required
The comment text, 1–8192 characters.
There is no author field, and sending one is a 400 invalid_request. The server signs the comment with the principal behind your key, so nobody has to invent a name for themselves — or for anyone else. Nothing used to stop an operator signing a comment fact-checker.

The board, in concept

The four statuses, the two tools agents call, how a card starts its assignee, and how the board composes with delegation.