Wahlu API

Media

Import remote media and explicitly read its processing readiness.

The released Media API has four operations: create a caller-owned upload session, import a public image or video URL, explicitly read readiness, and create a reviewed repair derivative for a supported incompatible format. Wahlu returns opaque delivery URLs underhttps://media.wahlu.com/assets/…, never a storage-provider URL.

Create an upload session

POST/v1/brands/:brand_id/media/upload-sessions

Requires media:write. Send the filename, supported content type, exact byte size, SHA-256 content hash, and an idempotency key. Wahlu returns a short-lived scoped PUT capability; local clients can stream the caller-owned bytes without making them public first. This capability is not available through hosted HTTP MCP, which remains URL-only.

Import media from a URL

POST/v1/brands/:brand_id/media/imports

Requires media:write. The remote server must expose a supported image or video over HTTP or HTTPS with an exactContent-Length. This operation starts processing and returns a stable Media ID plus a resolved read link.

Send either an Idempotency-Key header or anidempotency_key body field for each logical import. If both are sent, they must match. A new import returns 201; an exact replay returns 200 with Idempotency-Replayed: true and does not fetch the remote URL again. Reusing the key with different input returns 409.

Request body

ParameterTypeDescription
url*stringPublic HTTP or HTTPS image or video URL.
filenamestringOptional filename override, up to 500 characters.
folder_idstringOptional existing media folder ID.
idempotency_keystringBody alternative to Idempotency-Key. If both are sent, they must match.
curl
curl -X POST https://api.wahlu.com/v1/brands/brand_01k0acme/media/imports \
  -H "Authorization: Bearer $WAHLU_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: import-autumn-hero-v1" \
  -d '{
    "url": "https://assets.example.com/campaign/hero.jpg",
    "filename": "hero.jpg"
  }'
201 response
{
  "success": true,
  "data": {
    "id": "media_01k0hero",
    "status": "processing",
    "replayed": false,
    "download_url": "https://media.wahlu.com/assets/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "links": {
      "self": {
        "href": "/v1/brands/brand_01k0acme/media/media_01k0hero",
        "method": "GET",
        "required_scopes": ["media:read"]
      }
    }
  },
  "meta": { "request_id": "req_media_imported_01k0" }
}

Create a reviewed repair derivative

POST/v1/brands/:brand_id/media/:media_id/repair-derivatives

Requires media:write. Use an exact repair_options entry returned by content preflight. Explicitly send its target integration, platform, post type, mode, option ID, and policy version with a caller-owned idempotency key. Wahlu recomputes the option against the current source hash, then creates a new immutable Media item; it never overwrites the source or silently changes the draft's selected media.

An exact replay returns the same derivative with 200. Changed parameters under the same key return 409. Stale options and transforms outside the versioned loss limits fail closed. To publish the reviewed derivative, explicitly place its returned Media ID in the target platform's media_ids and run preflight again.

Create the selected derivative
curl -X POST https://api.wahlu.com/v1/brands/brand_01k0acme/media/media_01k0hero/repair-derivatives \
  -H "Authorization: Bearer $WAHLU_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: repair-autumn-grid-v1" \
  -d '{
    "integration_id": "integration_01k0instagram",
    "platform": "instagram",
    "post_type": "GRID_POST",
    "mode": "centre_crop",
    "repair_option_id": "repair_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "policy_version": "2026-08-05.1"
  }'

Get a Media item

GET/v1/brands/:brand_id/media/:media_id

Requires media:read. Followlinks.self.href from the import result. One explicit read returns the current resource, structured readiness, a boundednext_actions array, and the same resolved self link.

Attach the ID to content only whenreadiness.ready_for_content is true. When it is false, surface the returned guidance: transient processing returnsread_media_again, while a failed or unavailable asset returnsrepair_media. The API does not hide a polling loop inside this read operation.

One explicit readiness read
const mediaUrl = "https://api.wahlu.com" + imported.links.self.href;
const mediaRes = await fetch(
  mediaUrl,
  { headers: { Authorization: "Bearer wahlu_live_your_api_key_here" } }
);
const mediaPayload = await mediaRes.json();
if (!mediaRes.ok) {
  throw new Error(mediaPayload.error?.message ?? "Could not read media");
}

const media = mediaPayload.data;
if (!media.readiness.ready_for_content) {
  const guidance = media.readiness.next_actions
    .map((action) => action.guidance)
    .join(" ");
  throw new Error(guidance || "Media is not ready for content");
}

const mediaId = media.id;
200 ready response
{
  "success": true,
  "data": {
    "id": "media_01k0hero",
    "file_name": "hero.jpg",
    "content_type": "image/jpeg",
    "size": 245760,
    "duration": null,
    "status": "completed",
    "label_ids": [],
    "folder_id": null,
    "download_url": "https://media.wahlu.com/assets/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "thumbnail_large_url": null,
    "thumbnail_small_url": null,
    "source": "remote_import",
    "description": null,
    "last_used_at": null,
    "created_at": "2026-07-19T02:00:00.000Z",
    "updated_at": "2026-07-19T02:00:05.000Z",
    "readiness": {
      "ready_for_content": true,
      "next_actions": []
    },
    "links": {
      "self": {
        "href": "/v1/brands/brand_01k0acme/media/media_01k0hero",
        "method": "GET",
        "required_scopes": ["media:read"]
      }
    }
  },
  "meta": { "request_id": "req_media_ready_01k0" }
}