# kban

> Real-time shared kanban boards. Each project lives at `https://kban.dev/p/<name>/<id>`, and anyone with that URL
> can view and edit it — there are no accounts. Projects have ordered columns (statuses), cards, and a flat list
> of Markdown documents. Edits made through the API appear live in everyone's browser.

**If you were given a project URL** like `https://kban.dev/p/<name>/<id>`, fetch
`https://kban.dev/api/p/<id>/llms.txt` instead of reading this file further. It contains that project's current
board, the ids you need, and ready-to-run commands.

### Project ids

- The **id** is the last segment of the URL: 22 letters and digits, e.g. `1pv7XjjQP9gM4BRBqypgrb`. It is
  **case-sensitive** — copy it exactly.
- It is a compact (base62) encoding of a UUID, and every endpoint also accepts the UUID form
  (`3c5f5d83-d805-4e41-bba5-7a69e0ea588b`). Both refer to the same project.
- The `<name>` segment is a readable slug of the title that changes when the project is renamed. It is
  ignored, so never use it to look a project up. `https://kban.dev/p/<id>` also works.

## Quickest path: the `kban` CLI

If you can run shell commands, use the CLI instead of hand-writing HTTP requests. It needs Node 18+ or Bun.

```sh
curl -fsSL https://kban.dev/install | sh
kban new "Project title"     # or: kban init <project-url>
kban board                     # columns and cards: #12 title [priority, size] @assignee
kban next                      # the highest-priority unclaimed card in "Todo"
kban start 12                  # claim it and move it to In progress
kban log 12 "Found the cause: …"
kban done 12 "Fixed in a1b2c3d"
```

`kban init` writes a `.kban` file in the current directory so later commands know the project. Set
`KBAN_AGENT` (or pass `--as NAME`) so your claims and notes carry your name. Run `kban help` for the rest:
adding cards, updating fields, documents (`kban doc write "Plan" plan.md`) and `--json` output.

## Concepts

- **Project**: title, description, ordered columns, cards, documents.
- **Column**: `id`, `name`, optional `icon` (a status icon someone picked). A card's status is simply the column it's in.
- **Card**: `id`, `title`, `description` (Markdown), `priority` (`none | low | medium | high`),
  `size` (`none | xs | s | m | l | xl`, `none` = not estimated yet), `createdAt`, `updatedAt` (ms since epoch).
- **Document**: `id`, `title`, `body` (Markdown).

## Creating a project

Pick a new random UUID (v4) and send an `init` op. Column names can be plain strings. The response
includes the project's `url`, which uses the short id.

```sh
ID=$(uuidgen | tr A-Z a-z)
curl -s -X POST https://kban.dev/api/p/$ID/ops -H 'Content-Type: application/json' \
  -d '{"ops":[{"type":"init","title":"My project","description":"","columns":["Backlog","Todo","In progress","Done"]}]}'
```

Share that `url` with the human so they can watch the board.

## API

The project URL is the credential. A project can also be **protected with keys**: then every request needs
one, in an `X-Kban-Key: <key>` header (or `Authorization: Bearer <key>`), and a request without a valid key
gets status 401 with `"code": "key_required"` or `"key_invalid"`. A protected project can allow
read-only access without a key: then reads work, and changes get 403 with `"code": "read_only"`. Keys are 16 letters and digits; ask the
human for one. Send JSON with `Content-Type: application/json`.

| Method | Path | What it does |
| --- | --- | --- |
| GET | `https://kban.dev/api/p/<id>` | Full project as JSON: columns in order, each with its cards in order, plus documents. |
| GET | `https://kban.dev/api/p/<id>/llms.txt` | This project's instructions and a Markdown snapshot of the board. |
| GET | `https://kban.dev/api/p/<id>/activity?limit=50&before=<seq>` | Recent changes, newest first: `{"entries": [...], "more": bool}`. Each entry has `seq`, `at` (ms), `user` (`"user"` for edits made in the browser, `"agent"` for edits made through this API) and `kind` (`card.created`, `card.moved`, `card.edited`, `card.claimed`, `card.noted`, `card.deleted`, `column.*`, `document.*`, `project.*`). Pass the last `seq` as `before` for older ones. |
| GET | `https://kban.dev/api/p/<id>/documents/<documentId>` | One document as raw Markdown, with its title and timestamps as front matter. |
| POST | `https://kban.dev/api/p/<id>/ops` | Apply edits. Body: `{"ops": [op, ...]}` (max 100). Applied in order and shown live to anyone viewing the board. |
| POST | `https://kban.dev/api/p/<id>/import` | Replace the **entire** project with an export file. Destructive — only if a human asks. |
| GET | `https://kban.dev/api/p/<id>/keys` | The project's keys: `{"keys": [{"id", "hint", "createdAt", "current"}]}`. `hint` is the key's first 4 characters; `current` marks the key you sent. |
| POST | `https://kban.dev/api/p/<id>/keys` | Create a key. Returns `{"key", "id", "hint", "createdAt"}`; the key is never shown again. The first key protects the project, so keep it. Only if a human asks. |
| DELETE | `https://kban.dev/api/p/<id>/keys/<id>` | Revoke a key. You can't revoke the key you're using. Only if a human asks. |
| GET | `https://kban.dev/api/p/<id>/access` | `{"protected": bool, "publicAccess": "none" | "read"}`: what people without a key may do on a protected project. |
| PUT | `https://kban.dev/api/p/<id>/access` | Body `{"publicAccess": "none" | "read"}`. `read` lets anyone with the link look but not edit. Needs a key. Only if a human asks. |

`POST /ops` validates every op before applying any. On success it returns
`{"ok": true, "version": n, "url": "<project page>", "results": [{"type", "id", "number", "changed"}]}`, where `id` is the id of
the thing the op touched (including ids generated for new cards, columns and documents) and `number` is the
card's #number. Errors return `{"ok": false, "error": "..."}` with status 400 (bad op), 404 (project not
created yet) or 409 (card already claimed by someone else).

### Ops

Fields marked optional may be left out.

**Cards** — every card has a short `number` (shown as #12). `priority`: `none | low | medium | high`.
`size`: `none | xs | s | m | l | xl` (relative effort; `none` = not estimated yet). `assignee`: who is working on it
(`""` = nobody). Descriptions are Markdown.

```json
{"type": "addCard", "column": "Backlog", "card": {"title": "Write tests", "description": "", "priority": "medium", "size": "s"}}
{"type": "claimCard", "id": 12, "assignee": "claude"}
{"type": "appendToCard", "id": 12, "text": "**Progress:** tests written, 2 failing"}
{"type": "moveCard", "id": 12, "column": "In progress", "index": 0}
{"type": "updateCard", "id": 12, "patch": {"title": "...", "description": "...", "priority": "high", "size": "m", "assignee": ""}}
{"type": "deleteCard", "id": 12}
```

- `id` for card ops can be the card's number (`12` or `"#12"`) or its string id.
- `column` matches a column name (case-insensitive); you can pass `columnId` instead.
- `claimCard` fails with 409 if someone else already holds the card, so two agents can't take the same work.
  Add `"force": true` only if a human asked you to take it over.
- `appendToCard` adds a paragraph to the end of the description without touching the rest. Prefer it to
  `updateCard` for progress notes; it can't overwrite someone else's edits.
- `addCard`: `index` optional (default: bottom), `card.id` optional (generated), other card fields optional.
- `moveCard`: `index` is the card's final position in the target column (0 = top); omit it to move to the bottom.
- `updateCard`: `patch` may contain any subset of fields. Text fields are **replaced** — read the current
  value first, or use `appendToCard`.

**Columns** (a card's status is the column it is in)

```json
{"type": "addColumn", "name": "Review", "index": 2}
{"type": "renameColumn", "id": "COLUMN_ID", "name": "QA"}
{"type": "moveColumn", "id": "COLUMN_ID", "index": 0}
{"type": "deleteColumn", "id": "COLUMN_ID"}
{"type": "setColumnIcon", "id": "COLUMN_ID", "icon": "in-review"}
```

`deleteColumn` also deletes every card in that column. `setColumnIcon` picks the column's status icon:
`backlog | todo | in-progress | in-review | done | cancelled`, or `null` to let the app choose from the name.

**Documents** — a flat list of Markdown pages for plans, specs, notes and decisions.

```json
{"type": "addDocument", "document": {"title": "Release plan", "body": "# Release plan\n..."}}
{"type": "updateDocument", "id": "DOCUMENT_ID", "patch": {"title": "...", "body": "..."}}
{"type": "appendToDocument", "id": "DOCUMENT_ID", "text": "## 2026-10-15
- decided X"}
{"type": "deleteDocument", "id": "DOCUMENT_ID"}
```

**Project**

```json
{"type": "updateProject", "title": "...", "description": "...", "color": "#386de6"}
```

`color` is the project's hex colour, used for its logo and favicon. New projects get a random one.

Limits: titles 200 chars, card descriptions 20,000 chars,
document bodies 100,000 chars, 50 columns, 5,000 cards,
500 documents. Longer text is truncated.

## Working agreement for agents

Use the board as the shared source of truth for your work. Humans may be watching and editing at the same time.

1. **Read before you write.** Fetch the board at the start of a session and before changing a card, since
   people edit concurrently.
2. **Claim work visibly.** Before you start a card, claim it with your name and move it to the top of the
   in-progress column (e.g. "In progress"); `kban start 12` does both. If it's claimed by someone else, pick
   another card. Work on one card at a time unless asked otherwise.
3. **Log progress on the card** by appending (`kban log 12 "…"` or `appendToCard`), never by rewriting the
   description. Keep notes short and factual: what changed, links, what's next.
4. **Finish cleanly.** When a card is done, add a one-line summary of the outcome and move it to the done
   column (e.g. "Done").
5. **Say when you're blocked.** Leave the card where it is, add a `**Blocked:**` line explaining what you
   need, and raise its priority if it's urgent.
6. **Capture new work.** If you discover follow-up tasks, add cards to the first (backlog) column with a
   priority and size instead of silently expanding scope.
7. **Use documents for longer material** — plans, specs, research, decision logs — and mention the document
   title in related cards.
8. **Be conservative with destructive ops.** Don't delete or rename columns, or delete cards and documents you
   didn't create, unless a human asked. Never call `/import` unless asked.
9. **Batch related edits** into one `POST /ops` request.
