Bản lưu ngày / Snapshot of 2026-10-17 · 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.
Change history

History: Image and video generation: the Jobs API

Every change to this page, newest first. Expand an entry to see what changed.

← Image and video generation: the Jobs API

  1. 17 Oct 2026 · +3 −3 lines
    Show changes
    ⋯
      
      - `url` is an HTTPS link on Wandura storage (the Wandura CDN), downloadable directly with a plain GET. It is not a link to any third party.
    - - **Kept for 72 hours.** `expires_at` says exactly when. Copy the file to your own storage before then.
    + - **Works for at least 7 days.** `expires_at` says until when the link is guaranteed. Copy the file to your own storage before then if you need it longer.
      - Metadata is read from the file: `type`, `width`, `height`, `duration` and `fps` (video), `size_bytes`, `seed`. A value that cannot be read is `null`.
      - Test-key samples carry `null` metadata and do not expire.
    - - The job record itself is kept well past 72 hours: `GET /v2/jobs/{job_id}` keeps answering for old jobs.
    + - The job record itself is kept well past 7 days: `GET /v2/jobs/{job_id}` keeps answering for old jobs.
      
      ## Webhooks {#webhooks}
    ⋯
      | Maximum job time | image 60 s, video 300 s | Per style (`max_job_seconds`). Past it the job ends `timeout`, not charged. |
      | Submit answer time | well under 3 seconds | The submit only validates, reserves the price and queues the job. |
    - | Output files | 72 hours | See [Output](#output). |
    + | Output files | at least 7 days | See [Output](#output). |
      
      Live status and maintenance notices: [wandura.instatus.com](https://wandura.instatus.com). No uptime SLA is committed yet.
    ⋯
  2. 16 Oct 2026 · +39 −1 lines
    Show changes
      # Image and video generation: the Jobs API
      
    - > **TL;DR** — `POST /v2/jobs` with a model, a style and your inputs returns a `job_id` in about a second. Wandura generates in the background and POSTs the signed final job to your `webhook_url`; `GET /v2/jobs/{job_id}` is the fallback. Test keys (`wdr_test_…`) are a free sandbox. A bad body is refused at once with `422` and the reason; a job that fails, times out, is cancelled or is blocked by moderation is **not charged**.
    + > **TL;DR** — `POST /v2/jobs` with a model, a style and your inputs returns a `job_id` in about a second. Wandura generates in the background and POSTs the signed final job to your `webhook_url`; `GET /v2/jobs/{job_id}` is the fallback. Test keys (`wdr_test_…`) are a free sandbox. A bad body is refused at once with `422` and the reason; a job that fails, times out, is cancelled or is blocked by moderation is **not charged**. This is the recommended way in for every new integration.
      
      ## Basics {#basics}
    ⋯
      | `webhook_url` | Optional. A public https URL that gets the final job, signed with your product's Jobs API signing secret or, if it has none, its Webhook endpoint's signing secret. Needs one of the two first (see [Webhooks](#webhooks)). |
      | `end_user_id` | Optional. Your id for the person the job is for; when you send it, a per-person daily cap applies (see [Limits](#limits)). |
    + | `lane` | **Rolling out.** Optional: `priority` (default) or `relax`. See [Relax lane and a style's own inputs](#lane-and-inputs). |
    + | `inputs` | **Rolling out.** Optional object: the style's own inputs that no field above carries. See [Relax lane and a style's own inputs](#lane-and-inputs). |
      
      - **Input URLs are downloaded when the job starts**, not when you submit. A URL that cannot be downloaded ends the job `failed`, not charged. Keep a presigned URL valid for at least the style's `max_job_seconds` plus 10 minutes.
      - **Cancel** works while the job is `queued`: `200`, status `cancelled`, not charged, and the webhook fires. Once the job is `processing` it runs to the end, and cancel answers `409 JOB_NOT_CANCELLABLE`.
      
    + ## Relax lane and a style's own inputs {#lane-and-inputs}
    + 
    + **Rolling out.** Two optional fields of `POST /v2/jobs`, so every style a key can run also runs as a job. A body without them is answered exactly as before.
    + 
    + **`lane`**: `priority` (default) or `relax`.
    + 
    + | | `priority` | `relax` |
    + |---|---|---|
    + | Price | The style's Priority price | The style's Relax price, never above Priority |
    + | Deadline | `max_job_seconds`; past it the job ends `timeout`, not charged | None: the job waits in line until it runs |
    + | When the product is full | `429 PRODUCT_BUSY` | Never refused for that; the job waits as `queued` |
    + 
    + The job body is the same for both lanes (it has no `lane` field). Both prices are in the public catalog (`GET /v2/public/styles`, `price.priority` and `price.relax`).
    + 
    + **`inputs`**: an object of the style's own inputs that no field of the table above carries, named as in the style's input schema. Examples: a `keep_audio` switch, a second text field (`prompt` fills the first), a `mask_url`, or a `video_url` on an image style.
    + 
    + ```bash
    + curl -X POST https://api.wandura.ai/v2/jobs \
    +   -H "Authorization: Bearer $WANDURA_KEY" -H "Content-Type: application/json" \
    +   -d '{"model": "wandura-video-v1", "style": "<style id>", "image_urls": ["https://…/photo.jpg"], "lane": "relax", "inputs": {"keep_audio": false}}'
    + ```
    + 
    + - **Where to find a style's inputs**: `GET /v2/public/styles/{style}` → `inputs` (no key). Each style's OpenAPI, `GET /v2/public/styles/{style}/openapi.json`, carries the exact `POST /v2/jobs` body for that style, with an example.
    + - An input that a field above carries goes in that field, not in `inputs`: `{"field": "inputs.image_urls", "reason": "send it as image_urls"}`.
    + - A style whose **required** input is not one of the fields above runs only when the body sends `inputs`. It is not listed in `GET /v2/styles` or the model schema; without `inputs` it gets the same `422` as an unknown style.
    + - `GET /v2/models/{model_id}/schema` lists only the fields of the table above; `lane` and `inputs` are in the [OpenAPI](https://api.wandura.ai/v2/openapi.json) (`JobRequest`).
    + - Audio styles are not on the Jobs API yet.
    + 
    + Problems with these fields come back as `422 INVALID_INPUT` details, like every other field:
    + 
    + | `field` | `reason` |
    + |---|---|
    + | `lane` | `must be priority or relax` |
    + | `inputs` | `must be an object of the style's own inputs` |
    + | `inputs.<name>` | `send it as <field>` · `style <id> does not take this input` · `must be true or false` · `must be a public https URL (at most 8192 characters)` · `must be a list of 1 to 10 https URLs` · `must be a string of at most 4000 characters` · `required by style <id>` |
    + 
      ## Validation is strict and synchronous {#validation}
      
    ⋯
  3. 16 Oct 2026 · +3 −3 lines
    Show changes
    ⋯
      | `negative_prompt`, `aspect_ratio`, `resolution`, `duration`, `seed` | Only for styles that list them in `inputs`. See [Validation](#validation). |
      | `num_outputs` | Optional, must be `1`: one output per job. |
    - | `webhook_url` | Optional. A public https URL that gets the final job, signed. Needs your product's Jobs API signing secret first (see [Webhooks](#webhooks)). |
    + | `webhook_url` | Optional. A public https URL that gets the final job, signed with your product's Jobs API signing secret or, if it has none, its Webhook endpoint's signing secret. Needs one of the two first (see [Webhooks](#webhooks)). |
      | `end_user_id` | Optional. Your id for the person the job is for; when you send it, a per-person daily cap applies (see [Limits](#limits)). |
      
    ⋯
      Each job with a `webhook_url` gets **one** POST when it reaches a final status (no intermediate webhooks). The body is exactly what `GET /v2/jobs/{job_id}` returns at that moment.
      
    - 1. **Create your product's Jobs API signing secret once**: in the [console](https://open.wandura.ai/webhooks), "Jobs API signing secret" → Create. It is shown once; Roll replaces it at once. Until it exists, a submit with `webhook_url` is refused with `422`.
    + 1. **Have a signing secret once**: in the [console](https://open.wandura.ai/webhooks), either save a Webhook endpoint URL (its signing secret then signs job webhooks too), or create a dedicated "Jobs API signing secret" (Create; it is shown once; Roll replaces it at once). When the product has a Jobs API signing secret, that one signs job webhooks. Until it has one of the two, a submit with `webhook_url` is refused with `422`.
      2. **Verify every request** on the **raw body**: `X-Signature` is the hex HMAC-SHA256 of `X-Timestamp + "." + raw body` with your secret, and `X-Timestamp` (unix seconds) is within 5 minutes of now.
      3. **Answer `2xx` within 10 seconds**, then do your work. Dedupe on `job_id` (or the `Wandura-Event-Id` header, the same on every retry): delivery is at least once.
    ⋯
      ## Errors {#errors}
      
    - Every error answer is `{"error": {"code", "message", "retryable"}}`, plus `details` for `INVALID_INPUT`. Branch on `code`; quote the `X-Request-Id` header when you ask for support.
    + Every error answer is `{"error": {"code", "message", "retryable"}}`, plus `details` for `INVALID_INPUT`. Branch on `code`; quote the `X-Request-Id` header when you ask for support. **Rolling out:** each error also carries `docs_url`, a link to its section on the [Errors](errors.md#jobs-errors) page, and the headers `Wandura-Error-Code` and `Wandura-Should-Retry` (see [Error body and headers](errors.md#error-body)).
      
      | HTTP | `code` | `retryable` | Meaning |
    ⋯
  4. 16 Oct 2026 · +1 −1 lines
    Show changes
    ⋯
      | 401 | `UNAUTHORIZED` | false | Missing, unknown, revoked or expired API key. |
      | 402 | `INSUFFICIENT_CREDITS` | false | The product's wallet cannot cover the job. Add funds, then retry. |
    - | 403 | `FORBIDDEN` | false | The key is valid but may not do this. Reserved: not returned today. |
    + | 403 | `FORBIDDEN` | false | The key is valid but may not do this. Rolling out: a key with the Read only permission submitting or cancelling, or a key limited to other styles (see [API keys](concepts.md#api-keys)). Not returned before that. |
      | 404 | `NOT_FOUND` | false | No such job, model or endpoint for this key. |
      | 409 | `IDEMPOTENCY_KEY_REUSED` | false | This `Idempotency-Key` was already used with a different body. |
    ⋯
  5. 15 Oct 2026 · +7 −3 lines
    Show changes
    ⋯
      # X-RateLimit-Remaining: 59
      # X-RateLimit-Reset: 1791897360
    + # X-Concurrency-Limit: 20
    + # X-Concurrency-Running: 3
    + # X-Concurrency-Waiting: 0
      # {"job_id":"run_QVqsXwr7VGSP","status":"queued","model":"wandura-video-v1","created_at":"2026-10-13T13:15:40.825Z"}
      
    ⋯
      | 409 | `JOB_NOT_CANCELLABLE` | false | Only queued jobs can be cancelled. |
      | 429 | `RATE_LIMITED` | true | Too many jobs submitted this minute for this product. Retry after `Retry-After`. |
    + | 429 | `PRODUCT_BUSY` | true | Your product's concurrency slots and its queue are full. Retry after `Retry-After` (about 5 s). |
      | 429 | `END_USER_LIMIT_REACHED` | true | This end user reached the daily job cap. Retry after `Retry-After` (resets 00:00 UTC). |
      | 500 | `INTERNAL_ERROR` | true | Something went wrong on Wandura's side. Retry with the same `Idempotency-Key`. |
    - | 503 | `SERVICE_UNAVAILABLE` | true | This style cannot run on a live key right now. Test keys still return a sample. |
    + | 503 | `SERVICE_UNAVAILABLE` | true | This style cannot run on a live key right now (test keys still return a sample). Or the job queue is briefly unavailable: nothing was created or charged; retry after `Retry-After` with the same `Idempotency-Key`. |
      
    - A `429` always carries a `Retry-After` header (seconds). Wait that long, add a little random jitter, and retry with the **same** `Idempotency-Key`. Retry only the errors marked `retryable`.
    + A `429` always carries a `Retry-After` header (seconds). Wait that long, add a little random jitter, and retry with the **same** `Idempotency-Key`. Retry only the errors marked `retryable`. Submit answers, `429` included, also carry `X-Concurrency-Limit`, `X-Concurrency-Running` and `X-Concurrency-Waiting`: see [Queue and congestion](queue-and-congestion.md).
      
      ```http
    ⋯
      |---|---|---|
      | Submit rate | 60 jobs per minute per product | Live and test keys count apart. Over it: `429 RATE_LIMITED` with `Retry-After`. Every submit answers `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`. |
    - | Jobs running at once | 20 per product | Further jobs wait as `queued`. A job still waiting at its maximum job time ends `timeout`, not charged. |
    + | Jobs running at once | 20 per product | Further jobs wait as `queued`. As many jobs can wait as you have slots; past that, `429 PRODUCT_BUSY` with `Retry-After`. A job still waiting at its maximum job time ends `timeout`, not charged. Submit answers show the numbers (`X-Concurrency-*`). |
      | Per end user | 20 jobs per day | Only when you send `end_user_id`. Resets 00:00 UTC; `429 END_USER_LIMIT_REACHED`. |
      | Maximum job time | image 60 s, video 300 s | Per style (`max_job_seconds`). Past it the job ends `timeout`, not charged. |
    ⋯
  6. 13 Oct 2026 · Page created
↑↓ selectEnter openEsc close

Send feedback

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

0 / 4000