Skip to main content

States

Every request progresses through one of these statuses:
COMPLETED, FAILED, and CANCELLED are terminal — the request will never transition away from them.
Two things about terminal states that are easy to get wrong:A failed generation is recorded as COMPLETED, not FAILED. When the provider errors, the request itself completed — it just produced no output — so it lands on COMPLETED with a populated error and a billingStatus of failed. FAILED is reserved for requests the platform force-fails after they get stuck. So status alone does not tell you whether the work succeeded; check billingStatus (charged or partial means real output).CANCELLED is never written today. Nothing sets it, and cancel_url does not cancel (see Cancellation). Handle it defensively if you like, but do not build a flow that waits for it.

Submitting a request

The response returns immediately with request_id, status: "IN_QUEUE", and three URLs:

Tagging requests with metadata

The submit body may include a reserved top-level metadata object alongside the model’s input fields — a flat string map of your own tags (job ids, environments, batch labels):
Metadata is stored on the request and returned whenever you read the request back — GET /requests/{requestId}, the response_url result payload, and list items. (The lightweight status_url envelope does not include it.) It is never sent to the model and never merged into the stored input. Limits (violations return 400): Filter your request history by metadata with a single URL-encoded JSON query param — pairs match exactly and are AND-ed, combinable with status, modelEndpoint, and pagination:
metadata is a reserved word at the top level of the submit body: a model whose own input schema defines a metadata field cannot receive it through the raw body.

Three ways to watch a request

GET /{ownerName}/{modelName}/requests/{requestId}/status returns the same response shape as the create call. Poll at 1–2 second intervals. The SDK helpers (subscribe in JS, submit_async + iter_events in Python) wrap this loop for you.
The queue_position field is currently always 0 — real queue depth is not tracked. Don’t surface it as “you’re #N in line” in your UI.

Cancellation

GET /{ownerName}/{modelName}/requests/{requestId}/cancel returns the request’s current status payload.
This endpoint does not currently cancel anything. It is wired to the same handler as status_url and returns the same response, so the request continues to run and will still be billed if it completes. No request ever reaches CANCELLED (see the note under States).The route exists so that clients and SDKs built against it keep working once real cancellation ships. Until then, treat a submitted request as uninterruptible and do not rely on cancel_url to stop billable work.

Platform safety net: automatic finalization

You do not need to implement retry logic for requests you’ve abandoned. A background sweep runs every 60 seconds and re-checks every request that is:
  • Still IN_PROGRESS past the provider’s expected duration, or
  • Marked COMPLETED by the provider but missing output media (media upload still in flight).
The sweep advances each row through its terminal state automatically (uploads media to S3, generates thumbnails, charges billing on success, or marks failed otherwise). A request that has been retried 5 times and is older than 6 hours is force-failed. After that point its status is permanently FAILED and any associated billing is reverted.
Concretely: if your client crashes between submitting a request and polling for its result, the platform will still finalize the request correctly. You can retrieve it later with GET /requests/{requestId} (no model path required) or via list_my_requests.

Billing tie-in

Billing settles on the same lifecycle:
  • A request transitioning to COMPLETED charges the user’s balance.
  • A request transitioning to FAILED or CANCELLED is not charged.
  • A request that completed at the provider but failed schema validation is recorded with billingStatus: "failed" — you are not billed, but the call returns 422 so you can surface the upstream error.
See errors for the 422 payload shape.