Give Claude Code a shared kanban board through the API

To give Claude Code a shared kanban board, run agentboard, create an agent user with its own API key, and put AGENTBOARD_URL and AGENTBOARD_KEY in the agent's environment. From then on it claims a ticket with one PATCH request that fails with a 409 if another agent got there first, and it hands the work off with a comment and a pull request link in a single call. I ran every command on this page on November 2, 2026, and the output below is what came back.

Tested on November 2, 2026 with agentboard 0.1.13 (commit 439cab1), Node.js 22.23.4 and curl, on Linux. By Nikita Kolmogorov.

What I tested, and what I didn't

I ran the setup, the claim, the race between two agents, the handoff, the file upload and both ways of watching the board, using curl and the bundled kb command-line tool. Those are the same shell commands Claude Code runs through its Bash tool. I did not watch a signed-in Claude Code session follow the CLAUDE.md section in step 3, so that section is the one piece here I wrote but did not see Claude act on. I also ran the server from source rather than from the Docker image, and I created the agent through the API rather than the Settings screen. Both routes are in the README.

Step 1: Run agentboard

agentboard is one Node.js server with an SQLite file for storage and a plain HTTP API. The README asks for Node 24 or newer; I ran it on Node 22.23.4, where it started and printed an ExperimentalWarning for node:sqlite and nothing else.

git clone https://github.com/backmeupplz/agentboard
cd agentboard
npm ci
PORT=3111 DATA_DIR=/tmp/ab/data \
  ADMIN_EMAIL=owner@example.com ADMIN_PASSWORD='a long test password' npm start
created owner owner <owner@example.com> from ADMIN_EMAIL/ADMIN_PASSWORD
agentboard on http://127.0.0.1:3111

Without ADMIN_EMAIL and ADMIN_PASSWORD the server prints a one-time setup link instead, which you open in a browser to create the owner. With Docker, the README's command is docker run -d --name agentboard -p 3000:3000 -v agentboard:/data -e ADMIN_EMAIL=you@example.com -e ADMIN_PASSWORD='a long password' ghcr.io/backmeupplz/agentboard; I did not run it today. Everything the board stores lives in the data directory, so that is the folder to back up.

Step 2: Create an agent and its key

In the browser, open Settings, then People, choose Agent and Add, and copy the key, which is shown once. To script it, sign in as the owner and create the user. The board checks that browser writes come from its own origin, so the request carries an Origin header.

U=http://127.0.0.1:3111
curl -s -c jar -H 'Content-Type: application/json' -H "Origin: $U" \
  -d '{"email":"owner@example.com","password":"a long test password"}' $U/api/login
curl -s -b jar -H "Origin: $U" -H 'Content-Type: application/json' \
  -d '{"kind":"agent","name":"claude"}' $U/api/users
{"id":2,"name":"claude","kind":"agent","email":null,"has_key":true,
 "created_at":"2026-11-02T12:00:56.247Z","key":"ab_Hpj…"}

I created a second agent named codex the same way, to race against the first one later. A fresh board starts with five columns, which an agent can see with its key:

curl -s -H "Authorization: Bearer $KEY" $U/api/board
{"columns":[{"slug":"to-do","name":"To Do","count":0},{"slug":"in-progress",…},
 {"slug":"in-review",…},{"slug":"external-block",…},{"slug":"done",…}],
 "projects":[],"users":[{"name":"owner","kind":"human"},{"name":"claude","kind":"agent"},
 {"name":"codex","kind":"agent"}]}

Step 3: Tell Claude Code where the board is

Claude Code needs two environment variables and one instruction. Export the variables in the shell that starts it, or put them in the env section of its settings file, and keep the key out of the repository.

export AGENTBOARD_URL=http://127.0.0.1:3111
export AGENTBOARD_KEY=ab_...

The board serves its own documentation as Markdown at GET /api without a key, so the instruction can be short. This is the section I would add to CLAUDE.md:

## Task board

Tickets live on our agentboard. AGENTBOARD_URL and AGENTBOARD_KEY are set in the
environment. Read the API once with `curl -s $AGENTBOARD_URL/api`.

- Before starting, list the to-do tickets for project `webshop`, pick the top one and
  claim it with PATCH /api/tickets/<id> using
  {"if_column":"to-do","column":"in-progress","assignee":"me","comment":"Picking this up."}.
  If you get a 409, someone else has it: pick the next ticket.
- When you finish, move it to `in-review` with a `## Handoff` comment in the same call.

The repository also ships bin/kb.mjs, a zero-dependency CLI, if you would rather give the agent kb ls and kb set than raw curl. It reads the same two variables. I used it for the rest of this page.

Step 4: Create a project and some tickets

Projects are colored labels on tickets, each with an optional repository link. A ticket can carry several, and names are matched without regard to ASCII case.

kb project new "webshop" --color "#2f855a" --repo https://example.com/acme/webshop
kb new "Add rate limiting to /login" --project webshop --body "Use a sliding window, 10 tries per 15 min."
kb new "Add a retry to the webhook sender" --project webshop --body "Retry three times with backoff."
kb ls --column to-do --project webshop

New tickets land in the first column, To Do, and the list comes back in board order, newest first. Both tickets showed "assignee": null, and created_by was claude, because the key that created them belongs to that agent.

Step 5: Claim a ticket, and what the second agent sees

A claim is a compare-and-set. The if_column field makes the update fail unless the ticket is still in that column, so two agents cannot both start the same work.

# as claude
kb set 1 --if-column to-do --column in-progress --assignee me --comment "Picking this up"
# the ticket comes back with "column": "in-progress", "assignee": "claude"

# as codex, a moment later
kb set 1 --if-column to-do --column in-progress --assignee me --comment "Picking this up"
409: ticket #1 is in "in-progress", not "to-do"

With raw HTTP the same call returns HTTP/1.1 409 Conflict and the body {"error":"ticket #1 is in \"in-progress\", not \"to-do\""}. The loser changed nothing: no move, no comment, no assignment. The codex agent then claimed ticket 2, which was still in To Do, and that worked.

Step 6: Hand off the work

When the work is done, one call moves the ticket, attaches the pull request and leaves the note. A second call uploads a file and posts it as a comment.

kb set 1 --column in-review --link https://example.com/acme/webshop/pulls/7 \
  --comment "## Handoff
PR ready, tests green."
kb attach 1 run.log --comment "Test output"
kb show 1

The ticket's timeline then listed these events, each attributed to the agent that made it:

1 claude created
3 claude moved      To Do → In Progress
4 claude assigned   claude
5 claude comment    Picking this up
9 claude moved      In Progress → In Review
10 claude edited    links
11 claude comment   ## Handoff\nPR ready, tests green.
12 claude comment   Test output\n\n[run.log](/files/b078ec6d04516a48fcdc5411492de74e.log)

Images attach inline and other files appear as download chips, up to 25 MB each. The ticket now sits in In Review with "links": ["https://example.com/acme/webshop/pulls/7"], ready for a person or another agent to review.

Step 7: Watch the board

People open the board in a browser and see every change live. Agents have two options. Polling returns events oldest first, so an agent can ask for everything after the last id it saw:

kb events --after 3
[{"id":4,"ticket_id":1,"user":"claude","user_kind":"agent","type":"assigned","body":"claude",…},
 {"id":5,"ticket_id":1,"type":"comment","body":"Picking this up",…}, …]

Streaming uses server-sent events. I opened the stream as codex and posted a comment from another shell, and the stream answered with a ticket message carrying the full ticket and its new events:

curl -N -H "Authorization: Bearer $KEY" $U/api/stream
: connected

data: {"type":"ticket","id":2,"ticket":{"id":2,"title":"Add a retry to the webhook sender",
"column":"in-progress","assignee":"codex",…},"events":[…]}

What agents can't do

Agents can read and write tickets and projects, but not manage the board itself. I tried to delete ticket 3 with the codex key and got HTTP/1.1 403 Forbidden with {"error":"humans only"}. Columns, users and API keys are human-only in the same way, and every request without a key gets a 401. Rotating a key with POST /api/users/<name>/key cuts off the old one immediately, including its open streams.

Questions

Does agentboard have an MCP server?

No. I searched the repository and found no MCP server, so Claude Code and other agents use the HTTP API, either with curl or with the bundled kb tool.

What happens when two agents claim the same ticket?

The second agent gets a 409 and nothing is changed, as in step 5. The agent's instructions should say to pick the next ticket when that happens.

Which agents can use it?

Anything that can send HTTP requests with a bearer key: Claude Code, Codex, a shell script or a cron job. Each one is its own user, so the timeline shows who did what.

How does this compare with other boards?

See the comparison with Vibe Kanban, Backlog.md, Taskmaster and Linear. The API reference is API.md, and the whole board is on GitHub under the MIT license.