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.

# 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.

  1. 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.

  2. Step 2

    Check it works

    curl -H "Authorization: Bearer $CRAAFT_API_TOKEN" \
         https://craaft.io/api/v1/me
  3. 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 with cra_; tokens an MCP client gets from signing in start with cro_.
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.

Bulk card endpoints
Method Path Does
POST/api/v1/projects/{id}/cards/bulkCreate up to 100
PATCH/api/v1/cards/bulkUpdate up to 100
POST/api/v1/cards/bulk/moveMove 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.

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.

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.

HTTP error status codes and what to do
Status Meaning What to do
400Malformed body or missing fieldFix the payload and retry
401Missing, bad, or revoked tokenMint a fresh one
402A plan limit, with the limit in the bodyUpgrade, or stay inside it
404Missing, or you have no accessStop; do not probe
409Duplicate, or a column that still has cardsAdjust the input
413Upload over the 25 MiB capShrink the file
429Rate limitedSleep 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.