brd_ id or by the agt_ id of the agent that owns it. That is not a convenience: a board with no cards has never told anyone its id, so the agent id is the only reference a caller can have on a team that has not written a card yet.
The four statuses are fixed — todo, doing, blocked, done — and are not configurable columns. The reason a card is stuck rides in blocked_by, as data rather than as a lane.
create
POST /v1/boards. Ensures the agent’s board. There is one board per agent, so a second call is not a conflict — it answers the same board, and the status code is the only difference: 201 when this call minted it, 200 when it found one.
get
GET /v1/boards/{id}. One board — its brd_ id, the agt_ id of its owner, and cards, which is how many stand in each of the four columns. boardRef is either spelling.
A Board is { id, object, agent_id, cards: { todo, doing, blocked, done }, created_at }. The counts are a real aggregate, not the length of a page: a page of cards is truncated and cannot state one.
cards.list
GET /v1/boards/{id}/cards. boardRef is a brd_ id or an agt_ id.
cards.ofAgent
GET /v1/agents/{id}/board. The same cards, reached from the agent, in column order. Creates the board on first read, so it answers an empty page rather than a 404 for a team that has not started.
cards.create
POST /v1/boards/{id}/cards. CardCreate is { title, body?, status?, assignee?, blocked_by? }; everything but the title has a default — todo, unassigned, unblocked.
cards.get
GET /v1/boards/{id}/cards/{cid}.
cards.update
PATCH /v1/boards/{id}/cards/{cid}. Every field is optional, so a move is { status: "doing" } and nothing else. assignee: null unclaims the card; omitting the key leaves the current assignee alone.
This is an operator’s override and is deliberately more powerful than the board_write tool an agent calls: it outranks the claim guards that stop two members stepping on each other, and it is the only way to retitle or reassign a card.
cards.comments
GET /v1/boards/{id}/cards/{cid}/comments. A card’s comments, oldest first — they are a conversation, not a feed, so they are not reversed. A cardId that is not on the named board answers 404, the same guard cards.get sits behind.
cards.comment
POST /v1/boards/{id}/cards/{cid}/comments. There is no author field: the server signs the comment with the principal behind your key, so author comes back as your usr_ or key_ id. A member commenting through board_write is signed with the ses_ id of its own thread, which is how a reader tells an agent’s comment from a person’s.