# Tích hợp qua lớp trung gian

> **Tóm tắt** — Dành cho workflow engine đăng ký Wandura như một HTTP model với header API key: nó **gửi** một run rồi **poll** `status_url` đến trạng thái cuối. Callback có ký là tuỳ chọn. Gateway key do nhân sự Wandura tạo.

Dành cho workflow engine đăng ký Wandura như một HTTP model với header API key: nó **gửi** một run rồi **poll** `status_url` của run đến trạng thái cuối. Callback có ký là tuỳ chọn. Mọi ví dụ dưới đây lấy từ OpenAPI của API. (Engine mong đợi API dạng job của nhà cung cấp thì dùng [Jobs API](https://docs.wandura.ai/vi/developers/api-v2/jobs).)

## 1. Lấy gateway key
**Gateway key** là API key thường của một product, do nhân sự Wandura tạo (hãy nhờ chúng tôi; không tạo được từ console). Với key này:

- `end_user_id` là **tuỳ chọn**. Bỏ đi khi engine không có user id (ví dụ dưới không gửi); gửi khi có. Không có id thì không có trần theo ngày cho từng người.
- Giới hạn tính **theo product**: số run mới mỗi phút trên các gateway key của product (60 trong ví dụ dưới; Wandura đổi được theo product).
- Header `Idempotency-Key` là **tuỳ chọn**: với header `X-Job-Ref` bên dưới, thử lại không bao giờ chạy hay tính tiền hai lần.
- Run của nó mang `path: "gateway"`, nên Usage và Runs trong console lọc và so sánh được.

## 2. Gửi một run
`POST /v2/styles/{id}` với `lane` và `inputs` (các trường của style đó, xem [danh sách style](https://docs.wandura.ai/vi/developers/api-v2/styles); đầu vào là tệp thì nhận URL https, URL S3 presigned GET dùng được). Thêm `X-Job-Ref` là id job của bạn.

```bash
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"
  }
}'
```

Phản hồi là `202` kèm run. Giữ `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` `status_url` với cùng key đến khi run có trạng thái cuối. `output[0].url` là kết quả.

```
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: bắt đầu ở **2 s**, nhân 1.5 mỗi lần, tối đa **10 s**, cộng thêm 0–0.5 s ngẫu nhiên. Relax: 10 s đến 60 s. Trạng thái cuối: `completed`, `failed`, `nsfw`, `canceled`.

## 4. Thử lại và X-Job-Ref
Gửi lại cùng `X-Job-Ref` (engine của bạn thử lại) trong vòng 10 phút thì bạn nhận lại đúng run đó, chỉ tính tiền một lần:

```
HTTP 200
Idempotent-Replayed: true

(the same body as the first answer)
```

Cùng `X-Job-Ref` mà body khác là `409 idempotency_key_reused`. Không có `X-Job-Ref` thì Wandura dùng body của request (bỏ qua thứ tự key). `Idempotency-Key` bạn gửi được dùng nguyên như đã gửi.

## 5. Callback (tuỳ chọn)
Poll là đủ. Nếu muốn thêm push, đặt URL webhook của product trong [console](https://open.wandura.ai/webhooks): khi run có trạng thái cuối, Wandura POST một event **có ký** tới đó (`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"
  }
}
```

Kiểm `Wandura-Signature` trên raw body, trả `2xx` nhanh, khử trùng theo `Wandura-Event-Id` và giữ `sequence` cao nhất cho mỗi run. Cách làm ở [Khái niệm](https://docs.wandura.ai/vi/developers/api-v2/concepts#webhooks). `data` chính là run object mà `GET` trả ở `status_url`.

## 6. Run miễn phí
Run kết thúc `failed`, `nsfw` hoặc `canceled` kèm `free_run.reason` không tốn tiền:

| `free_run.reason` | Khi nào |
|---|---|
| `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. Giới hạn tốc độ
Vượt giới hạn mỗi phút của product, lệnh gọi bị từ chối với `429 gateway_rate_limited` và header `Retry-After` (giây). Không run nào được tạo, nên hãy gọi lại đúng lệnh đó với cùng `X-Job-Ref` sau thời gian chờ:

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

Nếu bạn có gửi `end_user_id`, trần theo ngày của người đó áp dụng như mọi key (`end_user_cap_reached`). Mọi mã lỗi ở [Lỗi](https://docs.wandura.ai/vi/developers/api-v2/errors).
