viewa/docs
API Reference

API Reference

Every endpoint, generated from the OpenAPI specification. Most requests are authenticated; render files are public immutable URLs.

Upload a photo

Send the photo, get back an image resource that is already preprocessing. One request — the file goes in the body; there is no separate upload step against storage. JPEG, PNG and WebP only, 4MB maximum. Both limits are enforced against the bytes actually sent: the declared content type is not trusted, so a file that decodes as anything else is rejected even when it is labeled as one of the three. The photo is resized server-side before preprocessing, so `width` and `height` on the response describe the stored image, not your original. Preprocessing begins immediately but does not block this call — poll the returned resource until it is `ready`. This is the one pipeline call that never requires a visitor token on any instance, and it is where tokens come from. Sent without `X-Viewa-Visitor-Token`, it mints a new anonymous shopper and returns `visitor` on the created image. Sent with one, the upload attaches to that existing shopper — their earlier images, areas, renders and history stay reachable — and `visitor` comes back with a refreshed `expiresAt`. Either way the block is present, so the client always has a current token to store.

Get an image and advance its preprocessing

Poll this until `status` is `ready` or `failed`. Reading the resource is what moves it forward: each call checks the preprocessing job once and returns the state that results. Nothing advances in the background, so an image you never poll stays `preprocessing`. A failed image is still a successful read — the response is 200 and the failure is carried by `status` and `error`, not by the HTTP status.

Fetch an uploaded photo

The photo's bytes. This is the URL published as `url` on the image resource. Deliberately takes no API key: a browser cannot attach one to an `<img src>`, a CSS background, or a download. The `img_…` id is the credential instead — it is not enumerable, and it is the only way to reach the image. Served by this API rather than redirecting to object storage, so no storage host enters the contract and published links keep working if storage moves.

List an image's area sets

The area sets defined on one image, newest first. Unlike reading a single set, this does not advance a set's `status` - poll `GET /areas/{id}` for that.

Define areas on an image

Creates a set of areas on a `ready` image and starts the work that prepares them for rendering. The response carries the areas with `measurements` and `plane` still null: they are estimated in the background job. `status` stays `processing` until that finishes - poll `GET /areas/{id}` until it reaches `ready`, which is required before a render and is when the measurements appear. Send every area for the image in one call: the estimation runs across the whole set at once. A set holds between 1 and 20 areas.

Read an area set, or a single area

Accepts either an `aset_` id, returning the whole set, or an `area_` id, returning just that area. Reading either form advances the set's preparation, but only the set form reports it: a single area carries no `status` of its own. Poll the `aset_` id until its `status` is `ready` (or `failed`). A `failed` set is still a successful read - `200` with `status: failed` and an `error`, not an HTTP error.

Delete an area set

Removes the set. Renders already produced from it are unaffected - a render keeps its own copy of the geometry it was built from.

Update an area set's corners

Replaces the set's areas and recalculates everything derived from them - for example after a shopper drags a corner. `status` returns to `processing`; poll until `ready` before rendering again. One call covers every edit: send an area's `id` back to keep it, omit `id` to add an area, omit the area entirely to remove it, and send the same ids in a different order to reorder. Selections you have stored against an `area_` id stay valid as long as you send that id. The same 1-to-20-area bound applies as on create. Only whole sets can be updated: the estimation runs across every area together, so there is no single-area update. If the recalculation fails, the set becomes `failed` with `area_calculation_failed` - send another update with adjusted corners, or create a new set.

List renders for an area set

Return a paginated, newest-first list filtered by one area set. Listing never advances a render's status. Historical renders remain readable from their snapshot even if the area set is later edited or deleted.

Request a composite render

Create an asynchronous composite from selections on one ready area set. The response is created before capture dispatch and normally starts in `queued`; poll the returned resource until it is `succeeded` or `failed`. The server snapshots the set geometry and resolved catalog selections, so later edits or deletion of the area set do not change this render. Omitted style, style options, materials and animation use the selected model/product defaults. Product and material values may be public ids or registered SKUs; the request vocabulary otherwise matches the catalog DTO. A successful composition cache hit creates a new succeeded resource that shares the immutable artifact. An idempotency replay returns the original resource instead. The cache is not invalidated by catalog content edits; catalog-aware purge is planned for ticket 12.

Get a render

Read a render by `ren_…` id. Rendering completes on its own; poll until the status is `succeeded` or `failed`. A render past its time budget may be marked `failed` by a read or by the scheduled reaper. A terminal failed render is still a successful read (`200`) with its closed-list error code.

Fetch a succeeded render

Stream the immutable composite bytes. This URL is unauthenticated so it can be used directly as an `<img src>`; the opaque `ren_…` id is the URL credential and the response allows every origin. It returns a uniform bodyless 404 until the render has succeeded or its artifact is unavailable. Optional transforms use Sharp's `fit: inside` and `withoutEnlargement`: `width` is capped at 1024 pixels on the longest edge, `format` may be jpeg, webp or png, and quality is 1 through 100. Successful render artifacts are immutable and cacheable indefinitely.

List the shopper's own renders

The visitor's history: every render they have created on this instance, newest first, in every state — still generating, succeeded, and failed. `me` is whoever the `X-Viewa-Visitor-Token` names; there is no other addressable visitor, which is why one shopper can never enumerate another's. Each row is a full render resource, so it already carries the `image` and `areaSet` it came from, its result `url`, and the `areas` selections that produced it — enough to rebuild a previous-looks strip without a second call. Like the other list endpoints, reading this never advances a render's status; poll `GET /renders/{id}` for that.

Delete a visitor's data

Erase one shopper's data on request, for your privacy obligations. Secret key only, and no visitor token: this is your server acting on the shopper's behalf, and a shopper exercising deletion has usually lost the token already. Removes the visitor's uploaded photos, area sets and renders — the documents and the stored files both. Afterwards their history is empty and their result URLs stop resolving. Candidate keys derived from the deleted records are removed as well; if stored files cannot be removed after the records are deleted, the response is a `500` and the affected keys are written to the server log. Usage counts are RETAINED with the visitor detached: the metered rows are what your invoice is reconciled against, and after this call they no longer reference the shopper. **Idempotent.** Calling it again — or for a visitor that never existed on your instance — is a `200` with every count zero, never a `404`. Retry freely. Two residuals to know about. First, derived preprocessing files on the rendering host (depth, normal, segmentation and mask data) are outside this endpoint and age out on that host's own schedule, within 90 days. Second, the visitor token is **not revoked**: it is stateless and keeps verifying until its own expiry, so discard it in your storefront — an upload that still presents it would create new data under the same `vis_` id.

List products

The published products in your catalog, ordered by the instance's configured priority then name. A child instance reads its parent's catalog, narrowed to the subset configured for it; products outside that subset are absent, exactly as if they did not exist. Products that publish styles are never listed — see the `Product` schema for how to model each look as its own product. Send `Accept: text/toon` to receive the same envelope encoded as TOON (see Catalog concepts); JSON is the default and errors are always problem+json.

Get a product

Fetch one published product by public id or by a SKU you have registered for it.

List the materials offered in one of a product's slots

The materials a product offers in a single material slot, with material-set membership expanded into individual materials and de-duplicated. Ordered by the instance's configured priority then name. Materials are deliberately not subset-filtered for child instances: a child sees its parent's full material catalog, mirroring the first-party app. Send `Accept: text/toon` to receive the same envelope encoded as TOON (see Catalog concepts); JSON is the default and errors are always problem+json.

Get a material

Fetch one material by public id or by a SKU you have registered for it.

Get a material set

Fetch one material set by public id or by a SKU you have registered for it. Set membership is not returned — read a product slot's materials to get the expanded list.

Per-key usage for a period

What each of your API keys consumed over a period, for reconciling an invoice. Secret key only: it enumerates every key on your instance. Counts come from the same ledger the quotas are enforced against, so `uploads` and `renders` are exactly the events that consumed allowance. `cachedRenders` is reported separately: a render answered from the composition-hash cache never reaches the GPU, never consumes quota, and is not billable. The period defaults to the current UTC calendar month, from its first instant up to now. It is half-open — `[from, to)` — so consecutive periods never double-count an event. Never cached.

List your registered SKU aliases

The audit of every SKU your instance has registered, oldest first. Secret key only: it enumerates your whole SKU map, which a browser never needs. Register, repoint and remove aliases with `PUT` and `DELETE /aliases/{sku}`; registrations made in the Viewa CMS admin appear here too. Never cached: a SKU you have just registered must be visible here on the next request.

Get one SKU alias

Read one registration by SKU. Secret key only. Never cached.

Remove a SKU alias

Remove the registration. The SKU stops resolving on the next request.

Register or repoint a SKU alias

Create the registration, or repoint it if the SKU is already registered — idempotent, so it is safe to replay when syncing your catalog. The SKU is stored exactly as sent in the path; to rename one, delete it and register the new SKU. Registration is per instance (a child instance's aliases are its own, even though it reads its parent's catalog). A SKU may not start with a reserved prefix (`prod_`, `mat_`, `set_`, `img_`, `aset_`, `area_`, `ren_`, `vis_`, `slot_`, `pk_`, `sk_`), since those forms address Viewa's own ids. The change takes effect on the next request to any endpoint that resolves a `ref`.

List your key's allowed origins

The domain allowlist of the key pair making the call: the origins its publishable key may be used from. Secret key only — a publishable key can never read or change its own allowlist. Each key pair has its own list; other key pairs on your instance are never affected. Never cached.

Allow an origin

Add an origin to the calling key pair's allowlist. It is reduced to its canonical form (lowercase scheme and host, port only when non-default, no path or trailing slash), so `https://Shop.example.com/cart` is stored as `https://shop.example.com`. List every variant you serve from — `www` and apex, staging — as separate entries. A key may list at most 50 origins. Takes effect on the next request.

Remove an allowed origin

Remove an origin from the calling key pair's allowlist. The origin is passed as a query parameter rather than in the path, because an encoded `//` in a path is not reliably preserved by proxies. Browser requests from it are refused with `origin_not_allowed` from the next request.