viewa/docs
Getting Started

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.

On this page