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
/v1/brands/:brand_id/media/upload-sessionsRequires 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
/v1/brands/:brand_id/media/importsRequires 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
| Parameter | Type | Description |
|---|---|---|
url* | string | Public HTTP or HTTPS image or video URL. |
filename | string | Optional filename override, up to 500 characters. |
folder_id | string | Optional existing media folder ID. |
idempotency_key | string | Body alternative to Idempotency-Key. If both are sent, they must match. |
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"
}'{
"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
/v1/brands/:brand_id/media/:media_id/repair-derivativesRequires 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.
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
/v1/brands/:brand_id/media/:media_idRequires 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.
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;{
"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" }
}