Bản lưu ngày / Snapshot of 2026-10-18 · Xem bản mới nhất / See the latest version
WanduraDocsBeta
BetaThese docs are in beta and change often. Something wrong, unclear or missing? Tell us.
Image and video API (v2)

Integrate via a middle layer

Markdown
On this page

TL;DR — For a workflow engine that registers Wandura as an HTTP model with an API key header: it submits a run, then polls the run's status_url until a final status. A signed callback is optional. Gateway keys are made by Wandura staff.

For a workflow engine that registers Wandura as an HTTP model with an API key header: it submits a run, then polls the run's status_url until a final status. A signed callback is optional. Every example below comes from the API's OpenAPI. (Engines that expect a provider-style job API can use the Jobs API instead.)

1. Get a gateway key

A gateway key is a normal API key for one product, made by Wandura staff (ask us; it cannot be made from the console). On it:

  • end_user_id is optional. Leave it out when your engine has no user id (the example below sends none); send it when you have one. No id means no per-user daily cap.
  • The limit is per product instead: a number of new runs per minute across the product's gateway keys (60 in the example below; Wandura can change it per product).
  • The Idempotency-Key header is optional: with the X-Job-Ref header below, retries never run or charge twice.
  • Its runs carry path: "gateway", so Usage and Runs in the console can filter and compare them.

2. Submit a run

POST /v2/styles/{id} with lane and inputs (the fields of that style, see the styles reference; file inputs take https URLs, an S3 presigned GET URL works). Add X-Job-Ref with your own job id.

curl -X POST https://api.wandura.ai/v2/styles/sty_ghibli_portrait \
  -H "Authorization: Bearer wdr_live_<gateway key>" \
  -H "Content-Type: application/json" \
  -H "X-Job-Ref: job_8f3a21" \
  -d '{
  "lane": "priority",
  "inputs": {
    "image_url": "https://example.com/photo.jpg"
  }
}'

The answer is 202 with the run. Keep status_url.

HTTP 202

{
  "id": "run_Qk2m9XaB7Lc4",
  "object": "run",
  "status": "queued",
  "style": "sty_ghibli_portrait",
  "lane": "priority",
  "end_user_id": null,
  "product": "prod_lensie_app",
  "livemode": true,
  "price": {
    "credits": 5.2,
    "usd": 0.052
  },
  "charge": "held",
  "moderation": "on",
  "path": "gateway",
  "created_at": "2026-10-13T08:00:00.000Z",
  "request_id": "run_Qk2m9XaB7Lc4",
  "status_url": "https://api.wandura.ai/v2/runs/run_Qk2m9XaB7Lc4",
  "cancel_url": "https://api.wandura.ai/v2/runs/run_Qk2m9XaB7Lc4/cancel"
}

3. Poll status_url

GET the status_url with the same key until the run reaches a final status. output[0].url is the result.

curl https://api.wandura.ai/v2/runs/run_Qk2m9XaB7Lc4 \
  -H "Authorization: Bearer wdr_live_<gateway key>"

HTTP 200

{
  "id": "run_Qk2m9XaB7Lc4",
  "object": "run",
  "status": "completed",
  "style": "sty_ghibli_portrait",
  "lane": "priority",
  "end_user_id": null,
  "product": "prod_lensie_app",
  "livemode": true,
  "price": {
    "credits": 5.2,
    "usd": 0.052
  },
  "charge": "charged",
  "moderation": "on",
  "path": "gateway",
  "created_at": "2026-10-13T08:00:00.000Z",
  "request_id": "run_Qk2m9XaB7Lc4",
  "status_url": "https://api.wandura.ai/v2/runs/run_Qk2m9XaB7Lc4",
  "cancel_url": "https://api.wandura.ai/v2/runs/run_Qk2m9XaB7Lc4/cancel",
  "output": [
    {
      "type": "image",
      "url": "https://files.example.com/outputs/2026-10-13/kJ3w9QzR1mXb7TcY0aLp4vNe.png",
      "content_type": "image/png",
      "width": 1024,
      "height": 1024,
      "duration": null,
      "fps": null,
      "size_bytes": 1482113,
      "expires_at": "2026-10-16T08:00:21.000Z"
    }
  ],
  "share_url": "https://api.wandura.ai/s/run_Qk2m9XaB7Lc4"
}

Priority: start at 2 s, multiply by 1.5, cap at 10 s, add 0–0.5 s of random jitter. Relax: 10 s up to 60 s. Final statuses: completed, failed, nsfw, canceled.

4. Retries and X-Job-Ref

Send the same X-Job-Ref again (your engine retried) within 10 minutes and you get the same run back, charged once:

HTTP 200
Idempotent-Replayed: true

(the same body as the first answer)

The same X-Job-Ref with a different body is 409 idempotency_key_reused. With no X-Job-Ref, Wandura uses the request body instead (key order ignored). An Idempotency-Key you send is used as sent.

5. The callback (optional)

Polling is enough. If you also want a push, set the product's webhook URL in the console: when the run reaches a final status Wandura POSTs a signed event to it (run.completed, run.failed, run.nsfw, run.canceled):

POST <your webhook URL>
Content-Type: application/json
User-Agent: Wandura-Webhooks/1
Wandura-Event-Id: evt_h3Zk1uPq8Wd2
Wandura-Event-Type: run.completed
Wandura-Signature: t=1791878460,v1=<hex HMAC-SHA256(secret, t + "." + raw body)>

{
  "id": "evt_h3Zk1uPq8Wd2",
  "object": "event",
  "type": "run.completed",
  "created_at": "2026-10-13T08:00:21.000Z",
  "sequence": 3,
  "data": {
    "id": "run_Qk2m9XaB7Lc4",
    "object": "run",
    "status": "completed",
    "style": "sty_ghibli_portrait",
    "lane": "priority",
    "end_user_id": null,
    "product": "prod_lensie_app",
    "livemode": true,
    "price": {
      "credits": 5.2,
      "usd": 0.052
    },
    "charge": "charged",
    "moderation": "on",
    "path": "gateway",
    "created_at": "2026-10-13T08:00:00.000Z",
    "request_id": "run_Qk2m9XaB7Lc4",
    "status_url": "https://api.wandura.ai/v2/runs/run_Qk2m9XaB7Lc4",
    "cancel_url": "https://api.wandura.ai/v2/runs/run_Qk2m9XaB7Lc4/cancel",
    "output": [
      {
        "type": "image",
        "url": "https://files.example.com/outputs/2026-10-13/kJ3w9QzR1mXb7TcY0aLp4vNe.png",
        "content_type": "image/png",
        "width": 1024,
        "height": 1024,
        "duration": null,
        "fps": null,
        "size_bytes": 1482113,
        "expires_at": "2026-10-16T08:00:21.000Z"
      }
    ],
    "share_url": "https://api.wandura.ai/s/run_Qk2m9XaB7Lc4"
  }
}

Verify Wandura-Signature on the raw body, answer 2xx fast, dedupe on Wandura-Event-Id and keep the highest sequence per run. The recipe is in Concepts. data is the same run object GET returns at status_url.

6. Free runs

A run that ends failed, nsfw or canceled with a free_run.reason costs nothing:

free_run.reason When
priority_late Priority did not finish within its promise.
provider_error The run failed on our side.
bad_input An input could not be used (for example a URL that is not http(s)).
content_blocked The input or output was blocked by the content filter (status nsfw).

7. Rate limit

Over the product's per-minute limit the call is refused with 429 gateway_rate_limited and a Retry-After header (seconds). No run is made, so retry the same call with the same X-Job-Ref after that wait:

HTTP 429
Retry-After: 40

{
  "error": {
    "code": "gateway_rate_limited",
    "message": "This product reached its limit of 60 runs per minute on gateway keys. Retry after the Retry-After header.",
    "limit": 60,
    "retry_after_s": 40
  }
}

If you do send an end_user_id, that person's daily cap applies as on any key (end_user_cap_reached). All error codes are in Errors.

Updated 14 Oct 2026 · Change history (1)

↑↓ selectEnter openEsc close

Send feedback

Your message goes straight to the Wandura team. It includes the address of this page.

0 / 4000