Media
Upload and manage media files.
Manage media files (images and videos) that can be attached to posts. You can fully upload media via API.
Required upload flow: request signed URL → upload bytes with PUT → mark media as ready_for_processing via PATCH → wait for completed.
Generate upload URL
POST
/v1/brands/:brand_id/media/upload-urlCreate a ready-for-processing media item and receive signed upload URLs. Requires media:write.
Request body
| Parameter | Type | Description |
|---|---|---|
filename | string | Original file name including extension. Required. |
content_type | string | MIME type such as image/jpeg or video/mp4. Required. |
size | number | File size in bytes. Optional. |
duration | number | Duration in seconds for video/audio. Optional. |
folder_id | string | Folder ID to organise media. Optional. |
source | string | One of: upload, generated, stock, scan. Optional (defaults to upload). |
Response fields
| Parameter | Type | Description |
|---|---|---|
id | string | Media ID for future post media_ids. |
upload_url | string | Signed URL for uploading the main file via PUT. |
thumbnail_small_upload_url | string | Optional signed URL for custom small thumbnail upload. |
thumbnail_large_upload_url | string | Optional signed URL for custom large thumbnail upload. |
thumbnail_small_path | string | Storage path for small thumbnail (for reference). |
thumbnail_large_path | string | Storage path for large thumbnail (for reference). |
next_step | string | Instruction to PATCH status=ready_for_processing after upload. |
JavaScript
// 1) Create signed upload URLs
const createRes = await fetch(
`https://api.wahlu.com/v1/brands/${brandId}/media/upload-url`,
{
method: "POST",
headers: {
Authorization: "Bearer wahlu_live_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({
filename: "slide-1.jpg",
content_type: "image/jpeg",
size: fileBuffer.length,
source: "upload",
}),
}
);
const { data: upload } = await createRes.json();
// 2) Upload bytes directly to storage
await fetch(upload.upload_url, {
method: "PUT",
headers: { "Content-Type": "image/jpeg" },
body: fileBuffer,
});
// 3) IMPORTANT: finalise upload and queue processing
await fetch(
`https://api.wahlu.com/v1/brands/${brandId}/media/${upload.id}`,
{
method: "PATCH",
headers: {
Authorization: "Bearer wahlu_live_your_api_key_here",
"Content-Type": "application/json",
},
body: JSON.stringify({ status: "ready_for_processing" }),
}
);List media
GET
/v1/brands/:brand_id/mediaReturns uploaded media for a brand. Requires media:read.
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | integer | Page number. Defaults to 1. |
limit | integer | Items per page. Defaults to 50, max 100. |
sort_by | string | Field to sort by. One of: created_at, updated_at, file_name, last_used_at. Defaults to created_at. |
sort_dir | string | Sort direction. One of: asc, desc. Defaults to desc. |
Media statuses
ready_for_processing— file is uploaded and queued for processing.processing— background processing running.completed— safe to use inmedia_idsfor posting.failed— processing failed; check logs or re-upload.
Use status as the source of truth.
Get a media item
GET
/v1/brands/:brand_id/media/:media_idReturns a single media item. Requires media:read.
Update media metadata or finalise upload
PATCH
/v1/brands/:brand_id/media/:media_idUpdates metadata and can trigger processing when status is set to ready_for_processing. Requires media:write.
Request body
| Parameter | Type | Description |
|---|---|---|
status | string | Use ready_for_processing to finalise an uploaded file and start media processing. |
file_name | string | Update the displayed filename. |
label_ids | string[] | Updated label IDs. Max 50 items. |
folder_id | string | Move media to a different folder. |
thumbnail_small_url | string | Custom small thumbnail URL. |
thumbnail_large_url | string | Custom large thumbnail URL. |
source | string | One of: upload, generated, stock, scan. |
description | string | Updated description. Max 2000 characters. |
curl (finalise upload)
curl -X PATCH https://api.wahlu.com/v1/brands/brand_abc123/media/media_abc123 \
-H "Authorization: Bearer wahlu_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"status": "ready_for_processing"
}'JavaScript (wait until completed)
async function waitForMediaCompleted(brandId, mediaId, apiKey, timeoutMs = 120000) {
const started = Date.now();
while (Date.now() - started < timeoutMs) {
const res = await fetch(
`https://api.wahlu.com/v1/brands/${brandId}/media/${mediaId}`,
{ headers: { Authorization: `Bearer ${apiKey}` } }
);
const { data } = await res.json();
if (data.status === "completed") return data;
if (data.status === "failed") {
throw new Error(`Media ${mediaId} failed processing`);
}
await new Promise((r) => setTimeout(r, 3000));
}
throw new Error(`Timed out waiting for media ${mediaId} to become completed`);
}Delete media
DELETE
/v1/brands/:brand_id/media/:media_idDeletes a media file. Requires media:write.
curl
curl -X DELETE https://api.wahlu.com/v1/brands/brand_abc123/media/media_abc123 \
-H "Authorization: Bearer wahlu_live_your_api_key_here"Response
{
"success": true,
"data": {
"deleted": true
}
}