Media endpoints
Create upload slots, stream files, list assets, and clean up.
Media items are created first, then uploaded, and finally attached to posts.
Files are capped at 10 MB and must be one of image/png, image/jpeg,
video/mp4, or video/quicktime.
Endpoints
- GET
/api/v1/media— List media for the workspace. - GET
/api/v1/media/{id}— Fetch a single media item. - DELETE
/api/v1/media/{id}— Delete and detach the asset. - POST
/api/v1/media/create-upload-url— Reserve an ID and get a signed upload URL. - PUT
/api/v1/media/upload/{id}— Upload the binary to the reserved ID.
GET /api/v1/media
Query: offset (number, default 0), limit (number, default 10, max 100),
post_id (array, OR logic), type (array enum image | video).
Response:
{
"data": [
{
"id": "uuid",
"mime_type": "image/jpeg",
"type": "image",
"post_id": "post-uuid | null",
"status": "pending | uploaded",
"object": {
"isDeleted": false,
"url": "https://.../file.jpg" | null,
"size_bytes": 12345,
"name": "file.jpg"
}
}
],
"meta": { "offset": 0, "limit": 10, "total": 1 }
}Example:
curl "https://<host>/api/v1/media?offset=0&limit=10&type=image" \
-H "Authorization: Bearer YOUR_SECRET_TOKEN"GET /api/v1/media/{id}
Path: id (string, required). Returns the same shape as the list items.
404 if not found or not owned by the workspace.
DELETE /api/v1/media/{id}
Path: id (string, required). Returns { "success": true } when deleted.
POST /api/v1/media/create-upload-url
Body (JSON, required):
mime_type:image/png|image/jpeg|video/mp4|video/quicktimename: original filename (string)size_bytes: integer > 0 and ≤ 10_485_760
Response:
{ "media_id": "uuid", "upload_url": "https://<host>/api/v1/media/upload/<id>", "name": "file.jpg" }PUT /api/v1/media/upload/{id}
- Path:
idfrom the reservation above. - Headers:
Content-Typemust match the reserved MIME type. - Body: raw binary bytes of the file.
Response:
{ "success": true, "media_id": "uuid", "url": "https://..." }If the body is empty you will receive { "error": "Request body is empty" }
with status 400.
Recommended upload flow
// 1) Reserve an upload slot
const reserve = await fetch('https://<host>/api/v1/media/create-upload-url', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer YOUR_SECRET_TOKEN',
},
body: JSON.stringify({
name: 'photo.jpg',
mime_type: 'image/jpeg',
size_bytes: file.size,
}),
}).then((res) => res.json());
// 2) Upload binary to the signed URL
await fetch(reserve.upload_url, {
method: 'PUT',
headers: { 'Content-Type': 'image/jpeg', Authorization: 'Bearer YOUR_SECRET_TOKEN' },
body: file,
});
// 3) Attach reserve.media_id in the Posts APINotes
- Status lifecycle:
pending(slot created) →uploaded(file stored). - Deleting media also unlinks attached post assets.
- For URL-based media, use the Posts API’s
media_urlsfield instead of uploading a file.
