Skip to content

Async jobs (video)

Video generation is asynchronous: you create a job, then poll until it reaches a terminal status. The gateway runs this lifecycle through the standard POST /v1/videos door, adding tenant safety and accurate billing on top.

The kinds that go through this create-then-poll lifecycle and appear in GET /gw/async are:

  • video — via the standard POST /v1/videos door (this page) or a provider’s native prefix.
  • image — some image models are async upstream too; the POST /v1/images door (create) / GET /v1/images/{image_id} (poll) mirrors the video lifecycle with a 202 on create — see media doors.
  • 3d — the same async machinery supports it, but no 3d models are in the live catalog yet.
  • audio — some ASR models run as async jobs, but only over a provider’s native prefix (e.g. /alibaba); there is no /v1/audio/* async door — see transcription (ASR) for the native-prefix lifecycle.

Embeddings, reranks and TTS (audio/speech) all return synchronously — one request, one response — and never create an async job, so they do not appear in GET /gw/async; their usage shows up only in GET /gw/usage. See embeddings, reranks & speech for those synchronous doors. The POST /v1/images/generations OpenAI-Images door is also always synchronous — see media doors for the split between that and the async POST /v1/images door.

You address a video model by its catalog slug author/model; the door resolves the slug and routes to wherever inference runs. The POST /v1/videos door is live today and serves both Alibaba Wan and ByteDance Seedance.

CREATE POST /v1/videos → returns a job id
POLL GET /v1/videos/{id} → status: pending | succeeded | failed | ...
LIST GET /gw/async → served from the gateway ledger

Example (ByteDance Seedance):

Terminal window
# CREATE
curl https://api.sociaro.com/v1/videos \
-H "Authorization: Bearer gw_live_..." \
-H "Content-Type: application/json" \
-d '{"model": "bytedance/seedance-1-5-pro", "prompt": "A cat playing piano", "seconds": 5}'
# → {"id": "video_...", "status": "pending", ...}
# POLL until terminal
curl https://api.sociaro.com/v1/videos/video_... \
-H "Authorization: Bearer gw_live_..."

The door returns a single, consistent job shape regardless of which model serves the request: a video_... job id at create, then a status field that moves to a terminal state. You always read the job id from the create response’s id field and poll the same door — no provider-specific field names to track.

The Python SDK drives this loop for you (client.video.generate(...) / client.video.submit(...)).

The catalog covers current video/image generations — Wan 2.7, Seedance 2.0, Seedream 5.0 and more (see GET /v1/models). The door translates only the envelope (model, prompt, seconds, size) and passes any other top-level field (e.g. seed, ratio, watermark, negative_prompt) straight through to the provider’s native body, so provider-specific parameters work without leaving the door — see media doors.

For genuine vendor APIs you can also call the native transparent-proxy prefix directly — e.g. /alibaba (Alibaba DashScope). The gateway forwards the request to the vendor and records the job in the same ledger, with the same tenant isolation and billing. Most callers should prefer the standard POST /v1/videos door, which gives one consistent job shape across every video model.

  • Tenant isolation. Every job is recorded in the gateway’s ledger. Polling a task that belongs to another organization returns 404 — the request never reaches the provider. The gateway serves LIST from its own ledger (GET /gw/async) rather than proxying any provider-side list (which would leak other tenants’ jobs).
  • Accurate billing. At CREATE time the job is metered with a provisional cost (from your request’s duration/resolution parameters). When the job reaches a terminal state — via your poll or the gateway’s background poller — the cost is finalized exactly once: succeeded jobs get the real final cost, failed/cancelled jobs are zeroed. See Final-cost precedence below for how the final number is derived.
  • Background finalization (reaper). If you stop polling, the gateway polls the provider itself and finalizes the job, so your analytics never show permanently “pending” spend. Jobs that can no longer be finalized are marked expired (e.g. after the polling TTL, or when upstream credentials were rotated mid-flight).

When a job succeeds, the gateway computes final_cost_usd from the terminal provider response, not from your original request. It tries the following sources in order and stops at the first one that applies (the source it used is recorded as finalize_source on the ledger row):

  1. explicit_cost — the provider reported a final cost in USD in its terminal task body. That figure is used verbatim, overriding all tariff math.
  2. explicit_credits — the provider reported a credit count, and the catalog tariff defines a credit_to_usd conversion. The cost is credits × credit_to_usd. (A per_credit tariff is billable only this way — without a reported credit count it falls back to the provisional.)
  3. computed — neither explicit figure is present, so the cost is derived from the tariff and the actual quantities the provider reports in the terminal task (tokens, seconds of video/audio, resolution, image count, megapixels, or a flat per-job rate), not your requested values.
  4. provisional_fallback — none of the above is computable (a tariff miss, e.g. the provider didn’t return the metering field). The provisional charge from CREATE stands as the final cost; nothing is silently set to zero.

Explicit cost and explicit credits are absolute amounts from the provider, so they are used as-is. The token/per-second/per-image/per-megapixel rates can be scaled by a variant multiplier: for a few models the rate depends on a single CREATE-time parameter (e.g. Seedance’s generate_audio — generating without audio bills at half rate). The catalog base rate is the worst case; a recognised variant value only discounts it. If you omit the parameter, or send a value the catalog doesn’t know, you are billed the full worst-case rate (never an under-charge). The variant parameter is read from your CREATE request and applied to both the provisional and the final cost. Variant multipliers do not apply to explicit_cost, explicit_credits, or flat tariffs.

The authoritative per-job figure is always the final_cost_usd on the GET /gw/async row (or provisional_cost_usd while the job is still pending).

StatusMeaningBilling
pendingCreated, not yet terminalprovisional cost
succeededCompletedfinal cost (replaces provisional)
failedProvider reported failurecost zeroed
cancelledCancelledcost zeroed
expiredGateway could not finalize (expiration_reason: ttl or credential_rotated)provisional cost stands

Use GET /gw/async to list jobs with status, provisional/final cost and attribution — filterable by status, kind and time range, with cursor pagination. The same data backs the dashboard’s Async Jobs page.

In raw usage rows (GET /gw/usage), async requests appear with parse_status = provisional until finalization, then ok (succeeded), failed, cancelled, or expired.