Media
Upload supported files, list attachments, and manage media records programmatically.
Endpoints
List media in your workspace
Scope
media:readParameters
targetstringOne of post | comment | task | taskComment. Defaults to post. Inferred from whichever parent-id filter is supplied.fileTypestringFilter by type: image, audio, document, lottie, model, animationpostIdstringFilter by post ID (implies target=post)commentIdstringFilter by post comment ID (implies target=comment)taskIdstringFilter by task ID (implies target=task)taskCommentIdstringFilter by task comment ID (implies target=taskComment)limitnumberNumber of items (max 100, default 50)cursorstringCursor for pagination (from nextCursor in previous response)Response
{
"items": [
{
"id": "att_abc123",
"target": "post",
"postId": "pst_def456",
"fileName": "screenshot.png",
"originalFileName": "screenshot.png",
"fileType": "image",
"fileSize": 245760,
"mimeType": "image/png",
"url": "/api/media/uploads/2026/04/01/abc123.png",
"thumbnailUrl": null,
"width": 1920,
"height": 1080,
"duration": null,
"createdAt": 1775073600000
}
],
"hasMore": true,
"nextCursor": "eyJjcmVhdGVkQXQiOjE3NzUwNzM2MDAwMDAsImlkIjoiYXR0X2FiYzEyMyJ9"
}Upload R2-backed media to a post, comment, task, or task comment
Scope
media:writeParameters
targetstringOne of post | comment | task | taskComment. If omitted, inferred from whichever parent-id field is set.postIdstringPost to attach to (exactly one of postId, commentId, taskId, or taskCommentId is required)commentIdstringPost comment to attach totaskIdstringTask to attach totaskCommentIdstringTask comment to attach tofileNamestringrequiredFile name (max 255 chars)mimeTypestringrequiredMIME type. Any type is accepted except raster images (use the Cloudflare Images flow) and videos (use Cloudflare Stream). application/json is stored as a Lottie animation when its content validates as one, otherwise as a generic file.fileDatastringrequiredBase64-encoded file contentdurationnumberAudio duration in secondsResponse
{
"id": "att_abc123",
"target": "post",
"postId": "pst_def456",
"fileName": "brief.pdf",
"fileType": "document",
"fileSize": 245760,
"mimeType": "application/pdf",
"url": "/api/media/uploads/2026/04/01/abc123.pdf",
"createdAt": 1775073600000,
"author": {
"id": "usr_ghi789",
"name": "Jane Smith",
"email": "jane@example.com",
"image": "/api/media/avatars/jane.jpg"
},
"post": {
"id": "pst_def456",
"title": "Weekly Update"
}
}Get a single media attachment by ID
Scope
media:readParameters
idstringrequiredMedia attachment ID (URL path)targetstringOne of post | comment | task | taskComment. Defaults to post.Delete a media attachment
Scope
media:writeParameters
idstringrequiredMedia attachment ID (URL path)targetstringOne of post | comment | task | taskComment. Defaults to post.Upload Format
The POST /api/v1/media endpoint accepts a JSON body with the file content as a base64-encoded string in the fileData field. Media attaches to one of four parent item types: a post, a post comment, a task, or a task comment. Supply exactly one of postId, commentId, taskId, or taskCommentId. An optional target field may be included to make the intent explicit; it must match whichever id field is set.
The base64 REST upload path accepts any file type — audio, PDFs, SVG, Rive, self-contained 3D models, valid Lottie JSON, and arbitrary documents, text, code, and data files. Raster image uploads are rejected because images must stay on the Cloudflare Images-backed upload flow; videos must stay on Cloudflare Stream-backed upload flows. Non-media files are stored inert and served as downloads (Content-Disposition: attachment) — they are never rendered in the browser. Base64 API uploads are stored as protected R2 objects and served through the media proxy; they are not Cloudflare Images or Cloudflare Stream assets and this route does not apply application-layer encryption.
Uploads are limited to 50 per hour per API key, in addition to the normal authenticated REST API rate limit.
Per-parent attachment limits follow the product's UI rules: 6 per post (10 on Pro), 10 per post comment, 20 per task, 10 per task comment.
Uploads follow the parent's live edit permissions. For posts and post comments the caller must be the author; for tasks and task comments the caller must have task access (owner, admin, or member). The API key still requires the media:write permission scope regardless of target.
Media reads follow the parent's live visibility. If you request GET /api/v1/media?taskId=... for a task you cannot see, the API returns an empty page instead of exposing the attachment list.
Backward compatibility: calls that omit target and provide only postId continue to work unchanged. GET /api/v1/media/:id and DELETE /api/v1/media/:id default to target=post when the query param is omitted.
Uploadable File Types
imageNot accepted by base64 REST upload; use Cloudflare Images-backed upload flows. SVG is accepted separately as R2-backed media.svgSVG files (max 50 MB via API)audioMP3, M4A, WAV, WebM audio, OGG, Opus, FLAC, AAC, AIFF (max 50 MB via API)documentPDF (max 50 MB via API)lottieLottie JSON animations (max 50 MB via API)animationRive .riv files (max 50 MB via API)modelGLB, OBJ, FBX, STL, PLY, USDZ, DAE, 3DS, 3MF, AMF, VRML (max 50 MB via API). glTF JSON with external references is rejected; use GLB for self-contained models.fileAny other file type — text, code, CSV, JSON data, Office documents, archives, etc. (max 50 MB via API). Stored as a generic file and served as a download.videoNot accepted by base64 REST upload; use Cloudflare Stream-backed upload flows.Media list, get, and delete responses can still include images created by the Cloudflare Images-backed web upload flow and videos created by Cloudflare Stream-backed upload flows. Base64 API uploads only create protected R2-backed attachments.
Size Limits
The maximum file size for API uploads is 50 MB (due to the base64 encoding overhead in the JSON body). Files that exceed the limit will receive a 413 response. The web interface uses Cloudflare-managed or direct-storage flows for larger supported media.
Pagination
The list media endpoint uses cursor-based pagination. Pass the nextCursor value from the previous response as the cursor query parameter to fetch the next page. The response includes a hasMore flag to indicate if more results are available.
The cursor is a JSON string containing the last item's createdAt timestamp and attachment ID, which keeps pagination stable when multiple attachments share the same upload time.
Deletion
Only the post author or a workspace admin/owner can delete media. Deleted files are cleaned up from storage after a 7-day grace period. Media under a legal hold cannot be deleted and will return a 409 response.
Response shape by target
The top-level target field tells you which parent kind the attachment belongs to. Non-post attachments also include a parent block with kind-specific context. Post attachments retain the legacy post: { id, title } shape for backward compatibility.
Task attachment
{
"id": "att_t123",
"target": "task",
"taskId": "tsk_456",
"fileName": "rendering-report.pdf",
"fileType": "document",
"url": "/api/media/uploads/2026/04/01/t123.pdf",
"createdAt": 1775073600000,
"parent": {
"kind": "task",
"id": "tsk_456",
"title": "Investigate rendering issue"
}
}Post-comment attachment
{
"id": "att_c789",
"target": "comment",
"commentId": "com_abc",
"fileName": "voice-memo.mp3",
"fileType": "audio",
"url": "/api/media/uploads/2026/04/01/c789.mp3",
"createdAt": 1775073600000,
"parent": {
"kind": "comment",
"id": "com_abc",
"postId": "pst_def456"
}
}Task-comment attachment
{
"id": "att_tc999",
"target": "taskComment",
"taskCommentId": "tc_xyz",
"fileName": "log.pdf",
"fileType": "document",
"url": "/api/media/uploads/2026/04/01/tc999.pdf",
"createdAt": 1775073600000,
"parent": {
"kind": "taskComment",
"id": "tc_xyz",
"taskId": "tsk_456"
}
}