craaft

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.

The authoritative surface is /openapi.yaml, served from the running binary with no authentication. This page is the orientation; the spec is the reference.

Client libraries

Three first-party SDKs, each maintained against the same spec. Anything a client does not wrap yet is still one HTTP call away.

  • Python

    pip install craaft
  • TypeScript

    npm i craaft
  • PHP

    composer require craaft/craaft

    Needs PHP 8.2 and ext-curl

Getting started

Three calls from a blank terminal to a card on the board.

  1. Mint a token

    Settings, then API keys. Tokens look like cra_ followed by 24 characters. They are shown once and stored only as a hash - if you lose one, revoke it and make another.

  2. Check it works

    curl -H "Authorization: Bearer $CRAAFT_API_TOKEN" \
         https://craaft.io/api/v1/me
  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

Everything you need to put on the wire, 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; the cra_ prefix is not optional.
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 endpoints

Three endpoints batch card work: create, update, and move. Each takes up to 100 items and runs as one transaction, so a bad item rolls the whole batch back rather than leaving a half-imported board. The error names the offending index.

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.

Things worth knowing

The four that most often cost people an afternoon.

position is 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, send 0.5.
column is the key, not the id
Card payloads take the column's stable key, which you read from columns[].key on the project. The column's UUID is for column endpoints.
Two different moves
Reordering inside a board is PATCH /cards/{id} with column and position. Moving to a different board is POST /cards/{id}/move with a targetProjectId.
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.

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 until the next token is free. Honour it rather than backing off blindly.

HTTP error status codes and what to do
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 human reading a log.

Prefer an agent over curl?

The MCP server exposes the same API as tools - Claude, your editor, or any MCP client, with your own token.