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
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
- Four views work best: front, back, left and right. One photo already gives a result; past four, accuracy gains flatten out.
- The pose may vary per photo. The person doesn’t have to stand the same way in every photo; the pose may differ from one photo to the next.
- Little or tight clothing. Underwear is ideal. Otherwise tightly fitted clothing; avoid anything loose or layered.
- One person in every photo, whole body in view. A photo with nobody or with several people in it fails the scan.
- Same shape. All photos in one scan need the same aspect ratio and orientation, such as all portrait shots from one phone camera. Otherwise the scan fails.
Photos are deleted as soon as the scan finishes, whether it succeeded or failed.
POST /v1/measure
Start a scan. One scan is one person.
Headers
| Header | Value |
|---|---|
| Authorization | Required. Bearer and your API key; see Authentication. |
| Idempotency-Key | Optional. Your own unique value per scan, so a retry can't create a second scan; see Retrying safely. |
Fields
Send the request as multipart/form-data. All four fields are required.
| Field | Value |
|---|---|
| photos[] | One to eight photos, repeated as photos[]. JPEG, PNG or HEIC, up to 10 MB each. The format is read from the file itself, not from its name. |
| height_cm | The person's height in cm, between 100 and 250. It sets the scale of the body model. |
| weight_kg | The person's weight in kg, greater than 0. |
| sex | male or female. |
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"
}
| Field | Meaning |
|---|---|
| height_cm, weight_kg, sex, photo_count | What you sent. |
| views_used | How many of the photos the solve used. |
| measurements | Each measurement as { "value", "confidence" }. value is in cm, or null when that measurement couldn't be taken. confidence is null for now; we are working on per-value confidence ranges. See Measurements. |
| composition | bmi in kg/m². Can be missing or empty. |
| visible_surface_fraction | The share of the body surface seen in at least one photo, from 0 to 1. A low value means another view would help. Left out when unknown. |
| error | Only on a failed scan: { "code", "message" }. See Failed scans. |
| completed_at | When the scan succeeded or failed. |
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.
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
| Name | Where it is measured |
|---|---|
| chest | Horizontal, just under the armpits, around the torso. ISO 8559-1 |
| waist | Horizontal, midway between the lowest rib and the top of the hip bone. ISO 8559-1 |
| waist_narrowest | The narrowest horizontal torso girth between the top of the hip bone and the lowest rib. |
| stomach | Horizontal, at the most forward point of the belly between the hip bone and the waist. |
| hip | Horizontal, at the most backward point of the buttocks. ISO 8559-1 |
| thigh | The largest horizontal girth of the leg just below the crotch. ISO 8559-1 |
| knee | Through the middle of the kneecap, perpendicular to the leg. ISO 8559-1 |
| calf | The largest girth of the lower leg. ISO 8559-1 |
| ankle | The smallest girth of the lower leg, just above the ankle joint. |
| upper_arm | Midway between the shoulder tip and the elbow, perpendicular to the upper arm. ISO 8559-1 |
| wrist | At the wrist joint, perpendicular to the forearm. ISO 8559-1 |
| neck | The narrowest full ring around the neck between its base and the head, perpendicular to the neck. ISO 8559-1 |
| shoulder_girth | Horizontal around the torso and both arms at the widest point of the shoulders, arms hanging. |
Lengths
| Name | What it measures |
|---|---|
| inseam | The vertical distance from the crotch to the sole of the foot. ISO 8559-1 |
| crotch_length | From the front waist through the crotch to the back waist. ISO 8559-1 |
| front_rise | The front part of the crotch length: front waist to crotch. |
| back_rise | The back part of the crotch length: crotch to back waist. |
| shoulder_width | From shoulder tip to shoulder tip across the back, over the base of the neck. ISO 8559-1 |
| sleeve_length | From the shoulder tip over the elbow to the wrist, with the elbow slightly bent. ISO 8559-1 |
| shirt_length | From the side of the neck down the front to crotch level. |
| back_neck_to_waist | From the base of the neck at the back down the spine to the waist. ISO 8559-1 |
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.
| Code | What happened |
|---|---|
| no_person_detected | No person was found in one of the photos. |
| multiple_people | More than one person was found in one of the photos. |
| bad_image | A photo couldn't be read as an image. |
| processing_error | The scan couldn't be completed. When the photos are the cause, such as photos with different aspect ratios or orientations, the message says so; fix them before sending again. Otherwise the problem was on our side and you can send the scan again. |
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." } }.
| Status and code | When |
|---|---|
| 400 invalid_request | A field is missing or invalid; the message says which. |
| 401 unauthorized | The API key is missing, invalid or revoked. |
| 402 payment_required | A paid key whose account has no working billing. Start or fix billing on the dashboard. |
| 403 dpa_not_accepted | The account hasn't accepted the data processing agreement yet. |
| 403 test_limit_reached | A test key has used its scans, or reached its limit of failed scans. |
| 404 not_found | No scan with this id on your account. |
| 410 expired | The scan's result has been deleted; see Retention. |
| 413 payload_too_large | The upload is larger than eight photos of 10 MB. |
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].