cnvs.app Reads API
The Reads API from cnvs.app — 2 operation(s) for reads.
The Reads API from cnvs.app — 2 operation(s) for reads.
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/cnvs-app:cnvs-app-reads-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: Cnvs App Reads 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 Reads 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: Reads
paths:
/api/boards/{boardId}/tasks.csv:
parameters:
- $ref: '#/components/parameters/BoardIdPath'
- name: download
in: query
required: false
description: 'Set to `1` to receive `Content-Disposition: attachment` instead of the default `inline`. Only the literal `1` opts in — the default is deliberately unchanged so existing readers (curl, API explorers, agents) keep displaying the document rather than downloading it.'
schema:
type: string
enum:
- '1'
get:
tags:
- Reads
summary: Export tasks as CSV
description: 'Export a `todo`-mode board''s columns + tasks as CSV — one row per task, RFC-4180 quoted. Header: `lane,column,name,priority,assignee,due_date,done,description`. `lane` is the title of the swimlane (row) the task''s column sits in, falling back to the lane''s 1-based row number (`1`, `2`, …) when that lane has no title. Mirrors the MCP `export_tasks` tool with `format: "csv"`. A `draw` board (no columns) still returns 200 with just the header row, so callers don''t have to branch on mode first.'
operationId: exportTasksCsv
responses:
'200':
description: CSV document.
content:
text/csv:
schema:
type: string
example: 'lane,column,name,priority,assignee,due_date,done,description
Backend,To do,Ship docs,H,alex,2026-06-30,false,Update llms.txt'
'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
/api/boards/{boardId}/tasks.md:
parameters:
- $ref: '#/components/parameters/BoardIdPath'
- name: download
in: query
required: false
description: 'Set to `1` to receive `Content-Disposition: attachment` instead of the default `inline`. Only the literal `1` opts in — the default is deliberately unchanged so existing readers (curl, API explorers, agents) keep displaying the document rather than downloading it.'
schema:
type: string
enum:
- '1'
get:
tags:
- Reads
summary: Export tasks as Markdown
description: 'Export a `todo`-mode board''s columns + tasks as a Markdown checklist grouped by column. Done cards render as `[x]`, open as `[ ]`; priority / assignee / due_date ride inline as trailing tags. Mirrors the MCP `export_tasks` tool (default `format: "markdown"`). A `draw` board (no columns) still returns 200 with just the board heading.'
operationId: exportTasksMarkdown
responses:
'200':
description: Markdown document.
content:
text/markdown:
schema:
type: string
example: '# Task board <id>
## To do
- [ ] Ship docs _(priority: H, @alex, due 2026-06-30)_
'
'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:
Error:
type: object
properties:
error:
type: string
description: Human-readable error message.
required:
- error
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.