viewa/docs
Core Concepts

Areas

Mark the openings in a photo and get back their real-world measurements.

An area is one opening in the customer's photo — a window, a door, a garage entrance. Once a photo is ready, you send the four corners of each area and the API works out how big that opening really is and how it sits in space, so a product can be placed into it convincingly.

Areas are created, updated and referenced as a set. The estimation runs across every area in the set at once rather than one at a time, so the corners for one photo belong in a single call.

Send the shopper's X-Viewa-Visitor-Token alongside your API key on every call on this page — an area set belongs to the visitor who created it, and some instances require the token. See Visitor tokens.

curl -X POST https://your-instance.example/api/v1/areas \
  -H "Authorization: Bearer pk_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 8c2e41a7-..." \
  -d '{
    "image": "img_7bQ2mR9vXpL4dK1sZeCt",
    "areas": [
      { "corners": [[0.21, 0.30], [0.78, 0.30], [0.78, 0.82], [0.21, 0.82]] }
    ]
  }'
{
  "id": "aset_9mK3pQ7wZxN2vC5tBhLr",
  "image": "img_7bQ2mR9vXpL4dK1sZeCt",
  "status": "processing",
  "areas": [
    {
      "id": "area_4nH8xW2qTvB6yJ0aRmDp",
      "name": null,
      "corners": [[0.21, 0.3], [0.78, 0.3], [0.78, 0.82], [0.21, 0.82]],
      "measurements": { "width": null, "height": null, "unit": "m" },
      "plane": { "rotation": null, "center": null }
    }
  ],
  "error": null
}

Corner points

Corners are [x, y] pairs normalized to 0..1 against the photo. Normalized coordinates mean your corner-picking UI doesn't have to care what resolution the photo is displayed at — or that the API resized it on upload.

Order doesn't matter. Four points describe exactly one shape, so the API sorts them around the perimeter for you. If a customer drags a corner past its neighbour and the outline crosses itself, that's normalized rather than rejected — your editor doesn't need to keep them in order. The response returns the sorted order.

Four points, no more, no fewer. These are rejected with invalid_corners:

  • anything other than exactly four points
  • a coordinate outside 0..1
  • two corners at the same point
  • a shape so thin or flat it encloses no real area

The detail on the error names which area failed and why, so you can put the message straight in front of the customer.

Naming an area

Each area takes an optional name — free text, up to 120 characters, purely for your own UI:

{
  "image": "img_7bQ2mR9vXpL4dK1sZeCt",
  "areas": [
    { "name": "Living room window", "corners": [[0.10, 0.30], [0.44, 0.30], [0.44, 0.80], [0.10, 0.80]] },
    { "name": "Kitchen window",     "corners": [[0.56, 0.28], [0.90, 0.28], [0.90, 0.78], [0.56, 0.78]] }
  ]
}

It is a label, never an identifier — two areas may share a name, and it plays no part in matching areas on update. id is the identity. Omit name and the area comes back with "name": null.

Measurements arrive with ready

Notice the 201 above comes back with measurements and plane as null. The size estimate and the occlusion pass run together in one background job, so poll the set and read them once status is ready.

measurements are in meters, estimated from the photo's depth data. plane describes how the opening is oriented in space; you only need it if you're doing your own placement, and can ignore it otherwise.

The lifecycle

status covers the remaining preparation, which finishes in the background:

POST /api/v1/areas       → { "id": "aset_…", "status": "processing", areas: [...] }
GET  /api/v1/areas/{id}  → poll until "ready" (or "failed")
statusMeaning
processingMeasuring and preparing. measurements and plane are null — keep polling.
readyComplete. Required before you can request a render.
failedTerminal. error says why.

As with images, a failed set is resource state, not an HTTP error — the poll still answers 200 with a structured error. error.code is stable: occlusion_failed, occlusion_timeout or area_calculation_failed. Recreate the set to retry. See Errors.

Reading one area

GET /api/v1/areas/{id} takes either id. Pass the set's aset_… id for the whole set, or a single area_… id for just that area:

GET /api/v1/areas/aset_9mK3pQ7wZxN2vC5tBhLr   → the set
GET /api/v1/areas/area_4nH8xW2qTvB6yJ0aRmDp   → one area

To find the sets already defined on a photo:

GET /api/v1/areas?image=img_7bQ2mR9vXpL4dK1sZeCt

That list is paginated and does not advance a set's status — poll the single resource for that.

Editing a set

PUT replaces the set's areas. Everything derived from them is recalculated and status returns to processing:

curl -X PUT https://your-instance.example/api/v1/areas/aset_9mK3pQ7wZxN2vC5tBhLr \
  -H "Authorization: Bearer pk_..." \
  -H "Origin: https://shop.example" \
  -H "Content-Type: application/json" \
  -d '{
    "areas": [
      { "id": "area_4nH8xW2qTvB6yJ0aRmDp",
        "corners": [[0.25, 0.35], [0.85, 0.35], [0.85, 0.85], [0.25, 0.85]] }
    ]
  }'

Send every area you want the set to have — the PUT replaces it. One call covers every edit:

To do thisSend
Move a cornerThe area's id with its new corners
Rename an areaThe area's id with a new name
Add an areaAn area with no id
Remove an areaNothing — just leave it out
Reorder areasThe same ids in a different order

Identity comes from id, never from position. Send an area's id back and it keeps that id, so any product or material selections you have stored against it stay valid — wherever it now sits in the array. This is why reordering is safe: if identity were inferred from position, a UI that re-sorts areas left-to-right would silently repoint area_4nH8... at a different window, and the customer would get the wrong blind on the wrong opening.

An id that isn't part of this set — or the same id twice in one request — is rejected with unknown_area.

You can't remove the last area this way; a set with no areas has no meaning. Use DELETE /api/v1/areas/{id} to discard the whole set.

If the recalculation fails, the set becomes failed with area_calculation_failed; send another PUT with adjusted corners, or create a new set.

One photo, many sets

A set stays bound to the photo it was created against. Sending a different image on a PUT is rejected with image_immutable: every measurement in the set was derived from that specific photo, so repointing it would be meaningless. Create a new set instead — the photo itself is reused, and one image can carry as many sets as you like.

That's also how you offer alternative layouts: create a set per layout, keep the ids, and render from whichever the customer picks.

Multiple areas

A photo with two windows is one set with two areas:

{
  "image": "img_7bQ2mR9vXpL4dK1sZeCt",
  "areas": [
    { "corners": [[0.10, 0.30], [0.44, 0.30], [0.44, 0.80], [0.10, 0.80]] },
    { "corners": [[0.56, 0.28], [0.90, 0.28], [0.90, 0.78], [0.56, 0.78]] }
  ]
}

Each gets its own area_… id and its own measurements, returned in the order you sent them. Send them together rather than as two separate sets: the estimation considers the whole photo at once, and a render covers one set.

Next

With a ready set, you can request a render — the area ids are what you attach product and material selections to.

On this page