# Find the people in a photo

`POST https://stylor.ai/api/v2/agent/pose`

- Authentication: public API key, sent as `Authorization: Bearer sgpt-pk-…`
- Rate-limit resource: `api.v2.agent.pose`
- Demo key: accepted
- Web page: https://stylor.ai/guides/rest/v2/detect-pose

The people in a photo, with MediaPipe's 33 body points for each (at
most two people). It is the server fallback of the shopper widget's
"See it on you" photo check, which normally runs the same models in
the browser: the widget calls it only in a browser with neither WebGL
nor WebAssembly. Deciding what the points mean (one person, face in
view, head to shin) is the caller's.

The photo is the request body itself: the raw bytes, with
`Content-Type: image/jpeg`, `image/png` or `image/webp`, at most 2 MB.
It is read in memory and dropped, never stored or logged. The photo is
turned upright and read at up to 1280px on its long edge; `width` and
`height` are the size it was read at.

```bash
curl https://stylor.ai/api/v2/agent/pose \
  -H "Authorization: Bearer $STYLOR_API_KEY" \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg
```

It is metered on `api.v2.agent.pose`, which is unlimited on every
plan.

## Example request

```bash
curl -X POST "https://stylor.ai/api/v2/agent/pose" \
  -H "Authorization: Bearer $STYLOR_API_KEY"
```

## Request body

Content type `image/jpeg`, required.

Type: `string (binary)`: The photo's bytes. `image/png` and `image/webp` are accepted the same way. [max length 2097152]

### Examples

#### A JPEG photo

The photo's raw bytes as the body, not JSON.

```json
"(binary JPEG bytes)"
```

## Responses

### 200

The people found.

Headers:

- `X-RateLimit-Resource`: The quota resource this endpoint is metered against: `api.v2.agent.chat` for the chat endpoint, `api.v2.agent.looks.render` for the looks endpoint (`api.v2.agent.looks` on a plan not yet moved to it), `api.v2.agent.match` for shop the look, `api.v2.agent.looks.complete` for complete the look and `api.v2.agent.pose` for the pose endpoint. These are separate from the v1 resources, so Agent API usage never draws down a v1 allowance.
- `X-RateLimit-Quota-Limit`: Requests allowed in the current billing period, or `unlimited`.
- `X-RateLimit-Quota-Remaining`: Requests left in the current billing period, or `unlimited`.
- `X-RateLimit-Quota-Reset`: Unix epoch seconds at which the billing period resets.
- `X-RateLimit-RPM-Limit`: Requests allowed per minute for this resource.
- `X-RateLimit-RPM-Remaining`: Requests left in the current one-minute window.
- `X-RateLimit-RPM-Reset`: Unix epoch seconds at which the one-minute window resets.

Content type `application/json`:

- `people` (array<array<array<number>>>, required): One entry per person found, each 33 points in MediaPipe Pose's order, each point `[x, y, z, visibility, presence]`. `x` and `y` are fractions of the photo's width and height as read, `z` is the model's depth estimate, and `visibility` and `presence` are 0 to 1. Rounded to five decimals. Empty when nobody is found. [max items 2]
- `width` (integer, required): The width the photo was read at, upright. [example `960`]
- `height` (integer, required): The height the photo was read at, upright. [example `1280`]
- `ms` (integer, required): How long finding the people took, in milliseconds. [example `41`]

Example:

```json
{
  "people": [
    [
      [
        0.51234,
        0.18321,
        -0.41023,
        0.99871,
        0.99912
      ],
      [
        0.52011,
        0.16544,
        -0.39871,
        0.99702,
        0.99854
      ]
    ]
  ],
  "width": 960,
  "height": 1280,
  "ms": 41
}
```

## Errors

Every error is a JSON object with an `error` string. Match on the status code; the message is written for people.

| Status | Message | When |
| --- | --- | --- |
| 400 | The request body is empty: send the photo as the body. | The body has no bytes. |
| 400 | The body could not be read as a JPEG, PNG or WebP photo. | The bytes are not an image in the declared format, or the image is over 40 million pixels. |
| 413 | The photo is <size> KB; the most this endpoint reads is 2048 KB. Send it smaller. | The `Content-Length` header declares more than 2 MB (refused without reading the body), or the body is larger than 2 MB. Checked after the API key and `Content-Type`. |
| 415 | Send the photo as the request body with Content-Type image/jpeg, image/png, image/webp. | `Content-Type` is not `image/jpeg`, `image/png` or `image/webp`. |

### Shared authentication, quota and rate-limit errors

| Status | Message | When |
| --- | --- | --- |
| 401 | API key missing. | The `Authorization` header is absent, is not `Bearer <key>`, or the key does not have three dash-separated segments. |
| 401 | Authentication failed: public key is invalid or not recognized. | A public-key endpoint was called with a key that does not exist. |
| 401 | Authentication failed: private key provided instead of a public key. | A public-key endpoint was called with an `sgpt-sk-…` key. |
| 401 | Authentication failed: this key's organization no longer exists. | The organization that owned the key has been deleted. |
| 401 | Authentication failed: API key could not be read. Regenerate the key. | The key's embedded organization data could not be decrypted or parsed. |
| 403 | This API key cannot be used from <origin>. Add the website to the key's allowed websites in the Stylor dashboard. | The key is limited to its allowed websites and the request's `Origin` is not one of them. |
| 403 | This API key only accepts requests from its allowed websites. Allow apps and servers on the key, or call from one of its websites. | The key is limited to its allowed websites and the request has no `Origin` (an app or a server). |
| 403 | This API key is for Stylor's own use only. Use another key, or change its public key access in the Stylor dashboard. | The key is set to Stylor's own use and the request does not come from a Stylor site. |
| 403 | Demo mode is not supported on this API route. Check the documentation for available demo endpoints. | The demo key was sent to an endpoint that opts out of demo mode. |
| 403 | No active subscription for this organization. | The organization that owns the key has no active subscription. |
| 403 | No active quota for this organization's plan this billing period. | The organization has no quota record for the current billing period. A failure to read the quota is a `500`, never this. |
| 403 | No entitlement for "<resource>". | The plan's quota template has no entry for this endpoint. |
| 403 | Access denied to "<resource>". | The plan's quota template turns this endpoint off. |
| 429 | Monthly quota exceeded for "<resource>" (<quota>/<quota> used). | The billing-period quota for this endpoint is exhausted. `Retry-After` gives the seconds until the period resets. |
| 429 | RPM limit exceeded for "<resource>" (<used>/<limit> used). | More than the allowed requests per minute were sent. `Retry-After` is 60. |
| 500 | Internal server error. | An unexpected failure on the server. The message is always this string; details are logged, never returned. Quota consumed by the request is refunded, as it is for every 5xx response. |
