Skip to content

Direct uploads

For files larger than a few megabytes, uploading through the API adds unnecessary latency. The presigned-URL flow lets your client upload the file directly to R2 storage, then tell CanvasMesh to attach it.

This is a three-step flow: presign → PUT → finalize.

Terminal window
curl -X POST https://api.canvasmesh.app/v1/upload/presign \
-H "Authorization: Bearer $CANVASMESH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"ext": "pdf",
"size": 1048576,
"mime": "application/pdf"
}'

Response:

{
"file_id": "fl_xyz789",
"blob_key": "files/ab/e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"upload_url": "https://<r2-presigned-url>...",
"content_type": "application/pdf",
"expires_in": 3600,
"exists": false
}

If exists: true, the content-addressed hash already exists in storage and you can skip step 2.

Terminal window
curl -X PUT "<upload_url>" \
-H "Content-Type: application/pdf" \
--data-binary @./large-file.pdf

Use the exact Content-Type from the presign response. The URL expires after expires_in seconds.

Tell CanvasMesh the upload completed so it reference-counts the blob:

Terminal window
curl -X POST https://api.canvasmesh.app/v1/upload/finalize \
-H "Authorization: Bearer $CANVASMESH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"ext": "pdf",
"size": 1048576,
"mime": "application/pdf"
}'

Instead of passing raw content, pass file_id:

Terminal window
curl -X POST https://api.canvasmesh.app/v1/canvases \
-H "Authorization: Bearer $CANVASMESH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"file_id": "fl_xyz789",
"title": "Q2 Report",
"ext": "pdf"
}'

Files are stored by SHA-256. Two identical uploads share one blob; deleting one canvas won’t delete content another canvas still references. This lets you upload the same large asset across many canvases without wasting storage.

  • Any file > 5 MB — direct upload is faster and avoids multipart limits on the Worker.
  • Cases where the client already has the hash (e.g. content-addressed caches).

For small files, POST /v1/canvases with multipart is simpler.