Renders & idempotency
The asynchronous render pipeline.
Rendering a composite takes real time — it's an AI image-generation step, not a template fill. The API models this explicitly.
Send the shopper's X-Viewa-Visitor-Token alongside your API key when creating,
polling and listing renders — a render belongs to the visitor who requested it, and
some instances require the token. See Visitor
tokens.
Asynchronous by design
POST a render request and you get back a render resource (ren_…) immediately, with
a status. Poll it until it completes:
POST /api/v1/renders → { "id": "ren_…", "status": "…" }
GET /api/v1/renders/{id} → poll until completePolling is the v1 contract: it is how a client learns the outcome. The render
completes on its own whether or not anyone polls it, and a render past its time
budget is marked failed by a read or by the scheduled reaper. A terminal
succeeded or failed resource is returned with HTTP 200.
Poll on a schedule shaped by when a render can finish rather than at a flat
rate: no request in the first 3 s — none completes sooner — then every 500 ms
out to 12 s, every second to 30 s, and every 2.5 s after that, until the render's
ten minute budget is spent. Honour a Retry-After header if one comes back. The
widget implements exactly this.
The status values are:
| Status | Meaning |
|---|---|
queued | The render was created and is waiting for capture. |
capturing | The product capture is in progress. |
generating | The realism generation job is running. |
succeeded | The immutable composite is available at url. |
failed | Terminal; inspect error.code and error.detail. |
Request selections
The area set must be ready. A render may select a subset of its areas; omitted
areas render empty. Each selected area needs a product, while style, style options,
materials and animation are optional and default from the selected model/product.
The field names match the catalog DTO:
curl -X POST https://your-instance.example/api/v1/renders \
-H "Authorization: Bearer pk_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4f9d36c2-..." \
-d '{
"areaSet": "aset_9mK3pQ7wZxN2vC5tBhLr",
"areas": [{
"area": "area_4nH8xW2qTvB6yJ0aRmDp",
"product": "prod_3xK9aQ0ZbW7mNc2LpRvT",
"style": "s-wave",
"styleOptions": [{ "type": "rollDirection", "value": "standard" }],
"materials": [{ "optionId": "slot_66c1f2a9e4b1c2d3f4a5b6c7", "material": "mat_7Bq2LmZ0xR4vNc9TgWpK" }]
}]
}'Products and materials may also be supplied as registered SKUs. Style options
are validated against the selected style's own model schema. Animation values are
numbers from 0 to 1; omit the array to use model defaults.
Idempotency
Send an Idempotency-Key header with every render POST. If the request is retried —
a flaky connection, a user double-click — the API returns the original render instead
of creating a duplicate:
POST /api/v1/renders
Idempotency-Key: 4f9d36c2-...Use a key that identifies the attempt (generate a UUID when the customer clicks
"visualise", and reuse it for retries of that same click). If the same key is
reused with different canonical selections, the API returns 409 idempotency_key_reused.
A successful composition cache hit is different: it creates a new succeeded resource that shares the existing immutable artifact.
Result URL and durable snapshots
On success, url points to /api/v1/renders/{id}/file. The file endpoint needs no
API key, allows wildcard CORS, and supports width (maximum 1024 pixels on the
longest edge), format (jpeg, webp, or png) and quality (1–100). It
uses an immutable cache policy, so it is suitable for an image element.
The render keeps a geometry and selection snapshot. Editing or deleting the area
set does not alter an existing render, and renders remain listable by areaSet.
Catalog content edits do not invalidate a cached artifact or change its URL; a
catalog-aware purge is a later API concern.
Failure is resource state rather than an HTTP error. The closed render error codes
are capture_failed, capture_timeout, generation_failed,
generation_timeout, dispatch_failed, and internal.