Operations 10
0 of 10 operations are conformant: apis.io called them with no credentials and the responses matched this contract; 1 more answered but could not be checked against a schema. Checked 2026-11-12. how this works · status file
0 of 10 operations are conformant: apis.io called them with no credentials and the responses matched this contract; 1 more answered but could not be checked against a schema. Checked 2026-11-12. how this works · status file
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/craaft-io:craaft-io-me-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a 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.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Craaft Me 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: Me
paths:
/me:
get:
summary: Current user
responses:
'200':
description: 'OK. Browser sessions with no signed-in user, including one that
has passed the password step of a two-factor sign-in but not the
code step, get a `null` body.
'
content:
application/json:
schema:
$ref: '#/components/schemas/User'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/RateLimited'
tags:
- Me
operationId: getMe
x-operation-id-source: derived
patch:
summary: Update profile (partial)
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
email:
type: string
format: email
description: 'A new address needs `currentPassword`. It doesn''t change
`email` yet: it sets `pendingEmail` and emails the new
address a link, and opening the link makes the change.
Sending the current address cancels a pending change.
'
currentPassword:
type: string
username:
type: string
emailNotifications:
type: boolean
description: Turn card-activity emails on or off.
responses:
'200':
description: Updated user
content:
application/json:
schema:
$ref: '#/components/schemas/User'
tags:
- Me
operationId: patchMe
x-operation-id-source: derived
/me/notifications:
get:
summary: List the caller's notification inbox
description: 'Returns an envelope, not a bare array: `{"items": [...], "nextBefore":
"" | null}`. See `NotificationList` for why `nextBefore`
(not the last item''s `createdAt`) is the cursor to pass back.'
parameters:
- name: before
in: query
schema:
type: string
format: date-time
description: RFC3339Nano cursor. Returns notifications older than this timestamp - pass back the previous page's `nextBefore`.
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 50
default: 30
description: Raw rows fetched before grouping. Default 30, clamped to 50. The returned `items` array can be shorter once unread rows on the same card are grouped.
- name: unread
in: query
schema:
type: string
enum:
- '1'
description: Set to `1` to return only unread rows.
- name: kind
in: query
schema:
type: string
enum:
- mention
- assigned
- comment
- moved
- updated
- due
description: 'Return only rows of this kind, for example `mention` to list
where the caller was mentioned. The filter applies before
`limit`, so pages stay full.
'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/NotificationList'
'400':
description: '`before` isn''t a valid RFC3339 time, or `kind` isn''t one of the listed kinds'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
tags:
- Me
operationId: getMeNotifications
x-operation-id-source: derived
/me/notifications/count:
get:
summary: Unseen notification count
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required:
- unseen
properties:
unseen:
type: integer
'401':
$ref: '#/components/responses/Unauthorized'
tags:
- Me
operationId: getMeNotificationsCount
x-operation-id-source: derived
/me/notifications/seen:
post:
summary: Mark notifications seen up to a timestamp
description: 'Silences the "unseen" badge without marking anything read - marking
seen and marking read are separate. Takes `upTo` (the newest
`createdAt` displayed), not a list of ids.'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- upTo
properties:
upTo:
type: string
format: date-time
description: RFC3339 timestamp; every unseen notification created at or before this is marked seen.
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required:
- unseen
properties:
unseen:
type: integer
'400':
description: Missing or invalid `upTo`
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
tags:
- Me
operationId: postMeNotificationsSeen
x-operation-id-source: derived
/me/notifications/read:
post:
summary: Mark specific notifications, or a whole card's, read
description: Marking read also marks seen. Send `ids`, `cardId`, or both; at least one is required.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
ids:
type: array
items:
type: string
format: uuid
maxItems: 200
description: Notification ids to mark read. Unknown or foreign ids are ignored.
cardId:
type: string
format: uuid
description: Marks every unread notification on this card read.
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required:
- unseen
properties:
unseen:
type: integer
'400':
description: Neither `ids` nor `cardId` sent, or more than 200 ids
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
$ref: '#/components/responses/Unauthorized'
tags:
- Me
operationId: postMeNotificationsRead
x-operation-id-source: derived
/me/notifications/read-all:
post:
summary: Mark every notification read
description: No request body.
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
required:
- unseen
properties:
unseen:
type: integer
'401':
$ref: '#/components/responses/Unauthorized'
tags:
- Me
operationId: postMeNotificationsReadAll
x-operation-id-source: derived
/me/events:
get:
summary: Server-Sent Events stream of the caller's own notifications
description: 'Opens a long-lived `text/event-stream` carrying `notification.created`
/ `notification.updated` events (each `{"unseen": }`), plus a
`: ping` comment every 30s to keep the connection warm. No board
access check - this is the caller''s own per-user stream, the same one
a board tab also subscribes to alongside its board events (see GET
/projects/{id}/events). Use it on pages with no board open (settings,
projects list, the notification bell) instead of opening a board
subscription just to keep the bell live.'
responses:
'200':
description: An SSE stream (connection stays open).
content:
text/event-stream:
schema:
type: string
'401':
$ref: '#/components/responses/Unauthorized'
tags:
- Me
operationId: getMeEvents
x-operation-id-source: derived
/me/connected-apps:
get:
summary: List OAuth clients (MCP clients) the caller has connected
description: 'Session-only - see the `sessionAuth` scheme description above for
why. Backs Settings → Connected apps in the UI. Newest grant first.'
security:
- sessionAuth: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ConnectedApp'
'403':
$ref: '#/components/responses/SessionOnly'
tags:
- Me
operationId: getMeConnectedApps
x-operation-id-source: derived
/me/connected-apps/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
description: The grant id, from ConnectedApp.id.
delete:
summary: Revoke a connected OAuth client
description: 'Revokes the grant and every access/refresh token issued under it.
The client''s next call with either token gets `invalid_grant` /
401 and must send the user through `/oauth/authorize` again.
Session-only, like GET /me/connected-apps.'
security:
- sessionAuth: []
responses:
'204':
description: Revoked
'403':
$ref: '#/components/responses/SessionOnly'
'404':
$ref: '#/components/responses/NotFound'
tags:
- Me
operationId: deleteMeConnectedAppsById
x-operation-id-source: derived
components:
schemas:
NotificationActor:
type: object
required:
- id
- name
properties:
id:
type: string
format: uuid
name:
type: string
NotificationRow:
type: object
description: 'One inbox line. Unread `comment` / `moved` / `updated` notifications
on the same card are folded into a single row with kind `"group"`;
`mention`, `assigned` and `due` rows, and any already-read row, are never
grouped. `ids`, `kinds`, and `details` are parallel arrays with one
entry per underlying notification folded into the row (length 1
when the row isn''t a group).
'
required:
- ids
- kind
- kinds
- details
- card
- actors
- count
- createdAt
- seen
- read
properties:
ids:
type: array
items:
type: string
format: uuid
description: Underlying notification ids folded into this row.
kind:
type: string
enum:
- mention
- assigned
- comment
- moved
- updated
- due
- group
description: '"group" when this row folds together multiple unread comment/moved/updated notifications on one card; otherwise the single underlying kind.'
kinds:
type: array
items:
type: string
enum:
- mention
- assigned
- comment
- moved
- updated
- due
description: The underlying kind of each id in `ids`, same order.
details:
type: array
items:
type: string
description: 'Per-notification detail, meaning depends on kind: `"card"` or
`"comment"` for mention, `"column"` or `"board"` for moved, an
update-kind string for updated, `"tomorrow"` or `"today"` for due
(a due date reminder, which has no actors), `""` for
assigned/comment.
'
card:
$ref: '#/components/schemas/NotificationCardRef'
commentId:
type: string
format: uuid
description: 'The comment a mention came from (kind `mention`, detail
`"comment"`). Omitted for every other row, for mentions made
before this field existed, and once the comment is deleted.
'
actors:
type: array
items:
$ref: '#/components/schemas/NotificationActor'
description: Distinct actors behind this row's notification(s), in the order first seen.
excerpt:
type: string
description: Comment body or similar context, truncated to 280 characters. Omitted when there's nothing to show.
count:
type: integer
description: Number of underlying notifications folded into this row (1 unless kind is "group").
createdAt:
type: string
format: date-time
description: 'For a group, the newest member''s timestamp, not the oldest.
Not a pagination cursor - see `nextBefore` on GET /me/notifications.
'
seen:
type: boolean
read:
type: boolean
User:
type: object
required:
- id
- email
- name
- username
- avatarUrl
- hasPassword
- emailVerified
- newsletterSubscribed
- newsletterAvailable
- emailNotifications
- isSuperAdmin
- twoFactorEnabled
properties:
id:
type: string
format: uuid
email:
type: string
format: email
name:
type: string
username:
type: string
avatarUrl:
type: string
description: Empty when no upload; otherwise /api/v1/users/{id}/avatar
hasPassword:
type: boolean
description: False for OAuth-only users (no password set). The hash itself is never returned.
emailVerified:
type: boolean
description: 'Whether the address has been confirmed. Currently a soft gate -
every endpoint still works for an unverified user - so treat it
as a prompt to nudge, not as an authorization signal.
'
pendingEmail:
type: string
format: email
description: 'Present while an email change waits for confirmation. `email`
stays the current address until the link sent to this one is
opened.
'
twoFactorEnabled:
type: boolean
description: 'Whether the user has two-factor sign-in on. Managed from
Settings → Security through session-only routes (`/me/2fa*`);
personal access tokens and MCP tokens are not affected by it.
'
newsletterSubscribed:
type: boolean
emailNotifications:
type: boolean
description: 'Whether card-activity emails are sent (mentions, assignments,
and activity on followed cards that stays unread for a few
minutes). Defaults to true. Account email such as password
resets is always sent.
'
newsletterAvailable:
type: boolean
description: 'False when the newsletter feature isn''t configured on the
deployment at all, which is different from being unsubscribed.
'
isSuperAdmin:
type: boolean
description: 'Whether the user can reach the `/admin` dashboard. Informational
only - the real gate is server-side, and the admin endpoints are
session-authenticated, so a token cannot use them.
'
csrfToken:
type: string
description: 'Present only for cookie/session callers, who need it to send
mutations. Omitted for bearer-token callers, since CSRF does not
apply to a credential that isn''t attached automatically.
'
NotificationCardRef:
type: object
required:
- id
- title
- projectId
- projectName
- archived
properties:
id:
type: string
format: uuid
title:
type: string
projectId:
type: string
format: uuid
projectName:
type: string
archived:
type: boolean
ConnectedApp:
type: object
description: 'An MCP client (or other OAuth client) the caller has approved via
the `/oauth/authorize` consent screen. Backed by an `oauth_grants`
row - one per (user, client) pair.
'
required:
- id
- clientName
- redirectHost
- verified
- createdAt
- lastUsedAt
properties:
id:
type: string
format: uuid
description: The grant id. Pass this to DELETE /me/connected-apps/{id} to revoke.
clientName:
type: string
description: Name the client supplied at dynamic registration (RFC 7591).
redirectHost:
type: string
description: 'Host (with port, if non-default) of the client''s registered
redirect URI - shown so the user can recognise which app this
is beyond just its self-reported name.
'
verified:
type: boolean
description: 'True for a small allowlist of known first-party MCP client
hosts (currently claude.ai and claude.com). Any other client
can self-register via dynamic client registration, so this is
a hint, not a security boundary.
'
createdAt:
type: string
format: date-time
description: When the grant was first approved.
lastUsedAt:
type: string
format: date-time
nullable: true
description: Last time this grant's access token was used. Null if never used since approval.
Error:
type: object
required:
- error
properties:
error:
type: string
NotificationList:
type: object
description: The GET /me/notifications response envelope (not a bare array).
required:
- items
- nextBefore
properties:
items:
type: array
items:
$ref: '#/components/schemas/NotificationRow'
nextBefore:
type: string
format: date-time
nullable: true
description: 'Cursor for the next page - pass back as `?before=`. Null when
there''s nothing older to fetch. This is the `createdAt` of the
OLDEST raw notification fetched for this page, before grouping -
grouping can make a page hold fewer than `limit` items, so the
last item''s `createdAt` is NOT a usable cursor (it can be a
group''s newest member, and re-fetching from it would re-show
rows already returned).
'
responses:
Unauthorized:
description: Missing or invalid token
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
SessionOnly:
description: 'Browser session required - a bearer token (`cra_` or `cro_`) was
used against a session-only route and is refused identically to
either scheme.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
RateLimited:
description: Per-token rate limit exceeded
headers:
Retry-After:
schema:
type: integer
description: Seconds until the next allowed request
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.
'