Bản lưu ngày / Snapshot of 2026-10-20 · Xem bản mới nhất / See the latest version
WanduraDocsBeta
BetaDocs đang ở bản beta và còn thay đổi. Thấy chỗ sai, khó hiểu hoặc còn thiếu? Hãy cho chúng tôi biết.
API tạo ảnh và video (v2)

Tạo ảnh và video: Jobs API

Markdown
Trên trang này

TL;DR — POST /v2/jobs với một model, một style và dữ liệu đầu vào của bạn sẽ trả về job_id sau khoảng một giây. Wandura tạo kết quả ở nền rồi POST job cuối cùng (có ký) tới webhook_url của bạn; GET /v2/jobs/{job_id} là đường dự phòng. Key test (wdr_test_…) là sandbox miễn phí. Body sai bị từ chối ngay bằng 422 kèm lý do; job failed, timeout, bị huỷ hoặc bị moderation chặn thì không bị tính tiền. Đây là cửa khuyên dùng cho mọi tích hợp mới.

Cơ bản

  • Base URL: https://api.wandura.ai (vùng Singapore).
  • OpenAPI 3.1: api.wandura.ai/v2/openapi.json, không cần key. Jobs API là các operation gắn tag Jobs.
  • Luồng: submit kèm webhook_url và Idempotency-Key → 202 {job_id, status: "queued"} → Wandura POST job cuối cùng (có ký) tới webhook_url, bạn trả 200 → nếu webhook không tới, poll GET /v2/jobs/{job_id} → tải output[0].url.
  • Mọi request là JSON qua HTTPS.
Endpoint Làm gì
POST /v2/jobs Gửi một job.
GET /v2/jobs/{job_id} Trạng thái và output. Cùng body với webhook.
POST /v2/jobs/{job_id}/cancel Huỷ job còn queued.
GET /v2/models Danh sách model.
GET /v2/models/{model_id}/schema JSON Schema của body submit cho một model.
GET /v2/styles?model= Các style đang chạy, kèm bản xem trước.

Xác thực và key

Mọi lời gọi gửi Authorization: Bearer <key>. Tạo key ở console; một product có thể có nhiều key và xoay vòng được. Giữ key ở máy chủ của bạn.

Key Làm gì Chi phí
wdr_test_… Sandbox. Cùng API, cùng validation, status và webhook; job trả về một file mẫu thay vì tạo thật $0
wdr_live_… Tạo thật Giá của style, chỉ khi job thành công

Job thuộc về product của key đã gửi nó: key của product khác nhận 404.

Model và schema

Hai model, mỗi loại đầu ra một model.

model Đầu ra
wandura-image-v1 ảnh
wandura-video-v1 video

v1 trong model id là version của model, không phải version của API. Id cố định: thay đổi phá vỡ tương thích sẽ có id mới (…-v2), không bao giờ đổi âm thầm. Style được thêm và gỡ dưới một model mà không đổi model.

curl https://api.wandura.ai/v2/models -H "Authorization: Bearer $WANDURA_KEY"

# JSON Schema (2020-12) của body submit cho một model: kiểu, field bắt buộc, min/max,
# độ dài, danh sách style id, và với mỗi style: field nào bắt buộc, field nào không nhận
curl https://api.wandura.ai/v2/models/wandura-video-v1/schema -H "Authorization: Bearer $WANDURA_KEY"

Đọc schema lúc build hoặc theo lịch để dựng form nhập; nó đổi khi style được thêm hoặc gỡ.

Style

Style là một "kiểu" đã đóng gói sẵn: prompt, các bước tạo và cài đặt của nó nằm ở phía Wandura. GET /v2/styles?model=wandura-video-v1 liệt kê các style đang chạy mà key của bạn dùng được:

{
  "id": "sty_motion_clip_test", "object": "style", "model": "wandura-video-v1", "name": "Motion Clip",
  "description": "Bring a still image to life with gentle camera motion.",
  "input_hint": "One clear image of the subject you want to bring to life.",
  "preview_url": "https://<wandura-storage-host>/samples/v1/motion-clip.mp4",
  "inputs": { "image_url": "required", "prompt": "optional" },
  "max_job_seconds": 300, "price": { "credits": 23.4, "usd": 0.234 }
}
Field Ý nghĩa
preview_url Link https tới ảnh hoặc video mẫu làm bằng style này. Hiển thị trong màn chọn style của bạn. null nếu style chưa có mẫu.
inputs Các field của POST /v2/jobs mà style này nhận, mỗi field là required hoặc optional. Field không có trong danh sách sẽ bị từ chối.
max_job_seconds Thời gian tối đa của style. Quá mức này job kết thúc timeout, không tính tiền.
price Giá của một job live thành công (1 credit = $0.01).

Gửi, poll, huỷ

curl -X POST https://api.wandura.ai/v2/jobs \
  -H "Authorization: Bearer $WANDURA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: your-job-123" \
  -d '{
    "model": "wandura-video-v1",
    "style": "sty_motion_clip_test",
    "image_url": "https://your-bucket.example.com/u/42/photo.jpg?X-Amz-Signature=...",
    "prompt": "a girl smiling at the camera",
    "webhook_url": "https://your-backend.example.com/hooks/wandura",
    "end_user_id": "user_42"
  }'
# HTTP 202
# X-RateLimit-Limit: 60
# 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"}

curl https://api.wandura.ai/v2/jobs/run_QVqsXwr7VGSP -H "Authorization: Bearer $WANDURA_KEY"

curl -X POST https://api.wandura.ai/v2/jobs/run_QVqsXwr7VGSP/cancel -H "Authorization: Bearer $WANDURA_KEY"
Field Ghi chú
model Bắt buộc. wandura-image-v1 hoặc wandura-video-v1.
style Bắt buộc. Id style của model đó (GET /v2/styles).
prompt Đoạn chữ mà style đưa vào kết quả. Tuỳ chọn, trừ khi style liệt kê nó là bắt buộc; "" được coi như không gửi.
image_url, image_urls, video_url URL https công khai (URL presigned GET dùng được, tối đa 8192 ký tự). Ảnh JPEG, PNG hoặc WEBP tới 20 MB; video MP4, MOV hoặc WEBM tới 50 MB.
negative_prompt, aspect_ratio, resolution, duration, seed Chỉ cho style có liệt kê trong inputs. Xem Validation.
num_outputs Tuỳ chọn, phải là 1: mỗi job một output.
webhook_url Tuỳ chọn. URL https công khai nhận job cuối cùng, ký bằng Jobs API signing secret của product, hoặc nếu product chưa có thì bằng signing secret của Webhook endpoint. Cần có một trong hai trước (xem Webhook).
end_user_id Tuỳ chọn. Id của người dùng trong hệ thống của bạn; khi gửi, giới hạn theo ngày cho từng người sẽ áp dụng (xem Giới hạn).
lane Đang triển khai. Tuỳ chọn: priority (mặc định) hoặc relax. Xem Lane Relax và đầu vào riêng của style.
inputs Đang triển khai. Object tuỳ chọn: các đầu vào riêng của style mà không trường nào ở trên mang được. Xem Lane Relax và đầu vào riêng của style.
  • URL đầu vào được tải khi job bắt đầu chạy, không phải lúc submit. URL không tải được sẽ làm job failed, không tính tiền. Giữ URL presigned còn hạn ít nhất bằng max_job_seconds của style cộng 10 phút.
  • Huỷ được khi job còn queued: 200, status cancelled, không tính tiền, và webhook vẫn được gửi. Khi job đã processing nó chạy tới cùng, và huỷ trả 409 JOB_NOT_CANCELLABLE.

Lane Relax và đầu vào riêng của style

Đang triển khai. Hai trường tuỳ chọn của POST /v2/jobs, để style nào key chạy được cũng chạy được thành job. Body không có hai trường này được trả lời y như trước.

lane: priority (mặc định) hoặc relax.

priority relax
Giá Giá Priority của style Giá Relax của style, không bao giờ cao hơn Priority
Hạn chót max_job_seconds; quá hạn thì job kết thúc timeout, không tính tiền Không có: job xếp hàng tới lượt thì chạy
Khi product đã đầy 429 PRODUCT_BUSY Không bao giờ bị từ chối vì lý do đó; job chờ ở queued

Body của job giống nhau ở cả hai lane (không có trường lane). Cả hai giá có trong catalog công khai (GET /v2/public/styles, price.priority và price.relax).

inputs: object chứa các đầu vào riêng của style mà không trường nào trong bảng trên mang được, đặt tên đúng như trong schema đầu vào của style. Ví dụ: công tắc keep_audio, trường chữ thứ hai (prompt điền trường đầu tiên), mask_url, hoặc video_url trên style ra ảnh.

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}}'
  • Tìm đầu vào của một style ở đâu: GET /v2/public/styles/{style} → inputs (không cần key). OpenAPI của từng style, GET /v2/public/styles/{style}/openapi.json, có sẵn body POST /v2/jobs đúng cho style đó, kèm ví dụ.
  • Đầu vào nào đã có trường ở trên mang thì gửi bằng trường đó, không gửi trong inputs: {"field": "inputs.image_urls", "reason": "send it as image_urls"}.
  • Style có đầu vào bắt buộc không thuộc các trường ở trên chỉ chạy khi body gửi inputs. Style đó không có trong GET /v2/styles hay schema của model; không gửi inputs thì nhận đúng 422 như một style không tồn tại.
  • GET /v2/models/{model_id}/schema chỉ liệt kê các trường trong bảng trên; lane và inputs nằm trong OpenAPI (JobRequest).
  • Style ra âm thanh chưa có trên Jobs API.

Lỗi ở hai trường này trả về trong details của 422 INVALID_INPUT, như mọi trường khác:

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 chặt và đồng bộ

Body sai không bao giờ trở thành job. Wandura kiểm tra cả body ngay lúc submit và liệt kê mọi lỗi:

HTTP 422
{"error":{"code":"INVALID_INPUT","message":"Validation failed.","retryable":false,"details":[
  {"field":"num_outputs","reason":"must be 1 (one output per job)"},
  {"field":"aspect_ratio","reason":"style sty_motion_clip_test does not take this field"},
  {"field":"image_url","reason":"required by style sty_motion_clip_test"}]}}
  • Style tự cố định tỉ lệ khung hình, độ phân giải và thời lượng. Các style hiện tại không nhận aspect_ratio, resolution, duration hay seed, nên gửi một trong số đó sẽ trả 422 kèm lý do. Chỉ gửi những gì inputs của style liệt kê, và dùng schema của model để biết đó là gì.
  • Field lạ, thiếu field bắt buộc, URL sai định dạng và style không tồn tại đều là 422.

Idempotency

Gửi Idempotency-Key (1 đến 255 ký tự) ở mọi submit để retry không bao giờ chạy hai lần.

  • Cùng key và cùng body: cùng một job (200, header Idempotent-Replayed: true, status hiện tại của job).
  • Cùng key nhưng body khác: 409 IDEMPOTENCY_KEY_REUSED. Dùng key mới cho job mới.
  • Không gửi header: Wandura tự sinh key từ API key và body, nên cùng body trong 10 phút là cùng một job.

Status

queued → processing → succeeded | failed | cancelled | timeout

Bốn status cuối là status cuối cùng và không bao giờ đổi nữa.

status Cuối Tính tiền (key live) error
queued không chưa null
processing không chưa null
succeeded có có, giá của style null
failed có không GENERATION_FAILED, INPUT_UNUSABLE hoặc CONTENT_MODERATION_BLOCKED
timeout có không TIMEOUT
cancelled có không null

Lỗi nằm trong một job đã kết thúc (error.code):

status code retryable Ý nghĩa
failed CONTENT_MODERATION_BLOCKED false Đầu vào hoặc kết quả bị content moderation chặn. Không tính tiền.
failed INPUT_UNUSABLE false Một file đầu vào không dùng được (không tải được, định dạng không hỗ trợ, hoặc quá dài). Không tính tiền.
failed GENERATION_FAILED true Tạo kết quả thất bại ở phía Wandura. Không tính tiền; gửi lại thường là được.
timeout TIMEOUT true Job không xong trong thời gian tối đa của style. Không tính tiền.

Bị moderation chặn là một job cuối cùng bình thường, không phải lỗi HTTP:

{"job_id":"run_4kT9wQx2LmPa","status":"failed","model":"wandura-video-v1","style":"sty_motion_clip_test","output":null,
 "error":{"code":"CONTENT_MODERATION_BLOCKED","message":"The input or the result was blocked by content moderation. Not charged.","retryable":false},
 "created_at":"2026-10-13T13:12:02.410Z","completed_at":"2026-10-13T13:12:03.902Z"}

Output

output là danh sách có một file, và là null cho tới khi job succeeded.

{"job_id":"run_…","status":"succeeded","model":"wandura-image-v1","style":"sty_…",
 "output":[{"url":"https://<wandura-storage-host>/outputs/2026-10-10/<random name>.png","type":"image/png",
   "width":48,"height":32,"duration":null,"fps":null,"size_bytes":136,"seed":null,"expires_at":"2026-10-13T00:00:00.000Z"}],
 "error":null,"created_at":"2026-10-10T00:00:00.000Z","completed_at":"2026-10-10T00:00:00.000Z"}
  • url là link HTTPS trên storage của Wandura (Wandura CDN), tải thẳng bằng GET thường. Đó không phải link của bên thứ ba nào.
  • Mở được ít nhất 7 ngày. expires_at cho biết link được bảo đảm tới khi nào. Cần giữ lâu hơn thì hãy chép file về storage của bạn trước đó.
  • Metadata đọc từ chính file: type, width, height, duration và fps (video), size_bytes, seed. Giá trị không đọc được là null.
  • File mẫu của key test có metadata null và không hết hạn.
  • Bản ghi job được giữ lâu hơn 7 ngày nhiều: GET /v2/jobs/{job_id} vẫn trả lời cho job cũ.

Webhook

Mỗi job có webhook_url nhận một POST khi đạt status cuối cùng (không có webhook trung gian). Body đúng bằng thứ GET /v2/jobs/{job_id} trả về tại thời điểm đó.

  1. Có một signing secret, làm một lần: ở console, hoặc lưu một URL Webhook endpoint (signing secret của nó ký luôn webhook của job), hoặc tạo riêng "Jobs API signing secret" (Create; chỉ hiện một lần; Roll thay ngay lập tức). Product có Jobs API signing secret thì secret đó ký webhook của job. Khi chưa có cái nào trong hai, submit kèm webhook_url bị từ chối bằng 422.
  2. Verify mọi request trên raw body: X-Signature là HMAC-SHA256 dạng hex của X-Timestamp + "." + raw body bằng secret của bạn, và X-Timestamp (unix giây) lệch hiện tại không quá 5 phút.
  3. Trả 2xx trong 10 giây, rồi mới xử lý việc của bạn. Dedupe theo job_id (hoặc header Wandura-Event-Id, giống nhau ở mọi lần retry): giao tin ít nhất một lần.
POST /hooks/wandura HTTP/1.1
Content-Type: application/json
User-Agent: Wandura-Webhooks/1
X-Timestamp: 1791897341
X-Signature: 6f1c…(64 hex characters)
Wandura-Event-Id: evt_…
Wandura-Event-Type: job.ended

{"job_id":"run_QVqsXwr7VGSP","status":"succeeded","model":"wandura-video-v1", …}

Chính sách retry

Trả non-2xx (kể cả redirect), timeout hoặc lỗi mạng sẽ được retry sau 30 giây, 2 phút, 10 phút, 1 giờ, 6 giờ và 24 giờ: 7 lần thử trong khoảng 31 giờ, cùng body và job_id. URL có host trỏ về địa chỉ riêng hoặc dành riêng sẽ không bao giờ được gọi. Nếu mọi lần thử đều thất bại, job vẫn còn đó: hãy poll.

Verify chữ ký

Node.js (Express):

import express from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';

// raw = the body exactly as received (before JSON.parse)
export function verifyWandura(raw, headers, secret, toleranceS = 300) {
  const ts = headers['x-timestamp'], sig = String(headers['x-signature'] || '');
  if (!/^\d+$/.test(ts || '') || Math.abs(Date.now() / 1000 - Number(ts)) > toleranceS) return false;
  const want = createHmac('sha256', secret).update(`${ts}.${raw}`).digest('hex');
  return sig.length === want.length && timingSafeEqual(Buffer.from(sig), Buffer.from(want));
}

const app = express();
app.post('/hooks/wandura', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8');
  if (!verifyWandura(raw, req.headers, process.env.WANDURA_JOBS_SECRET)) return res.sendStatus(401);
  const job = JSON.parse(raw); // { job_id, status, output, error, ... }
  // store it, dedupe on job.job_id, then:
  res.sendStatus(200);
});

Python (Flask):

import hashlib, hmac, os, time
from flask import Flask, request

def verify_wandura(raw: bytes, headers, secret: str, tolerance_s: int = 300) -> bool:
    ts, sig = headers.get("X-Timestamp", ""), headers.get("X-Signature", "")
    if not ts.isdigit() or abs(time.time() - int(ts)) > tolerance_s:
        return False
    want = hmac.new(secret.encode(), ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(want, sig)

app = Flask(__name__)

@app.post("/hooks/wandura")
def wandura_hook():
    raw = request.get_data()  # raw bytes, before any JSON parsing
    if not verify_wandura(raw, request.headers, os.environ["WANDURA_JOBS_SECRET"]):
        return "", 401
    job = request.get_json()  # dedupe on job["job_id"]
    return "", 200

Dùng sandbox để thử endpoint của bạn: job wdr_test_… cũng bắn webhook có ký y như thật.

Lỗi

Mọi câu trả lời lỗi có dạng {"error": {"code", "message", "retryable"}}, thêm details cho INVALID_INPUT. Rẽ nhánh theo code; khi nhờ hỗ trợ, đưa kèm header X-Request-Id. Đang triển khai: mỗi lỗi có thêm docs_url, link tới mục của nó trên trang Lỗi, và các header Wandura-Error-Code, Wandura-Should-Retry (xem Thân lỗi và header).

HTTP code retryable Ý nghĩa
422 INVALID_INPUT false Body hoặc header sai; details[] liệt kê từng {field, reason}.
401 UNAUTHORIZED false Thiếu key, key lạ, đã thu hồi hoặc hết hạn.
402 INSUFFICIENT_CREDITS false Ví của product không đủ trả cho job. Nạp thêm rồi thử lại.
403 FORBIDDEN false Key hợp lệ nhưng không được làm việc này. Đang triển khai: key quyền Read only gửi hoặc huỷ job, hoặc key chỉ được chạy style khác (xem Khoá API). Trước đó: chưa trả.
404 NOT_FOUND false Không có job, model hoặc endpoint đó cho key này.
409 IDEMPOTENCY_KEY_REUSED false Idempotency-Key này đã dùng với body khác.
409 JOB_NOT_CANCELLABLE false Chỉ huỷ được job đang queued.
429 RATE_LIMITED true Product gửi quá nhiều job trong phút này. Thử lại sau Retry-After.
429 PRODUCT_BUSY true Product đã hết suất chạy và hàng chờ đầy. Thử lại sau Retry-After (khoảng 5 s).
429 END_USER_LIMIT_REACHED true End user này đã chạm giới hạn job trong ngày. Thử lại sau Retry-After (reset 00:00 UTC).
500 INTERNAL_ERROR true Phía Wandura có sự cố. Thử lại với cùng Idempotency-Key.
503 SERVICE_UNAVAILABLE true Style này tạm thời không chạy được trên key live (key test vẫn trả file mẫu). Hoặc hàng đợi job tạm thời không nhận: không có gì được tạo hay tính tiền; thử lại sau Retry-After với cùng Idempotency-Key.

429 luôn kèm header Retry-After (giây). Chờ đúng khoảng đó, cộng thêm một chút jitter ngẫu nhiên, rồi retry với cùng Idempotency-Key. Chỉ retry những lỗi có retryable. Câu trả lời submit, kể cả 429, cũng có X-Concurrency-Limit, X-Concurrency-Running và X-Concurrency-Waiting: xem Hàng chờ và nghẽn.

HTTP/1.1 429
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1791897360

{"error":{"code":"RATE_LIMITED","message":"Too many jobs submitted this minute for this product. Retry after the Retry-After header.","retryable":true}}

Giới hạn

Đây là mức mặc định hiện tại, và được nâng theo từng khách hàng khi có yêu cầu: hãy báo cho Wandura đỉnh tải dự kiến của bạn (số job mỗi phút, tỉ lệ video) và giới hạn cho product của bạn sẽ được nâng.

Gì Hiện tại Ghi chú
Tốc độ submit 60 job mỗi phút mỗi product Key live và key test tính riêng. Vượt mức: 429 RATE_LIMITED kèm Retry-After. Mỗi submit trả X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
Job chạy cùng lúc 20 mỗi product Job thừa chờ ở queued. Hàng chờ chứa số job bằng số suất chạy; quá mức đó, 429 PRODUCT_BUSY kèm Retry-After. Job còn chờ tới hết thời gian tối đa của nó sẽ kết thúc timeout, không tính tiền. Câu trả lời submit cho thấy các số này (X-Concurrency-*).
Mỗi end user 20 job mỗi ngày Chỉ khi bạn gửi end_user_id. Reset 00:00 UTC; 429 END_USER_LIMIT_REACHED.
Thời gian job tối đa ảnh 60 s, video 300 s Theo style (max_job_seconds). Quá mức này job kết thúc timeout, không tính tiền.
Thời gian trả lời submit dưới 3 giây Submit chỉ kiểm tra, giữ chỗ giá và xếp job vào hàng.
File output ít nhất 7 ngày Xem Output.

Trạng thái và thông báo bảo trì: wandura.instatus.com. Chưa cam kết SLA uptime.

Tính tiền thế nào

  • Tính tiền: job succeeded trên key live, theo giá của style.
  • Không tính tiền: failed (lỗi tạo, đầu vào không dùng được, bị moderation chặn), timeout, cancelled, và mọi thứ bị từ chối ngay lúc submit (4xx). Giá được giữ khi job bắt đầu và được trả lại khi job kết thúc mà không có kết quả.
  • Key test không bao giờ tính tiền.

Phiên bản

  • Đường dẫn có version (/v2/…). Model id mang version riêng (wandura-image-v1 là version của model, không phải version của API).
  • Thay đổi phá vỡ tương thích đến dưới dạng version đường dẫn mới hoặc model id mới, báo trước ít nhất 30 ngày. Bản cũ vẫn chạy trong thời gian đó.
  • Thay đổi cộng thêm có thể ra bất cứ lúc nào: style mới, field tuỳ chọn mới, mã lỗi mới, field mới trong câu trả lời. Bỏ qua field bạn không biết, và coi error.code lạ là không retry được trừ khi retryable nói khác.

Liên hệ

  • Trạng thái và bảo trì: wandura.instatus.com.
  • Câu hỏi, nâng giới hạn, sự cố: [đầu mối tích hợp của Wandura: sẽ bổ sung].
  • Gửi kèm job_id và X-Request-Id của lời gọi.

Cập nhật 17/10/2026 · Lịch sử thay đổi (6)

↑↓ chọnEnter mởEsc đóng

Gửi góp ý

Góp ý được gửi thẳng tới đội Wandura, kèm địa chỉ trang này.

0 / 4000