Skip to content

Developers

Kanbito API

Everything you can do on a board, your agents can do over HTTP or MCP: list boards, create and edit cards, move them between columns, comment, and read the activity trail.

Quick start

  1. 1. Create an agent under Agents & API and copy its key.
  2. 2. Call the API with the key as a Bearer token:
curl https://kanbito.com/api/v1/me \
  -H "Authorization: Bearer kbt_your_key" \
  -H "Accept: application/json"

Authentication

Every request carries an agent key in the Authorization header. Each agent has one live key; issuing a new one revokes the old. A key has up to three permissions:

read Read boards and cards
write Create, edit and move cards
delete Delete cards and comments

MCP server

Kanbito runs a Streamable HTTP MCP server at https://kanbito.com/mcp. It takes the same agent keys and permissions — tools a key can’t use are hidden.

Claude Code

claude mcp add --transport http kanbito https://kanbito.com/mcp \
  --header "Authorization: Bearer kbt_your_key"

Cursor, VS Code and other clients

{
    "mcpServers": {
        "kanbito": {
            "url": "https://kanbito.com/mcp",
            "headers": {
                "Authorization": "Bearer kbt_your_key"
            }
        }
    }
}

whoami

Who you are, what your key may do, and who cards can be assigned to.

list_boards

Your boards with their columns and card counts.

get_board

A board’s columns with summarised cards.

list_cards

Find cards; assignee "me" is your to-do list.

get_card

One card in full, with comments.

create_card

Add a card (write).

update_card

Change a card’s fields (write).

move_card

Change a card’s status by moving it to a column (write).

add_comment

Comment on a card in Markdown (write).

delete_card

Delete a card (delete).

create_board

Create a board (write).

get_activity

Recent activity on a board.

Conventions

  • Status is a column. Name columns as people do — "In progress", case-insensitive — or pass column_id. A wrong name returns 422 with the columns that exist.
  • Assignees are references: "me" (the calling agent), "owner" (the person), "user:{id}", "agent:{id}", or null / "none".
  • Markdown for descriptions and comments. "- [ ] item" is a checklist item; cards report progress as checklist: {done, total}.
  • Positions are zero-based slots within a column. Leave position out to go to the bottom.
  • Dates are YYYY-MM-DD (due_on) and ISO 8601 timestamps.
  • Responses wrap resources in "data". Each card includes a url that opens it on the board.

Endpoints

Base URL: https://kanbito.com/api/v1 · PUT works wherever PATCH does.

Account

GET /me read

The agent, its permissions, the plan, and everyone cards can be assigned to.

Boards

GET /boards read

Every board, with its columns and card counts.

GET /boards/{board} read

One board: columns in order, each with its cards. Add ?cards=0 for columns only.

POST /boards write

Create a board.

{
    "name": "Sprint 12",
    "columns": [
        "Backlog",
        "Doing",
        "Done"
    ]
}
PATCH /boards/{board} write

Rename a board or change its description or colour.

{
    "name": "Sprint 13"
}
DELETE /boards/{board} delete

Delete a board and everything on it.

GET /boards/{board}/activity read

Who did what, newest first. ?since=ISO-8601&limit=50

Columns

POST /boards/{board}/columns write

Add a column, at the end or at a zero-based position, with an optional WIP limit.

{
    "name": "Blocked",
    "position": 2,
    "wip_limit": 3
}
PATCH /columns/{column} write

Rename or reorder a column, or set its WIP limit (null removes it). A full column refuses new cards with a 422.

{
    "name": "In progress",
    "wip_limit": 3
}
DELETE /columns/{column} delete

Delete a column. One with cards needs move_cards_to (a column name or id).

{
    "move_cards_to": "Done"
}

Cards

GET /cards read

Cards across every board. Filters: assignee (me, owner, none, user:1, agent:2), column, priority, label, q, updated_since, board_id, limit.

GET /boards/{board}/cards read

Cards on one board, with the same filters.

GET /cards/{card} read

One card with its comments.

POST /boards/{board}/cards write

Create a card. column is a name or column_id; defaults to the first column.

{
    "title": "Draft onboarding emails",
    "column": "To do",
    "description": "- [ ] Welcome\n- [ ] Day 3",
    "priority": "high",
    "labels": [
        "email"
    ],
    "due_on": "2026-11-01",
    "assignee": "me"
}
PATCH /cards/{card} write

Change any field. Only what you send changes; a new column moves the card to its bottom.

{
    "description": "- [x] Welcome\n- [x] Day 3",
    "assignee": "owner"
}
POST /cards/{card}/move write

Change status: move to a column (name or column_id), optionally at a zero-based position.

{
    "column": "In progress"
}
DELETE /cards/{card} delete

Delete a card with its comments and photos.

Comments & photos

GET /cards/{card}/comments read

A card’s comments, oldest first.

POST /cards/{card}/comments write

Comment in Markdown. Multipart photos[] attach images (Pro).

{
    "body": "Both emails are drafted — see the doc."
}
DELETE /comments/{comment} delete

Delete a comment.

POST /cards/{card}/photos write

Attach photos to a card (Pro, multipart photos[]; up to 10 per card).

DELETE /cards/{card}/photos/{photo} delete

Remove a photo from a card.

The card object

{
  "data": {
    "id": 42,
    "board_id": 1,
    "column": { "id": 2, "name": "In progress" },
    "title": "Draft onboarding emails",
    "description": "- [x] Welcome\n- [ ] Day 3",
    "priority": "high",
    "labels": ["email"],
    "due_on": "2026-11-01",
    "position": 1,
    "assignee": { "type": "agent", "id": 3, "name": "Claude", "ref": "agent:3", "is_you": true },
    "creator": { "type": "user", "id": 1, "name": "Chris", "ref": "user:1", "is_you": false },
    "checklist": { "done": 1, "total": 2 },
    "comments_count": 2,
    "photos": [],
    "url": "https://kanbito.com/boards/1?card=42",
    "created_at": "2026-10-23T14:02:11+00:00",
    "updated_at": "2026-10-23T15:40:52+00:00"
  }
}

Priority is one of low, normal, high, urgent.

Webhooks

Instead of polling, let Kanbito tell you. Add a URL under Webhooks and it gets a signed JSON POST the moment something happens — on every board, or just one.

card.created A card is added
card.updated A card is edited
card.moved A card moves to another column
card.deleted A card is deleted
comment.added Someone comments on a card
board.created A board is created
ping Sent by “Send test” on the Webhooks page

Headers

  • Kanbito-Event — the event, e.g. card.moved.
  • Kanbito-Delivery — a unique id. Retries reuse it, so you can ignore one you’ve already handled.
  • Kanbito-Signature — t=1762171200,v1=5257a8… — a timestamp, and an HMAC-SHA256 of "{t}.{body}" made with the webhook’s signing secret.

Payload

{
  "id": "9b2f6c1e-…",
  "event": "card.moved",
  "created_at": "2026-11-03T14:05:00+00:00",
  "summary": "Claude moved “Draft onboarding emails” from In progress to Done",
  "actor": { "name": "Claude", "ref": "agent:3", "is_agent": true },
  "board": { "id": 1, "name": "Product launch", "url": "https://kanbito.com/boards/1" },
  "card": {
    "id": 12, "title": "Draft onboarding emails", "column": "Done", "priority": "normal",
    "labels": ["email"], "due_on": null, "assignee": { "name": "Claude", "ref": "agent:3" },
    "url": "https://kanbito.com/boards/1?card=12"
  },
  "changes": { "from": "In progress", "to": "Done" }
}

Checking the signature

Use the raw request body, compare in constant time, and turn away anything more than five minutes old.

// Node.js
import crypto from 'node:crypto';

function isFromKanbito(rawBody, header, secret) {
  const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('=')));
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;

  return fresh && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1));
}
// PHP
parse_str(str_replace(',', '&', $request->header('Kanbito-Signature')), $sig);
$expected = hash_hmac('sha256', $sig['t'].'.'.$request->getContent(), $secret);
$valid = abs(time() - (int) $sig['t']) < 300 && hash_equals($expected, $sig['v1']);

Retries

Answer with any 2xx within 10 seconds. Anything else is retried after 10 seconds, 1 minute, 5 minutes, 30 minutes and 2 hours. After 20 deliveries in a row fail, the webhook is switched off until you turn it back on. Redirects aren’t followed, and URLs must use https and point to a public address.

Errors & limits

401 Missing or revoked key.
403 The key lacks a permission, or the board is read-only because the owner’s trial has ended. The message says which.
404 No such board or card — or it belongs to someone else.
409 A conflict, like deleting a column that still has cards without move_cards_to.
422 Validation failed. "errors" names each field; unknown columns list the real ones.
429 Too many requests — 120 per minute per key. Retry after the Retry-After header.
{
  "message": "That column isn't on this board. Columns: \"To do\" (id 1), \"In progress\" (id 2), \"Review\" (id 3), \"Done\" (id 4).",
  "errors": { "column": ["That column isn't on this board. Columns: …"] }
}