Project Boards
Every project has one Kanban board, created with the project: columns in order, cards in each column, and a set of labels. Cards are never deleted, only archived; archived cards keep their content and can be restored.
All endpoints require authentication via X-API-Key header and the appropriate scope: projects:read to read a board, projects:write to change it. You need access to the project.
Reading the board
GET /api/v1/projects/:id/board
The board: its columns in order, the cards in each column, and its labels. The card, column and label ids returned here are the ones the other endpoints take.
Scope: projects:read
{
"data": {
"boardId": "brd_abc",
"projectId": "proj_abc123",
"labels": [{ "labelId": "lbl_1", "name": "bug", "color": "#ff3366" }],
"columns": [
{
"columnId": "col_backlog",
"name": "Backlog",
"cards": [
{
"cardId": "card_1",
"title": "Retrain the churn model",
"description": "Use the October data.",
"labelIds": ["lbl_1"],
"assigneeIds": ["user_456"],
"dueDate": "2026-10-20T00:00:00Z"
}
]
}
]
}
}
Creating a card returns its id: { "data": { "cardId": "card_2" } }.
GET /api/v1/projects/:id/board/archived-cards
The board's archived cards, newest first, each with who archived it and the column it came from. Paged.
Scope: projects:read
| Query parameter | Type | Description |
|---|---|---|
q | string | Text in the card's title, description, or the name of the column it was archived from |
skip | number | Cards to skip, for paging (default 0) |
limit | number | Page size, up to 100 (default 25) |
GET /api/v1/projects/:id/board/members
The people on the board, with their user ids and names. Only entries with assignable: true (the project's owner and collaborators) can be assigned to a card.
Scope: projects:read
Columns
A new board starts with Ice Box, Backlog, Work In Progress, Ready For Review and Complete.
POST /api/v1/projects/:id/board/columns
Add a column at the end of the board.
Scope: projects:write
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Column name, up to 60 characters |
Response 201 Created: the new column, { columnId, name, position }, last on the board.
POST /api/v1/projects/:id/board/columns/reorder
Set the left-to-right order of the columns. columnIds must list every column on the board, in the order you want; a partial list is refused.
Scope: projects:write
| Field | Type | Required | Description |
|---|---|---|---|
columnIds | string[] | Yes | Every column id on the board, in order |
Response 200 OK: the columns in their new order, each { columnId, name, position }.
PATCH /api/v1/projects/:projectId/board/columns/:id
Rename a column. Its cards stay where they are.
Scope: projects:write
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | New column name |
Response 200 OK: the column, { columnId, name, position }.
DELETE /api/v1/projects/:projectId/board/columns/:id
Remove a column. Any cards still in it are archived, not deleted: they keep their content, appear in the archive labelled with the column they came from, and can be restored into any remaining column. The response says how many were archived.
Scope: projects:write
Labels
Response 200 OK: { archivedCount }, how many of its cards were archived.
POST /api/v1/projects/:id/board/labels
Create a label, or update one by passing its labelId.
Scope: projects:write
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Label name, up to 40 characters |
color | string | Yes | One of the board palette: #6571ff (blue), #05a34a (green), #fbbc06 (yellow), #ff3366 (red), #0dcaf0 (teal), #7987a1 (gray). Any other value is refused. |
labelId | string | No | An existing label to update; omit to create one |
Response 201 Created: the label, { labelId, name, color } (the same labelId when it updated one).
DELETE /api/v1/projects/:projectId/board/labels/:id
Delete a label. It is also removed from every card that carries it, archived cards included.
Scope: projects:write
Response 204 No Content
Cards
POST /api/v1/projects/:id/board/cards
Add a card at the end of a column.
Scope: projects:write
| Field | Type | Required | Description |
|---|---|---|---|
columnId | string | Yes | The column to add the card to |
title | string | Yes | Card title, up to 200 characters |
description | string | No | Markdown description |
Response 201 Created: the new card, as the board shows it (cardId, title, description, labelIds, assigneeIds, dueDate, dueComplete, position, createdBy, createdAt, updatedAt).
PATCH /api/v1/projects/-/board/cards/:id
Update a card. Send only the fields you are changing: anything you leave out is kept, so two people changing different fields do not overwrite each other.
Scope: projects:write
| Field | Type | Description |
|---|---|---|
title | string | New title |
description | string | New markdown description |
labelIds | string[] | The card's complete set of labels (each must exist on the board) |
assigneeIds | string[] | The card's complete set of assignees (assignable board members) |
dueDate | string | null | ISO date, or null to clear it |
dueComplete | boolean | Whether the due date is met |
Response 200 OK: the updated card, as the board shows it.
POST /api/v1/projects/-/board/cards/:id/move
Move a card to a column. With only toColumnId it goes to the end of that column. To place it exactly, also pass the card that should end up directly above it (prevCardId) and/or below it (nextCardId), both already in the target column.
Scope: projects:write
| Field | Type | Required | Description |
|---|---|---|---|
toColumnId | string | Yes | Destination column |
prevCardId | string | No | The card that should end up directly above this one |
nextCardId | string | No | The card that should end up directly below this one |
Response 200 OK: the card, as the board shows it, in its new column and position.
POST /api/v1/projects/-/board/cards/:id/archive
Archive a card. This is how a card leaves the board; there is no delete. It keeps its content and the column it came from, and can be restored.
Scope: projects:write
Response 200 OK: the card, archived.
POST /api/v1/projects/-/board/cards/:id/restore
Bring an archived card back, at the end of the column you name. The column is always explicit because the one it came from may have been removed since.
Scope: projects:write
| Field | Type | Required | Description |
|---|---|---|---|
toColumnId | string | Yes | The column to restore the card into |
Example
# Add a card to the Backlog and assign it
BOARD=$(curl -s -H "X-API-Key: $KEY" "$BASE/api/v1/projects/$PROJECT/board")
BACKLOG=$(echo "$BOARD" | jq -r '.data.columns[] | select(.name=="Backlog") | .columnId')
CARD=$(curl -s -X POST -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
-d "{\"columnId\":\"$BACKLOG\",\"title\":\"Retrain the churn model\"}" \
"$BASE/api/v1/projects/$PROJECT/board/cards" | jq -r '.data.cardId')
curl -s -X PATCH -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
-d '{"assigneeIds":["<user id from /board/members>"]}' "$BASE/api/v1/projects/-/board/cards/$CARD"
Response 200 OK: the card, as the board shows it, at the end of the column.