API
Everything the board does, over HTTP
A JSON REST API for projects, columns, cards, comments, checklists, milestones, members and search. Mint a bearer token in Settings. Available on every plan, including Free. Plan limits apply here exactly as they do on the board.
# Python
pip install craaft
# TypeScript
npm i craaft
# PHP 8.2 or later, with ext-curl
composer require craaft/craaft
# Or skip the SDK: every endpoint is one curl away.
SDKs
Three SDKs, one spec
Each first-party SDK is maintained against the same OpenAPI document. Anything a client does not wrap yet is still one HTTP call away.
- Python on PyPI as
craaft. - TypeScript on npm as
craaft, typed from the spec. - PHP on Packagist as
craaft/craaft.
Quick start
From a blank terminal to a card on the board
Three steps, two of them curl.
-
Step 1
Mint a token
In craaft, open Settings, then API keys. Tokens are
cra_followed by 32 characters, shown once and stored only as a hash, so if you lose one, revoke it and make another. -
Step 2
Check it works
curl -H "Authorization: Bearer $CRAAFT_API_TOKEN" \ https://craaft.io/api/v1/me -
Step 3
Create a card
curl -X POST \ -H "Authorization: Bearer $CRAAFT_API_TOKEN" \ -H "Content-Type: application/json" \ -d '{"title":"Ship it","column":"todo","position":1}' \ https://craaft.io/api/v1/projects/$PROJECT_ID/cards
Authentication
What goes on the wire
Everything a request needs, and what a token can never reach.
- Base URL
https://craaft.io/api/v1. The version prefix is mandatory, and unversioned paths return 404.- Header
Authorization: Bearer cra_...on every request. The scheme is case-insensitive. Personal tokens start withcra_; tokens an MCP client gets from signing in start withcro_.- Tokens act as you
- A token carries exactly the permissions of the person who made it, on the same boards. There is no separate scope system, and nothing a token can reach that you could not reach yourself.
- No CSRF header
- Bearer-authenticated requests skip CSRF entirely. That protection exists for cookie-authenticated browser sessions, which is a different path.
- Not reachable by token
- Sign-in, API key management, billing and avatar upload stay session-only. A key that could mint another key is a lateral-movement problem, so it cannot.
Bulk
100 cards in one call
Each bulk call takes up to 100 items and runs as one transaction, so a bad item rolls the whole batch back instead of leaving a half-imported board. The error names the offending index.
| Method | Path | Does |
|---|---|---|
POST | /api/v1/projects/{id}/cards/bulk | Create up to 100 |
PATCH | /api/v1/cards/bulk | Update up to 100 |
POST | /api/v1/cards/bulk/move | Move up to 100 |
One bulk call spends one rate-limit token. Looping 100 single creates spends 100, and leaves a partial board if it fails halfway.
Gotchas
Four things that catch people out
Each of these has cost someone an afternoon.
positionis a float, not an index- Reordering is a midpoint calculation. To drop a card between siblings at 2 and 3, send
2.5. To put one at the top of a column whose first card is at 1, send0.5. columnis the key, not the id- Card payloads take the column's stable key, which you read from
columns[].keyon the project. The column's UUID is for column endpoints. - Two different moves
- Reordering inside a board is
PATCH /cards/{id}withcolumnandposition. Moving to a different board isPOST /cards/{id}/movewith atargetProjectId. - 404 means “no” without saying why
- A missing resource and one you cannot access return the same 404, deliberately, so the API cannot be used to discover what exists. Do not treat it as “not found” and retry.
Limits
Rate limits and errors
Each token gets a bucket of 60 requests, refilling at one per second. Over it you get a 429 with a Retry-After header giving the exact seconds to wait. Honour it instead of backing off blindly.
| Status | Meaning | What to do |
|---|---|---|
400 | Malformed body or missing field | Fix the payload and retry |
401 | Missing, bad, or revoked token | Mint a fresh one |
402 | A plan limit, with the limit in the body | Upgrade, or stay inside it |
404 | Missing, or you have no access | Stop; do not probe |
409 | Duplicate, or a column that still has cards | Adjust the input |
413 | Upload over the 25 MiB cap | Shrink the file |
429 | Rate limited | Sleep for Retry-After |
Every error body is {"error": "..."}, with a message meant for a person reading a log.
Prefer an agent over curl?
The MCP server exposes the same API as tools for Claude, your editor or any MCP client, connected with a sign-in or a token.