# Concepts

> **TL;DR** — A run is one call to one style. Priority is on time or free; Relax is cheaper and waits. A run that did not deliver is free. Webhooks are signed; submits are idempotent; `end_user_id` is required.

## Runs
A **run** is one call to one style that makes one result. You submit it, it is `queued`, then `running`, then final.

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`.

A run carries its `price` and a `charge`: `held` while it runs, `charged` when it completes, `released` when it is free, `none` for test keys. Cancel a queued run with `POST` to its `cancel_url`; the hold is released.

## Styles and lanes
A **style** is a ready-made result (for example a portrait look or a motion clip). You never pick a model: Wandura keeps the providers behind each style and can switch them without changing your code. Each style has a schema of `inputs`, at `/v2/public/styles/{style}/openapi.json`.

Every run picks a **lane**:

- **Priority**: done within the style's promise (shown in the [styles reference](https://docs.wandura.ai/developers/api-v2/styles)) or the run is free.
- **Relax**: cheaper, waits in line, one active run per end user.

`POST /v2/quote` takes the same body as a run and returns the price and ETA without charging.

## Free runs
You never pay for a run that did not deliver. A free run ends with `free_run.reason`:

| `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). |

## Webhooks
Set one webhook URL per product in the [console](https://open.wandura.ai/webhooks). Each run status change is POSTed as an event, **signed**:

```
Wandura-Signature: t=1791600000,v1=5f2b…
```

`v1` is the hex HMAC-SHA256 of `t + "." + raw body` with your webhook secret. Verify it on the raw body, reject a `t` more than 5 minutes from now, then answer 2xx quickly. Non-2xx is retried (30 s, 2 min, 10 min, 1 h, 6 h, 24 h). Deliveries can repeat: dedupe on `Wandura-Event-Id`, and keep the highest `sequence` per run. The [SDKs](https://docs.wandura.ai/developers/api-v2/sdks) include a verify helper.

Event types: `run.running`, `run.completed`, `run.failed`, `run.nsfw`, `run.canceled`. Output links are kept 72 h.

## Limits
| Limit | Default | What happens |
|---|---|---|
| Runs per end user per day | 20 | Over it: 429 `end_user_cap_reached` with `Retry-After` (resets 00:00 UTC). Your org can change its cap. |
| Runs at once per product | 20 | More runs wait in line (queued) instead of failing; Priority goes first. |
| Active Relax runs per end user | 1 | The next Relax run of that end user starts when the last one ends. |
| Webhook signature window | 300 s | Reject a webhook whose timestamp is older or newer than this. |

Need more? Tell us your volume and we raise your org's limits.

## Idempotency
Every submit (`POST /v2/styles/{style}` or `POST /v2/runs`) must send an `Idempotency-Key` header, any unique string up to 255 characters. If the network drops and you retry with the same key and body, you get the first run back (`200`, header `Idempotent-Replayed: true`): it never runs or charges twice. The same key with a different body is `409 idempotency_key_reused`. Use a new key for each new run.

## End-user ID
`end_user_id` is required on every run: the id of the person in your app the run is for (any string you choose, up to 255 characters; do not send emails or names). Daily caps and the Relax rule count per end user, so one heavy user cannot use up your whole product. Use it in the console to answer a user's complaint.

## Uploads
No storage of your own? `POST /v2/uploads` with `{content_type, size_bytes}` returns a presigned `upload_url`. PUT the bytes there, then pass `file_url` in `inputs`. Files are kept 24–48 hours.

## Test and live
Test keys (`wdr_test_…`) return a sample result for $0 and follow the same rules (idempotency, end-user ID, webhooks), so the code you test is the code you ship. Live keys run for real.
