Wahlu API

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-url

Create a ready-for-processing media item and receive signed upload URLs. Requires media:write.

Request body

ParameterTypeDescription
filenamestringOriginal file name including extension. Required.
content_typestringMIME type such as image/jpeg or video/mp4. Required.
sizenumberFile size in bytes. Optional.
durationnumberDuration in seconds for video/audio. Optional.
folder_idstringFolder ID to organise media. Optional.
sourcestringOne of: upload, generated, stock, scan. Optional (defaults to upload).

Response fields

ParameterTypeDescription
idstringMedia ID for future post media_ids.
upload_urlstringSigned URL for uploading the main file via PUT.
thumbnail_small_upload_urlstringOptional signed URL for custom small thumbnail upload.
thumbnail_large_upload_urlstringOptional signed URL for custom large thumbnail upload.
thumbnail_small_pathstringStorage path for small thumbnail (for reference).
thumbnail_large_pathstringStorage path for large thumbnail (for reference).
next_stepstringInstruction 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/media

Returns uploaded media for a brand. Requires media:read.

Query parameters

ParameterTypeDescription
pageintegerPage number. Defaults to 1.
limitintegerItems per page. Defaults to 50, max 100.
sort_bystringField to sort by. One of: created_at, updated_at, file_name, last_used_at. Defaults to created_at.
sort_dirstringSort 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 in media_ids for 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_id

Returns a single media item. Requires media:read.

Update media metadata or finalise upload

PATCH/v1/brands/:brand_id/media/:media_id

Updates metadata and can trigger processing when status is set to ready_for_processing. Requires media:write.

Request body

ParameterTypeDescription
statusstringUse ready_for_processing to finalise an uploaded file and start media processing.
file_namestringUpdate the displayed filename.
label_idsstring[]Updated label IDs. Max 50 items.
folder_idstringMove media to a different folder.
thumbnail_small_urlstringCustom small thumbnail URL.
thumbnail_large_urlstringCustom large thumbnail URL.
sourcestringOne of: upload, generated, stock, scan.
descriptionstringUpdated 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_id

Deletes 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
  }
}