REST API reference
Base URL: https://api.canvasmesh.app
All /v1/* routes require an Authorization: Bearer <api-key> header. Share routes under /share/:token/* are public.
Content negotiation: every endpoint accepts both multipart/form-data (for file uploads) and application/json (for metadata-only changes or presigned uploads).
Canvases
Section titled “Canvases”POST /v1/canvases
Section titled “POST /v1/canvases”Create a canvas from a previously uploaded file. Upload the file first via the presigned flow, then reference its file_id here.
JSON body:
file_id(string, required) — from a successful/v1/upload/finalizecalltitle(string, required)ext(string, required) — extension without dot, e.g."md","pdf"renderer_id(string, optional) — override the default renderer (see renderer types)permanent(boolean, defaultfalse) — skip the 24-hour auto-expiry
Response (201 Created):
{ "canvas_id": "cv_abc123", "owner_user_id": "u_xyz", "title": "Q2 Report", "ext": "md", "renderer_id": "rdr_builtin_markdown", "visibility": "private", "version": 1, "created_at": "...", "expires_at": "..."}To get the view URL, build it client-side as https://canvasmesh.app/c/<canvas_id> or use the SDK which returns it directly.
GET /v1/canvases
Section titled “GET /v1/canvases”List canvases.
Query: visibility, limit (max 100, default 20), offset.
Response:
{ "canvases": [...], "limit": 20, "offset": 0 }GET /v1/canvases/:id
Section titled “GET /v1/canvases/:id”Get canvas metadata, including the current main item’s file and renderer.
PUT /v1/canvases/:id
Section titled “PUT /v1/canvases/:id”Update a canvas. To replace file content, first upload via the presigned flow and pass the new file_id. Metadata-only updates don’t need a new file.
JSON body (all fields optional):
file_id(string) — new file to attach (creates a new version)title(string)visibility(private|public)renderer_id(string)
Response: updated canvas object.
DELETE /v1/canvases/:id
Section titled “DELETE /v1/canvases/:id”Delete the canvas and all its versions. Returns { "success": true }.
POST /v1/canvases/:id/save
Section titled “POST /v1/canvases/:id/save”Convert a temporary canvas to permanent. Returns the updated canvas with expires_at: null.
GET /v1/canvases/:id/content
Section titled “GET /v1/canvases/:id/content”Download the latest content. Responds 302 with a signed R2 URL in Location. Query ?dl=1 forces Content-Disposition: attachment.
GET /v1/canvases/:id/versions
Section titled “GET /v1/canvases/:id/versions”List all versions.
Response:
{ "versions": [{ "version": 3, "size": 4211, "created_at": "..." }] }GET /v1/canvases/:id/versions/:ver
Section titled “GET /v1/canvases/:id/versions/:ver”Download a specific version (302 → signed URL).
Sharing
Section titled “Sharing”POST /v1/canvases/:id/share
Section titled “POST /v1/canvases/:id/share”Create a public share link.
JSON body:
expires_in(string, optional) — e.g."7d","24h","30m"
Response (201):
{ "token": "sh_opaque_token", "expires_at": "...", "view_count": 0, "created_at": "..."}Public share URL: https://canvasmesh.app/s/<token>.
DELETE /v1/canvases/:id/share
Section titled “DELETE /v1/canvases/:id/share”Revoke all share links for this canvas.
GET /share/:token/content
Section titled “GET /share/:token/content”Public endpoint — no auth. Returns 302 with a signed URL. Query ?dl=1 forces attachment. Optional header X-Share-Password.
Presigned uploads
Section titled “Presigned uploads”POST /v1/upload/presign
Section titled “POST /v1/upload/presign”Request a presigned R2 URL for direct upload.
JSON body:
{ "sha256": "<hex>", "ext": "pdf", "size": 1048576, "mime": "application/pdf"}Response:
{ "file_id": "fl_xyz789", "blob_key": "files/ab/...", "upload_url": "<r2-presigned-url>", "content_type": "application/pdf", "expires_in": 3600, "exists": false}If exists: true, skip the PUT and go straight to finalize.
POST /v1/upload/finalize
Section titled “POST /v1/upload/finalize”Confirm a presigned upload completed.
JSON body: same shape as presign request.
Response:
{ "file": { "file_id": "fl_xyz789", "sha256": "...", "size": 1048576 }, "reused": false }Health
Section titled “Health”GET /health
Section titled “GET /health”Unauthenticated health check. Returns { "ok": true }.
Errors
Section titled “Errors”All errors return a JSON body:
{ "error": { "code": "canvas_not_found", "message": "..." } }| HTTP | Meaning |
|---|---|
| 400 | Invalid request |
| 401 | Missing or invalid API key |
| 403 | Key lacks permission for the resource |
| 404 | Resource not found |
| 409 | Conflict (e.g. duplicate share link) |
| 413 | Payload too large — use presigned upload |
| 429 | Rate limit exceeded |
| 500 | Server error |