Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Craaft Imports API
version: 1.0.0
description: 'Personal-access-token API for Craaft. See `docs/api.md` for the
narrative guide, rate limits, and worked examples.'
contact:
name: Craaft
servers:
- url: https://craaft.io/api/v1
description: Production
- url: http://localhost:8080/api/v1
description: Local
security:
- bearerAuth: []
tags:
- name: Imports
paths:
/imports/preview:
post:
summary: Preview a board import
description: 'Parses an uploaded Trello, Jira, Asana, CSV or craaft export and
returns what `POST /imports` would create. Nothing is written. The
preview and the import run the same code, so the counts match.
`multipart/form-data`, file up to 5 MB. Limits per import: 2,000
cards, 200 columns, 500 checklist items on any one card, 20,000
checklist items and 20,000 comments in all, 200 CSV headers.
`source` is detected from the file when omitted. For CSV sources,
`mapping` replaces the server''s suggested mapping: send every field
you want imported. The response then carries a `csv` block with the
headers, the mapping in use and the first sample rows. When no title
column can be guessed and no `mapping` is sent, the preview still
answers `200` with the `csv` block and an empty summary named after
the file, so the client can map the title and preview again.
Matched assignees who aren''t you or a workspace owner or admin are
listed in `access`: the import adds them to the new private board
as contributors.'
requestBody:
required: true
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/ImportRequest'
responses:
'200':
description: What the import would create
content:
application/json:
schema:
$ref: '#/components/schemas/ImportPreview'
'400':
description: 'No file, an unknown `source`, a malformed `mapping`, a file that
isn''t a recognised export of the given source, a CSV with no
`title` in the `mapping` sent, a CSV with more than 200 headers
("This file has too many columns to map. The limit is 200"), or
a file that isn''t UTF-8 ("Save the file as UTF-8 CSV and try
again").
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'413':
description: The file is over 5 MB
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'422':
description: 'Over a limit, worded with the real numbers: more than 2,000
cards ("This file has 2,001 cards. The limit is 2,000"; a CSV
stops counting at the limit, "This file has more than 2,000
cards. The limit is 2,000"), more than 200 columns ("This file
has 238 columns. The limit is 200"), more than 500 checklist
items on a card ("A card has 1,234 checklist items. The limit is
500 per card"), more than 20,000 checklist items or comments in
all ("This file has 25,000 comments. The limit is 20,000"); or
no cards and no columns to import.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
$ref: '#/components/responses/ImportBusy'
tags:
- Imports
operationId: postImportsPreview
x-operation-id-source: derived
/imports:
post:
summary: Import a board
description: 'Creates a new board from an uploaded Trello, Jira, Asana, CSV or
craaft export. It never writes into an existing board. Takes the same
fields as `POST /imports/preview` and runs the same parse. All or
nothing: a failed import leaves no board behind. The board is
private; each matched assignee listed in the preview''s `access` is
added to it as a contributor. A craaft export''s board description
comes across. No notifications
are sent (no emails, mention, assignment or follower notifications,
no Slack or Discord messages). The new board counts against the
workspace''s board cap.
If the board is created but reloading it fails, the `201` still
carries at least `id` and `name`.'
requestBody:
required: true
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/ImportRequest'
responses:
'201':
description: Created. Shaped like the `POST /projects` response.
content:
application/json:
schema:
$ref: '#/components/schemas/Project'
'400':
description: 'Same cases as `POST /imports/preview`, and a CSV with no `title`
mapping, sent or guessed ("Map a column to the card title")
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'402':
description: Plan limit reached (board cap). Same body as `POST /projects`.
content:
application/json:
schema:
type: object
required:
- error
- limit
- currentPlan
- max
- current
properties:
error:
type: string
example: project limit reached
limit:
type: string
example: projects
currentPlan:
type: string
max:
type: integer
current:
type: integer
'413':
description: The file is over 5 MB
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'422':
description: Over a limit (same cases as `POST /imports/preview`), or no cards and no columns to import
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'503':
$ref: '#/components/responses/ImportBusy'
tags:
- Imports
operationId: postImports
x-operation-id-source: derived
components:
responses:
Unauthorized:
description: Missing or invalid token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
ImportBusy:
description: 'Too many imports are running and none finished within 15 seconds
("Too many imports are running. Try again in a moment"). Retry after
the `Retry-After` delay.
'
headers:
Retry-After:
schema:
type: integer
example: 5
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
ImportPreview:
type: object
required:
- source
- name
- columns
- totals
- assignees
- access
- warnings
properties:
source:
type: string
enum:
- trello
- csv
- jira
- asana
- craaft
name:
type: string
maxLength: 200
columns:
type: array
items:
type: object
required:
- title
- isDone
- cards
properties:
title:
type: string
maxLength: 80
isDone:
type: boolean
cards:
type: integer
totals:
type: object
required:
- cards
- archived
- comments
- checklistItems
- tags
properties:
cards:
type: integer
archived:
type: integer
comments:
type: integer
checklistItems:
type: integer
tags:
type: integer
assignees:
type: object
required:
- matched
- unmatched
properties:
matched:
type: array
items:
type: object
required:
- source
- userId
- name
properties:
source:
type: string
userId:
type: string
name:
type: string
unmatched:
type: array
items:
type: string
access:
type: array
description: 'Matched assignees the import adds to the new private board as
contributors: everyone matched except you and the workspace
owners and admins, who already see every board.
'
items:
type: object
required:
- userId
- name
properties:
userId:
type: string
name:
type: string
warnings:
type: array
items:
type: object
required:
- code
- message
properties:
code:
type: string
example: closed_lists_skipped
message:
type: string
csv:
type: object
description: Present only for CSV sources (generic, Jira and Asana)
properties:
headers:
type: array
items:
type: string
mapping:
type: object
description: The mapping in use, as sent or as suggested
additionalProperties:
type: string
sample:
type: array
description: The first 3 data rows
items:
type: array
items:
type: string
Project:
type: object
required:
- id
- workspaceId
- name
- createdAt
- updatedAt
properties:
id:
type: string
format: uuid
workspaceId:
type: string
format: uuid
name:
type: string
maxLength: 200
description:
type: string
brief:
type: string
maxLength: 20000
description: 'The board brief (Markdown): goals, constraints, voice, decisions and links for anyone working on the board. On GET and PATCH /projects/{id} only; the list leaves it out. Every card''s prompt ends with it.'
isFavorite:
type: boolean
publicToken:
type: string
description: Non-empty when public sharing is on
backgroundImage:
type: string
backgroundColor:
type: string
description: Board background hex color (#rrggbb), empty when unset; mutually exclusive with backgroundImage
colorScheme:
type: string
textColor:
type: string
enum:
- dark
- light
showMilestones:
type: boolean
description: Whether the board's top bar shows the milestones button. Off on new boards (since 28 October 2026); boards made before then keep it on unless changed.
showArchived:
type: boolean
description: Whether the board's top bar shows the archived cards button. Off on new boards (since 28 October 2026); boards made before then keep it on unless changed.
showFiles:
type: boolean
description: Whether the board's top bar shows the files button. Off on new boards (since 28 October 2026); boards made before then keep it on unless changed.
showStandup:
type: boolean
description: Whether the board's top bar shows the standup button. Off on new boards (since 28 October 2026); boards made before then keep it on unless changed.
showActivity:
type: boolean
description: Whether the board's top bar shows the activity button. Off by default. The /projects/{id}/activity feed answers either way.
myRole:
type: string
enum:
- owner
- admin
- member
description: Caller's role in the project's *workspace* (distinct from myBoardRole).
visibility:
type: string
enum:
- private
- workspace
description: '`private` (default): only users with an explicit grant (plus
workspace owners/admins) see the board. `workspace`: every
workspace member is an implicit contributor.
'
myBoardRole:
type: string
enum:
- admin
- contributor
description: 'Caller''s effective role on the board. Workspace owners and admins
are always `admin`; explicit grants resolve to `admin` or
`contributor`; on a `workspace`-visibility board, plain workspace
members default to `contributor`.
'
canUploadAttachments:
type: boolean
description: 'Whether cards on this board accept file attachments. Reflects the
board''s *workspace* plan (Pro), NOT the caller''s own plan - so a
Free user collaborating on a Pro workspace''s board sees `true`.
Matches the server''s upload gate; the SPA uses it to show/hide the
attachment dropzone.
'
totalCards:
type: integer
columnCounts:
type: object
additionalProperties:
type: integer
workspaceName:
type: string
description: Workspace display name; populated on list responses.
members:
type: array
description: Capped avatar-stack preview on list responses only.
items:
$ref: '#/components/schemas/ProjectMemberPreview'
columns:
type: array
items:
$ref: '#/components/schemas/Column'
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
Column:
type: object
required:
- id
- key
- title
- position
properties:
id:
type: string
format: uuid
key:
type: string
description: Stable identifier for cards.column
title:
type: string
maxLength: 80
color:
type: string
description: Optional palette token; empty for default
position:
type: number
isDone:
type: boolean
cardLimit:
type: integer
nullable: true
Error:
type: object
required:
- error
properties:
error:
type: string
ProjectMemberPreview:
type: object
required:
- id
- name
- avatarUrl
properties:
id:
type: string
format: uuid
name:
type: string
avatarUrl:
type: string
description: Empty when no upload; otherwise /api/v1/users/{id}/avatar
ImportRequest:
type: object
required:
- file
properties:
file:
type: string
format: binary
description: The export file, up to 5 MB
source:
type: string
enum:
- trello
- csv
- jira
- asana
- craaft
description: Detected from the file when omitted
mapping:
type: string
description: 'CSV sources only. A JSON object mapping craaft fields to CSV
header names, for example `{"title":"Summary","column":"Status"}`.
Fields: `title` (required), `description`, `column`, `dueDate`,
`priority`, `tags`, `assignee`, `archived`. It replaces the
server''s suggested mapping, so send every field to import.
'
name:
type: string
description: Board name. Defaults to the name in the file.
securitySchemes:
sessionAuth:
type: apiKey
in: cookie
name: craaft_session
description: 'Browser session cookie (`__Host-craaft_session` in production,
`craaft_session` in dev), set at sign-in. Not obtainable through
this API - it''s here only so the handful of session-only routes
below (the OAuth consent screen''s own endpoints, connected-apps
management) can be marked accurately. They reject both bearer
schemes with a 403.
'
bearerAuth:
type: http
scheme: bearer
bearerFormat: cra_<24 base64url chars>
description: 'Two token schemes, same permissions. `cra_<24 base64url chars>` is a
personal access token minted in Settings, doesn''t expire on its own,
and is revoked from the same page. `cro_<32 base64url chars>` is an
OAuth access token issued to an OAuth client (an MCP client such as
Claude) via the authorization-code-with-PKCE flow; it expires after
1 hour and the client refreshes it. Either token authenticates as
the user who authorized it, with that user''s exact permissions.
A token can be read-only: a personal access token created with
access `read`, or an OAuth grant with scope `craaft:read`. A
read-only token can send GET, HEAD and OPTIONS; any other method
gets `403` with "this token is read-only; this request needs a
token with read and write access", before the request is handled.
A token can also be limited to chosen boards. Other boards then
behave as if they don''t exist (`404`, or left out of lists), and
`POST /projects`, `/imports`, `/members` and `/invitations` answer
`403` with "this token is limited to some boards; this request
needs a token for all boards".
OAuth clients discover every endpoint they need - authorization,
token, dynamic client registration, revocation - at
`/.well-known/oauth-authorization-server` (RFC 8414); they don''t
need to be told the URLs out of band.
'