API reference · v1

API docs

Send one to eight photos of a person, get their body measurements back as JSON. Requests go over HTTPS to https://api.menzura.ai; answers are JSON, and the photo upload is multipart/form-data.

On this page
Get started

Quick start

Send the photos with the person's height, weight and sex. You get a scan id back right away; fetch the result with it.

# 1. Send the photos
curl -X POST https://api.menzura.ai/v1/measure \
  -H "Authorization: Bearer $MENZURA_KEY" \
  -F photos[][email protected] \
  -F photos[][email protected] \
  -F photos[][email protected] \
  -F photos[][email protected] \
  -F height_cm=183 \
  -F weight_kg=80 \
  -F sex=male

# 202 Accepted
{ "id": "msr_8f2c41a07d9e4b1c2f3a", "status": "queued", "created_at": "2026-10-30T09:12:04.512Z" }

# 2. Poll until status is "succeeded" or "failed"
curl https://api.menzura.ai/v1/measurements/msr_8f2c41a07d9e4b1c2f3a \
  -H "Authorization: Bearer $MENZURA_KEY"

Authentication

Send your API key as a bearer token on every request:

Authorization: Bearer $MENZURA_KEY

There are two kinds of key. A test key is free and may have a limit on the number of scans; ask us for one. A paid key is created on the dashboard and billed the way your account is billed: pay per scan or a bundle.

Before the first scan, accept the data processing agreement on the dashboard. Until then, POST /v1/measure answers 403 dpa_not_accepted.

Photos

Photos are deleted as soon as the scan finishes, whether it succeeded or failed.

Endpoints

POST /v1/measure

Start a scan. One scan is one person.

Headers

Fields

Send the request as multipart/form-data. All four fields are required.

The answer is 202 Accepted with the scan's id, its status (queued) and created_at, and a Location header with the address of the result.

Retrying safely

If a request times out or the connection drops, you can't tell whether the scan was created. Sending it again could create a second scan, billed separately. To prevent that, add an Idempotency-Key header with a value you choose, unique for each scan, such as a random UUID (up to 255 characters). A retry with the same key answers 202 with the scan that was already created, in its current status, instead of starting a new one.

Idempotency-Key: 3f1c9a52-7d4e-4b8a-9c21-5e6f0a7b8d90

Use a new key for every new scan: a key you used before always returns its original scan, even with different photos. Keys are shared by all API keys of your account, test and paid alike, so don't reuse values between testing and production. A new random UUID per scan avoids both.

GET /v1/measurements/:id

Get a scan's status and, once it has succeeded, its measurements.

A scan's status goes from queued to processing to succeeded or failed; after a passing problem on our side it can go back to queued to be tried again. Most scans finish within a minute. There are no webhooks: poll every few seconds until the status is succeeded or failed.

{
  "id": "msr_8f2c41a07d9e4b1c2f3a",
  "status": "succeeded",
  "units": "cm",
  "height_cm": 183,
  "weight_kg": 80,
  "sex": "male",
  "photo_count": 4,
  "created_at": "2026-10-30T09:12:04.512Z",
  "views_used": 4,
  "measurements": {
    "chest": { "value": 114.6, "confidence": null },
    "waist": { "value": 86.2, "confidence": null },
    "hip": { "value": 106.9, "confidence": null },
    "inseam": { "value": 76.5, "confidence": null },
    "inseam_left": { "value": 76.4, "confidence": null },
    "inseam_right": { "value": 76.6, "confidence": null },
    … every name under Measurements
  },
  "composition": { "bmi": 23.9 },
  "visible_surface_fraction": 0.83,
  "completed_at": "2026-10-30T09:12:51.207Z"
}

GET /v1/usage

Count the calling key's scans.

Over the key's whole lifetime, or over the days from to to (both included, formatted YYYY-MM-DD, days in UTC, at most 366 days). Give both or neither. Scans count by the moment they finished, as billing counts them.

curl "https://api.menzura.ai/v1/usage?from=2026-10-01&to=2026-10-31" \
  -H "Authorization: Bearer $MENZURA_KEY"

{
  "key": { "id": "key_…", "name": "Production", "type": "paid" },
  "period": { "from": "2026-10-01T00:00:00Z", "to": "2026-10-31T23:59:59Z" },
  "usage": { "succeeded": 412, "failed": 9 },
  "limit": null,
  "remaining": null
}

For a test key with limits, limit and remaining show the successful and failed scans allowed and left over the key's lifetime, such as { "succeeded": 25, "failed": 10 }. A scan that is still running counts towards the successful scans.

Reference

Measurements

Every successful result has these 21 names. Measurements marked ISO 8559-1 follow the definition in that standard. The others are common in tailoring or added by us; their definition is given here.

Girths

Lengths

Left and right

For thigh, knee, calf, ankle, upper_arm, wrist, sleeve_length and inseam, the result also has _left and _right values, such as thigh_left and thigh_right. The name without a side is the average of both.

We may add measurements over time. Ignore names you don't know, so your integration keeps working when one is added.

Failed scans

A scan that fails has status failed and an error with one of these codes. The message explains it in plain words, so it can be shown to the person taking the photos. Failed scans aren't billed.

Errors

A refused request answers with an HTTP status and a body like { "error": { "code": "invalid_request", "message": "height_cm must be a number between 100 and 250." } }.

Retention

Photos are deleted as soon as a scan finishes. The result is kept for 90 days, so your application can fetch it; store what you need. After that, GET /v1/measurements/:id answers 410 expired. See Privacy and the data processing agreement.

Questions or a test key: [email protected].