Quickstart
From API key to first render.
The steps below sketch the raw-API path. If you're embedding the widget, it performs steps 2–4 for you.
The raw curl examples below represent calls from your server, so they use a secret
key and send no Origin header. Keep the sk_ key in a server-side environment
variable; never copy it into browser or client-side code. For a browser-direct call,
use a publishable key instead. The browser's actual Origin must be on that key's
allowlist — do not fabricate an Origin header from a server.
The secret key satisfies the API-key tier only. It does not replace visitor tokens, instance scoping, quotas or metering, or any route-specific authorization.
1. Browse your catalog
List the products available to your instance:
curl https://your-instance.example/api/v1/products \
-H "Authorization: Bearer sk_..."Every catalog object has a stable, prefixed public ID (prod_…, mat_…, set_…) —
see Catalog & public IDs. If you've registered
SKU aliases, you can refer to products by your own SKUs
instead.
Calling from the browser with a publishable key is supported: every /v1 endpoint answers a
CORS preflight. The key's domain allowlist is enforced on the request itself, so add your
storefront's origin to the key before you call it from the page — with
POST /api/v1/origins and your secret key.
2. Upload a photo
Upload the customer's photo (JPEG, PNG, or WebP, up to 4 MB) to start a visualisation session. The file goes in the request body — one call, no separate upload target:
curl -X POST https://your-instance.example/api/v1/images \
-H "Authorization: Bearer sk_..." \
-H "Idempotency-Key: 4f9d36c2-..." \
-F "file=@room.jpg"The response is an image resource (img_…) already in preprocessing — plus the
visitor this shopper now is:
{
"id": "img_7bQ2mR9vXpL4dK1sZeCt",
"status": "preprocessing",
"visitor": {
"id": "vis_5nQ8xR2wKt7bZm3LpVdC",
"token": "vis_5nQ8xR2wKt7bZm3LpVdC.1788307200.hSg9…",
"expiresAt": "2026-12-01T00:00:00.000Z"
}
}Store visitor.token. Every later call in this quickstart sends it as
X-Viewa-Visitor-Token — your API key identifies the shop, the visitor token
identifies the shopper, and a token you send always scopes the call to that
shopper. Sending it is optional unless your instance is configured to require it
(off by default), so build it in now and nothing changes if that gate is later
switched on. See Visitor tokens.
Poll the image until its status is ready:
curl https://your-instance.example/api/v1/images/img_7bQ2mR9vXpL4dK1sZeCt \
-H "Authorization: Bearer sk_..." \
-H "X-Viewa-Visitor-Token: vis_..."See Photos & preprocessing for the full lifecycle, including how a failed preprocess surfaces.
3. Define areas
Submit the corner points the customer marked for each opening to cover. Corners are
[x, y] pairs normalised to 0..1 against the photo, ordered around the perimeter.
Send every area for the photo in one call — they are prepared as a set:
curl -X POST https://your-instance.example/api/v1/areas \
-H "Authorization: Bearer sk_..." \
-H "X-Viewa-Visitor-Token: vis_..." \
-H "Content-Type: application/json" \
-d '{
"image": "img_7bQ2mR9vXpL4dK1sZeCt",
"areas": [
{ "corners": [[0.21, 0.30], [0.78, 0.30], [0.78, 0.82], [0.21, 0.82]] }
]
}'The response is an area set (aset_…) in processing, with each area's
measurements and plane still null — they are estimated in the background.
Poll until the set is ready: that is when the estimated real-world
measurements appear, and a render requires it.
curl https://your-instance.example/api/v1/areas/aset_9mK3pQ7wZxN2vC5tBhLr \
-H "Authorization: Bearer sk_..." \
-H "X-Viewa-Visitor-Token: vis_..."Keep the area_… ids — they are what you attach product and material selections
to in the next step. See Areas for corner
validation, multi-area photos, and what happens when a customer drags a corner.
4. Request a render
POST a render with the selected product and material. The area set must be
ready; use the area_… ids returned by the area-set response. Renders are
asynchronous: the response is a render resource whose status you poll.
curl -X POST https://your-instance.example/api/v1/renders \
-H "Authorization: Bearer sk_..." \
-H "X-Viewa-Visitor-Token: vis_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 4f9d36c2-..." \
-d '{
"areaSet": "aset_9mK3pQ7wZxN2vC5tBhLr",
"areas": [{
"area": "area_4nH8xW2qTvB6yJ0aRmDp",
"product": "prod_3xK9aQ0ZbW7mNc2LpRvT",
"materials": [{ "optionId": "slot_66c1f2a9e4b1c2d3f4a5b6c7", "material": "mat_7Bq2LmZ0xR4vNc9TgWpK" }]
}]
}'curl https://your-instance.example/api/v1/renders/ren_2pL7vQ4mXa9cN6tBhRzK \
-H "Authorization: Bearer sk_..." \
-H "X-Viewa-Visitor-Token: vis_..."Use an Idempotency-Key on the POST so a retried request
never produces (or bills) a duplicate render.
5. Display the result
Poll the render until status is succeeded or failed:
curl https://your-instance.example/api/v1/renders/ren_2pL7vQ4mXa9cN6tBhRzK \
-H "Authorization: Bearer sk_..." \
-H "X-Viewa-Visitor-Token: vis_..."When it succeeds, the resource's url is a durable, unauthenticated image URL
for display on your product page. A failed render remains a 200 resource with
an error.code; retry the request with a new idempotency key when appropriate.
6. Show their history
The same token lists everything this shopper has rendered on your instance, newest first — enough to build a "your saved looks" strip on a return visit:
curl https://your-instance.example/api/v1/visitors/me/renders \
-H "Authorization: Bearer sk_..." \
-H "X-Viewa-Visitor-Token: vis_..."Each row is a full render resource, carrying its image, its areaSet, its result
url, and the areas selections that produced it. A shopper only ever sees their
own — see Visitor tokens.