Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
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.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/cnvs-app:cnvs-app-ai-facing-api"
All apis
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.
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.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Cnvs App AI Facing API
version: 1.0.0
contact:
name: cnvs.app
url: https://cnvs.app/about/
license:
name: Public service — no account required
url: https://cnvs.app/about/
x-accessLock:
modes:
- write
- all
keyFormat: 8 chars [a-z0-9] (6-char legacy keys also accepted)
channel:
rest: X-Board-Key header
mcp: X-Board-Key header OR `access_key` tool argument
ws: Sec-WebSocket-Protocol subprotocol `cnvs-key.<code>` (server echoes it back in the 101 response). `?key=<code>` query parameter is accepted as a deprecated fallback but leaks the key into access logs.
endpoints:
lock: POST /api/boards/{id}/lock
unlock: POST /api/boards/{id}/unlock
verify: POST /api/boards/{id}/verify-key
rejection:
rest:
status: 401
body:
code: board_locked
lockMode: write|all
mcp:
jsonRpcCode: -32001
data:
code: board_locked
lockMode: write|all
ws:
status: 401
note: Upgrade rejected for read-locked boards; write-locked allows the upgrade but server drops mutation frames with type=error code=board_locked.
note: By default boards are open (anyone with the ID can read + write). When locked, mode `write` only requires the key for mutations; `all` requires it for both reads and writes. There is no recovery — losing the key loses the board.
x-boardImportLimits:
maxItems: 2550
maxBatchStatements: 320
maxBoundParamsPerStatement: 100
countedCollections:
- texts
- lines
- images
- columns
- tasks
- lanes
maxLaneTitles: 10
laneTitleRule: Each lane title must reference a lane used by a column created in the same request.
infrastructureToken: X-Import-Token is server-to-server. A valid token selects the infrastructure REST/MCP and board-create rate tiers; it never bypasses hard caps, validation, storage, board access locks, or Cloudflare platform limits.
x-cors:
allowOrigin: '*'
allowMethods:
- GET
- POST
- DELETE
- OPTIONS
allowHeaders:
- Content-Type
- Mcp-Session-Id
- If-None-Match
- X-Board-Key
note: 'Every public endpoint (/api/boards/..., /json/..., /svg-preview/..., /mcp) returns permissive CORS headers and handles OPTIONS preflight. The board ID is the access credential when the board is unlocked; locked boards additionally require the X-Board-Key header. X-Import-Token is intentionally absent: it is a server-to-server credential, so browser clients cannot send it and belong on the standard per-IP rate tier.'
x-fieldLimits:
maxAuthorChars: 80
maxThumbnailBytes: 8000
textWidth:
min: 160
max: 4096
columnWidth:
min: 200
max: 480
default: 280
maxLaneTitleChars: 200
maxColumnTitleChars: 200
maxTaskNameChars: 500
maxAssigneeChars: 200
x-operationLimits:
maxRecolorItems: 500
maxBatchOps: 500
wsContinuation:
maxFrames: 500
idleMs: 500
x-quotasManifest:
url: /quotas.json
note: Machine-readable single source of truth for limits. Cache ≤ 5 min.
x-rateLimits:
standard:
restMcpPerBoard:
requests: 60
windowSeconds: 10
per: boardId
boardCreate:
requests: 5
windowSeconds: 60
per: clientIp
enforcement: Cloudflare Rate Limiting binding; permissive and eventually consistent per edge location
infrastructure:
credential: valid X-Import-Token (server-to-server only)
restMcpPerBoard:
requests: 600
windowSeconds: 10
per: boardId
boardCreate:
requests: 50
windowSeconds: 60
per: infrastructureToken
perNote: Keyed by a one-way digest of the token VALUE (the credential never enters limiter keys or telemetry). There is a single import-token secret, so all trusted callers share this one bucket — it is not a per-integration allowance.
enforcement: Cloudflare Rate Limiting binding; permissive and eventually consistent per edge location
webSocket:
requests: 60
windowSeconds: 10
per: boardId
infrastructureTokenChangesTier: false
security:
lockUnlockVerify:
requests: 5
windowSeconds: 60
per: clientIp
infrastructureTokenChangesTier: false
scope: Browser WebSocket frames are rate-limited inside the per-board BoardServer Durable Object — single-writer and strong. REST and MCP are rate-limited in whichever Worker isolate serves the request; because Cloudflare routes traffic across many isolates (regions, colos, warm/cold starts), a caller distributing load effectively gets N × the advertised cap. Treat REST/MCP as a soft per-isolate ceiling, not a strong global guarantee. X-Import-Token raises only the REST/MCP and board-create tiers; it does not bypass hard caps or board access locks.
response:
rest:
status: 429
headers:
Retry-After: <seconds>
body:
code: rate_limited
retryAfterSeconds: 10
mcp:
jsonRpcCode: -32000
data:
code: rate_limited
retryAfterSeconds: 10
ws:
body:
type: error
code: rate_limited
retryAfterSeconds: 10
note: Strong for browser WS (DO-enforced), soft for REST/MCP (isolate-local; bypassable via distribution). Cloudflare's platform-level limits remain the hard wall. Live values in /quotas.json.
x-requestBodyLimit:
bytes: 5242880
note: Single-request body cap (5 MiB) enforced via Content-Length AND during the streamed body read. Chunked / header-less clients cannot bypass the cap. HTTP 413 (REST) / JSON-RPC -32000 code:payload_too_large (MCP).
x-webSocketErrors:
note: 'Every WebSocket error frame is `{type: "error", code, message, originType, originId}` and is sent ONLY to the socket whose mutation failed — never broadcast. `originType`/`originId` echo the frame that caused it.'
codes:
rate_limited: Rejection — per-board WS budget exceeded (see x-rateLimits). Nothing was written.
board_locked: Rejection — board is write-locked and this socket has no valid key (see x-accessLock). Nothing was written.
quota_exceeded: Rejection — a per-board quota would be exceeded; carries `kind`. Nothing was written.
invalid_payload: Rejection — the frame failed validation; carries `field`. Nothing was written.
server_error: 'NOT a rejection — the mutation threw server-side (e.g. a D1 storage reset mid-write). The throw may occur before, during or after the write commits, so the outcome is unknown: the change may be fully stored, partly stored, or absent. Re-read the board (GET /json/{id}) and reconcile; do not assume local state is correct, and do not blindly undo the change.'
description: 'Operations tagged AI-facing across 2 of this provider''s published API definitions: cnvs-app-openapi.json, cnvs-app-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://cnvs.app
description: Production
tags:
- name: AI-facing
description: Endpoints designed for LLM consumption — schematic SVG previews and clean JSON snapshots.
paths:
/api/boards/{boardId}/wait:
parameters:
- $ref: '#/components/parameters/BoardIdPath'
get:
tags:
- AI-facing
summary: 'Long-poll: block until the next edit'
description: 'REST equivalent of the MCP `wait_for_update` tool. Blocks until the next debounced edit burst lands on this board, or `timeout_ms` elapses. Designed for AI clients without MCP push notifications: call it after your turn is done, refresh with `GET /json/{id}` (using the returned ETag) when it resolves with `updated: true`. Resolves ~3 s after the edit burst settles (same debounce as push notifications).'
operationId: waitForBoardUpdate
parameters:
- name: timeout_ms
in: query
required: false
description: Milliseconds to block before giving up. Clamped to [1000, 55000]; default 25000.
schema:
type: integer
minimum: 1000
maximum: 55000
default: 25000
responses:
'200':
description: 'Either an edit landed (`updated: true`) or the timeout elapsed (`timedOut: true`). Returned ETag matches what `GET /json/{id}` would produce right now — save it and send back as `If-None-Match` on the next read to skip the 200 body when nothing has changed.'
content:
application/json:
schema:
type: object
properties:
boardId:
type: string
updated:
type: boolean
timedOut:
type: boolean
etag:
type: string
required:
- boardId
- updated
- timedOut
- etag
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/BoardLocked'
'429':
$ref: '#/components/responses/RateLimited'
servers:
- url: https://cnvs.app
description: Production
/json/{boardId}:
parameters:
- $ref: '#/components/parameters/BoardIdPath'
get:
tags:
- AI-facing
summary: Board snapshot (MCP get_board shape)
description: 'Full structured JSON snapshot of a board, identical in shape to the MCP `get_board` tool. Includes every text node''s COMPLETE content (Mermaid source stays intact inside the `content` field), every stroke''s point array, and image metadata. Heavy image payloads (>8 kB base64) are elided (`dataUrl: null`); tiny images (small SVGs, icon-sized PNGs) ride along inline so AI clients can render them without a second round-trip.
The `boardId` can be supplied as a trailing path segment OR as `?board=...` query param — the query form accepts a raw UUID, a hash URL like `https://cnvs.app/#`, or any URL whose path tail is the id.
**Caching for pollers**: every response carries a weak `ETag` header derived from the board''s last-updated timestamp + per-kind item counts. Send it back as `If-None-Match` and unchanged boards short-circuit to `304 Not Modified` (empty body, no rate-limit burn). `HEAD` is also supported for cheap existence/freshness probes.'
operationId: getBoardJson
parameters:
- name: If-None-Match
in: header
required: false
description: Weak ETag from a previous response. Unchanged boards respond `304 Not Modified`.
schema:
type: string
responses:
'200':
description: Board snapshot.
headers:
ETag:
description: Weak ETag. Reuse with `If-None-Match` to poll cheaply.
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/BoardSnapshot'
'304':
description: Unchanged since the supplied `If-None-Match`. Empty body.
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/BoardLocked'
'429':
$ref: '#/components/responses/RateLimited'
head:
tags:
- AI-facing
summary: Cheap existence + freshness probe
description: Same headers as `GET /json/{boardId}` but zero body. Returns 200 with ETag if the board exists, 404 otherwise.
operationId: headBoardJson
responses:
'200':
description: Board exists; ETag returned.
headers:
ETag:
schema:
type: string
'404':
$ref: '#/components/responses/NotFound'
servers:
- url: https://cnvs.app
description: Production
/json:
get:
tags:
- AI-facing
summary: Board snapshot (via query param)
description: 'Same as `/json/{boardId}` but with the id in the query string. Convenient when you have a full cnvs URL and don''t want to strip the hash: `GET /json?board=https://cnvs.app/#`.'
operationId: getBoardJsonQuery
parameters:
- name: board
in: query
required: true
description: 'Raw board UUID, hash URL (`https://cnvs.app/#<id>`), or bare id. Alias: `id`.'
schema:
type: string
responses:
'200':
description: Board snapshot.
content:
application/json:
schema:
$ref: '#/components/schemas/BoardSnapshot'
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/BoardLocked'
'429':
$ref: '#/components/responses/RateLimited'
servers:
- url: https://cnvs.app
description: Production
/svg-preview/{boardId}:
parameters:
- $ref: '#/components/parameters/BoardIdPath'
get:
tags:
- AI-facing
summary: Schematic SVG preview
description: Compact schematic render of the board — a few kB of plain SVG text, directly consumable by multimodal LLMs as an image. Shows texts as labeled rectangles, strokes as polylines with exact world coordinates, and images as placeholder boxes (or tiny thumbnails if provided). Mermaid blocks currently render as a `[mermaid diagram]` placeholder — fetch `/json/{id}` for the raw source. AI-authored items are rendered with a purple border so the viewer can tell apart AI and human contributions.
operationId: getSvgPreview
responses:
'200':
description: SVG preview.
content:
image/svg+xml:
schema:
type: string
format: binary
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/BoardLocked'
'429':
$ref: '#/components/responses/RateLimited'
servers:
- url: https://cnvs.app
description: Production
/svg-preview:
get:
tags:
- AI-facing
summary: Schematic SVG preview (via query param)
description: Same as `/svg-preview/{boardId}` but with the id in the query string.
operationId: getSvgPreviewQuery
parameters:
- name: board
in: query
required: true
description: 'Raw board UUID, hash URL, or bare id. Alias: `id`.'
schema:
type: string
responses:
'200':
description: SVG preview.
content:
image/svg+xml:
schema:
type: string
format: binary
'400':
$ref: '#/components/responses/BadRequest'
'404':
$ref: '#/components/responses/NotFound'
'401':
$ref: '#/components/responses/BoardLocked'
'429':
$ref: '#/components/responses/RateLimited'
servers:
- url: https://cnvs.app
description: Production
components:
schemas:
Task:
type: object
description: A kanban task (card) on a `todo`-mode board.
properties:
id:
type: string
column_id:
type: string
description: Column the card currently lives in.
name:
type: string
description: Card title.
content:
type:
- string
- 'null'
description: Opaque JSON string blob, e.g. `{"description":...}`. Treat as a blob — do not parse application meaning out of it. Max 20 000 chars.
due_date:
type:
- string
- 'null'
description: ISO 8601 due date, or null.
priority:
type:
- string
- 'null'
enum:
- H
- M
- L
- null
description: High / Medium / Low, or null.
assignee:
type:
- string
- 'null'
done:
type: boolean
description: Whether the task is completed (checked off). Defaults to false.
sort:
type: number
description: Order within the column.
author:
type:
- string
- 'null'
last_updated:
type: string
description: UTC timestamp.
required:
- id
- column_id
- name
Error:
type: object
properties:
error:
type: string
description: Human-readable error message.
required:
- error
ImageNode:
type: object
description: Image metadata. Full `dataUrl` is NOT included in this shape — fetch `/api/boards/{id}` if you need raw pixels.
properties:
id:
type: string
x:
type: number
y:
type: number
width:
type: number
height:
type: number
thumbDataUrl:
type:
- string
- 'null'
description: Tiny preview embedded in SVG previews (≤8 kB).
author:
type:
- string
- 'null'
last_updated:
type: string
required:
- id
- x
- y
- width
- height
Column:
type: object
description: A kanban column on a `todo`-mode board.
properties:
id:
type: string
title:
type: string
sort:
type: number
description: Left-to-right order of the column.
lane:
type: integer
default: 0
description: Row index for multi-lane layout. Defaults to 0 (top row).
color:
type:
- string
- 'null'
enum:
- red
- blue
- green
- null
description: Column title color — one of `red` / `blue` / `green`, or null for the default.
author:
type:
- string
- 'null'
last_updated:
type: string
description: UTC timestamp.
required:
- id
- title
TextNode:
type: object
description: A draggable, editable text block. Content supports cnvs markup (Markdown-ish) and Mermaid diagrams. When a node contains Mermaid, its entire content is one ```mermaid fenced block (one diagram per node).
properties:
id:
type: string
x:
type: number
description: World x (+x right).
y:
type: number
description: World y (+y DOWN, standard SVG).
content:
type: string
description: Raw text content (up to 100 000 chars). May include Markdown, Mermaid, emoji.
color:
type:
- string
- 'null'
description: Resolved ink value stored on the node — the palette hex for a named color (e.g. `red` → `#ff3b30`) or the CSS variable `var(--text-color)` for theme-aware ink. Writes accept only the color NAMES, never raw hex.
width:
type:
- number
- 'null'
description: Explicit wrapping width in px, or null for auto.
postit:
type: integer
enum:
- 0
- 1
description: 1 renders as a yellow sticky note.
kind:
type: string
enum:
- text
- link
description: '`text` is a free-form markdown/Mermaid node; `link` is a URL capsule rendered as a clickable pill. Both live in the same table — `kind` lets machine clients distinguish them without guessing from content.'
author:
type:
- string
- 'null'
description: 'Author tag: `user:<uuid>` for browser edits, `ai:<label>` for MCP edits. IMMUTABLE after creation — subsequent moves / edits by other collaborators do NOT change this value.'
last_updated:
type: string
description: UTC timestamp.
required:
- id
- x
- y
- content
StrokeLine:
type: object
description: A freehand stroke — an ordered list of world-coordinate points.
properties:
id:
type: string
points:
type: array
description: Array of `[x, y]` pairs in board world coordinates.
items:
type: array
items:
type: number
minItems: 2
maxItems: 2
color:
type:
- string
- 'null'
author:
type:
- string
- 'null'
last_updated:
type: string
required:
- id
- points
BoardSnapshot:
type: object
description: Full board state as returned by `/json/{id}` and the MCP `get_board` tool. Same shape as the `cnvs://board/{id}/state.json` MCP resource.
properties:
boardId:
type: string
mode:
type: string
enum:
- draw
- todo
description: Board mode. `draw` (default) = infinite-canvas whiteboard; `todo` = kanban task board. Switchable whenever the board is empty of real content (drawings + tasks); empty/seeded columns don't count, so a cleared board can switch again.
texts:
type: array
items:
$ref: '#/components/schemas/TextNode'
lines:
type: array
items:
$ref: '#/components/schemas/StrokeLine'
images:
type: array
items:
$ref: '#/components/schemas/ImageNode'
columns:
type: array
description: Kanban columns (present / non-empty on `todo`-mode boards).
items:
$ref: '#/components/schemas/Column'
tasks:
type: array
description: Kanban tasks (present / non-empty on `todo`-mode boards).
items:
$ref: '#/components/schemas/Task'
required:
- boardId
- texts
- lines
- images
responses:
BadRequest:
description: Invalid input (missing or malformed board id).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
RateLimited:
description: 'Rate limit exceeded. Standard REST/MCP: 60 requests/10s/board; valid X-Import-Token infrastructure tier: 600/10s/board; browser WebSocket always 60/10s/board. REST/MCP enforcement is soft per-isolate; WebSocket enforcement is strong per Durable Object.'
headers:
Retry-After:
schema:
type: integer
description: Seconds to wait before retrying.
content:
application/json:
schema:
type: object
properties:
error:
type: string
code:
type: string
enum:
- rate_limited
retryAfterSeconds:
type: integer
required:
- error
- code
BoardLocked:
description: Board is access-locked and the request did not carry a valid key. `lockMode` tells the client whether to prompt for a code or just disable write UI.
headers:
WWW-Authenticate:
schema:
type: string
description: '`Bearer realm="cnvs board", error="board_locked"`.'
content:
application/json:
schema:
type: object
properties:
error:
type: string
code:
type: string
enum:
- board_locked
locked:
type: string
enum:
- read
- write
description: '`read` when lockMode=all, `write` when lockMode=write.'
lockMode:
type: string
enum:
- write
- all
required:
- error
- code
- lockMode
NotFound:
description: Board does not exist or was deleted within the 30-day tombstone window.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
parameters:
BoardIdPath:
name: boardId
in: path
required: true
description: Board identifier. Charset `[A-Za-z0-9-]`, max 64 characters. Usually a UUID but `open_board` accepts any conforming string.
schema:
type: string
pattern: ^[A-Za-z0-9-]{1,64}$
example: 0a01ba29-bc15-4880-b135-ec47e33d95b2
securitySchemes:
BoardKey:
type: apiKey
in: header
name: X-Board-Key
description: 8-char [a-z0-9] access key (6-char legacy keys also accepted). Required for write operations on any locked board, and for ALL operations on a board locked with mode=all. Obtain via POST /api/boards/{id}/lock. Browsers carry the key in localStorage under `cnvs:board_key:<id>`.
externalDocs:
description: cnvs.app / MCP reference
url: https://cnvs.app/llms.txt
x-refined-from:
- cnvs-app-openapi.json
- cnvs-app-openapi.yml
x-mcp:
endpoint: https://cnvs.app/mcp
wellKnown: https://cnvs.app/.well-known/mcp.json
transport: streamable-http
note: This REST API covers reads and mutations one-to-one with MCP. MCP additionally provides live `notifications/resources/updated` subscriptions and the `wait_for_update` long-poll tool.