Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we
store it to create your key and to recognise you if you sign in with another
provider. See our Privacy Policy and
Terms.
openapi: 3.2.0
info:
title: Craaft Members 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: Members
paths:
/members:
get:
summary: List workspace members
operationId: listMembers
responses:
'200':
description: 'Workspace members. `boardAccess` is attached on rows the caller
is allowed to see (their own row, or every row when the caller
is owner/admin); omitted on owner/admin rows since they have
implicit access to every board.
'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Member'
'401':
$ref: '#/components/responses/Unauthorized'
tags:
- Members
/members/{userId}:
parameters:
- name: userId
in: path
required: true
schema:
type: string
format: uuid
patch:
summary: Change a member's workspace role
operationId: updateMemberRole
description: 'Owner/admin only. Flips a member between `admin` and `member`. The
owner role can''t be set or changed here.'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- role
properties:
role:
type: string
enum:
- admin
- member
responses:
'200':
description: Updated
content:
application/json:
schema:
$ref: '#/components/schemas/Member'
'400':
description: Missing / invalid role
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: Caller isn't a workspace owner/admin
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
$ref: '#/components/responses/NotFound'
tags:
- Members
delete:
summary: Remove a member from the workspace
operationId: removeMember
description: Admin/owner only. Revokes membership and closes the member's open SSE streams.
responses:
'204':
description: Removed
'401':
$ref: '#/components/responses/Unauthorized'
'403':
description: Caller isn't a workspace owner/admin
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
$ref: '#/components/responses/NotFound'
tags:
- Members
components:
schemas:
BoardAccess:
type: object
required:
- projectId
- name
- role
properties:
projectId:
type: string
format: uuid
name:
type: string
maxLength: 200
role:
type: string
enum:
- admin
- contributor
Member:
type: object
required:
- userId
- email
- name
- role
- avatarUrl
- joinedAt
properties:
userId:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
role:
type: string
enum:
- owner
- admin
- member
avatarUrl:
type: string
description: Empty when no upload; otherwise /api/v1/users/{id}/avatar
joinedAt:
type: string
format: date-time
boardAccess:
type: array
description: 'Explicit board grants the caller is allowed to see. Omitted when
the row is a workspace owner/admin (implicit access to every
board) OR when a non-admin caller is reading someone else''s row.
Empty array means "this member has no explicit grants".
'
items:
$ref: '#/components/schemas/BoardAccess'
Error:
type: object
required:
- error
properties:
error:
type: string
responses:
Unauthorized:
description: Missing or invalid token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
NotFound:
description: Resource doesn't exist or caller isn't a member of its workspace
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
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.
'