viewa/docs
Core Concepts

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 complete

Polling 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:

StatusMeaning
queuedThe render was created and is waiting for capture.
capturingThe product capture is in progress.
generatingThe realism generation job is running.
succeededThe immutable composite is available at url.
failedTerminal; 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.

On this page