# More like one product

`POST https://stylor.ai/api/v1/shoppers/me/similar`

- Authentication: public API key, sent as `Authorization: Bearer sgpt-pk-…`
- Rate-limit resource: `api.v1.shoppers`
- Demo key: accepted
- Web page: https://stylor.ai/guides/rest/v1/shopper-similar

Products like one the shopper picked, from the same slot, ordered by
how alike they are and, with a token, a quarter by the shopper's own
taste. The product itself is not returned, nor products that are not
worn (slot `non fashion`).

## Example request

```bash
curl -X POST "https://stylor.ai/api/v1/shoppers/me/similar" \
  -H "Authorization: Bearer $STYLOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "itemId": "35a725c8-23fb-4009-a52a-2a778d2e504a",
  "limit": 12,
  "country": "US"
}'
```

## Headers

- `X-Shopper-Token` (string): The shopper's profile token, from `auth/start`, `auth/verify` or any call that made a profile. Optional here. [example `"eyJ2IjoxLCJ0eXAiOiJwcm9maWxlIiwicCI6IjRmM2Ei…"`]

## Request body

Content type `application/json`, required.

- `itemId` (string, required): The product to find more like.
- `ageGroup` (string): Only this age group, as products carry it in `metadata.age_group`. `adult` keeps products with no age group (labelled before age groups existed), as search does. [one of `"adult"`, `"kids"`, `"baby"`]
- `limit` (integer): Products to return. Clamped to 1–60. [default `12`; minimum 1; maximum 60]
- `country` (string): The shopper's two-letter country, for prices in their market.

### Examples

#### More like this

Twelve products like a pair of jeans.

```json
{
  "itemId": "35a725c8-23fb-4009-a52a-2a778d2e504a",
  "limit": 12,
  "country": "US"
}
```

## Responses

### 200

The products, most alike first.

Content type `application/json`:

- `items` (array<ShopperProductCard>, required)
  - `itemId` (string): The product. Open it with Retrieve products.
  - `name` (string, nullable): The product's name.
  - `url` (string, nullable): The product's page on its store.
  - `price` (number, nullable): Price in `currency`, in the shopper's market when `country` was sent.
  - `currency` (string, nullable): ISO currency code.
  - `compareAtPrice` (number, nullable): The price before a sale, when on sale.
  - `image` (string, nullable): A 600 px wide WebP of the product's best photo.
  - `slot` (string, nullable): The garment slot.
  - `colour` (string, nullable): Its colour family.
  - `score` (number): How well it fits their taste, or how alike it is for similar.
  - `reason` (string, nullable): Why it is here, in a short sentence the shopper can read.
  - `style` (string, nullable): The id of the style it was chosen for, from `profile.styles`. Null for something new.

Example:

```json
{
  "items": [
    {
      "itemId": "a342cbe3-965b-4055-8ac3-20f63103cade",
      "name": "NEW CLASSIC JEAN DENIM",
      "url": "https://ca.stussy.com/products/new-classic-jean-denim",
      "price": 195,
      "currency": "CAD",
      "compareAtPrice": null,
      "image": "https://stylor.ai/api/v1/image/v1/datasets/images/0c1d2e3f.jpeg?resize=600x&format=webp",
      "slot": "bottom",
      "colour": "blue",
      "score": 0.912,
      "reason": "Like the one you picked.",
      "style": null
    }
  ]
}
```

## 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 | Request body must be valid JSON. | A body was sent and could not be parsed as JSON. |
| 400 | Request body must be a JSON object. | A body was sent and parsed to something other than a JSON object. |
| 401 | That shopper token is not valid here. Start a profile or sign in again. | `X-Shopper-Token` was sent but cannot be opened with this key. |
| 404 | No such product. | `itemId` is not a product in your catalogue. |
| 422 | ageGroup must be one of adult, kids, baby. | `ageGroup` was sent (non-null) but is another value. |
| 422 | itemId is required. | `itemId` is missing or empty. |

### 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: token is not recognized. | A private-key endpoint was called with a key whose lookup segment does not exist. |
| 401 | Authentication failed: private key is invalid. | A private-key endpoint was called with a key whose secret does not match. |
| 401 | Authentication failed: public key provided instead of a private key. | A private-key endpoint was called with an `sgpt-pk-…` key. |
| 401 | Authentication failed: bearer token format is invalid. | A private key was sent without its `::` lookup segment. |
| 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 key does not have the "<scope>" scope. | A private-key endpoint was called with a key whose scopes do not include the one the endpoint needs. Nothing is counted against your quota. See Authentication. |
| 403 | This key's scopes do not cover this endpoint. | A key with scopes called a private endpoint that no scope grants yet. Give the key every scope to reach it. |
| 403 | Demo mode is not available for private-key routes. Demos are only supported with public API keys. | The demo key was sent to a private-key endpoint. |
| 403 | Demo mode is not supported on this API route. Check the documentation for available demo endpoints. | The demo key was sent to a public-key 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 quota found for organization "<organizationId>". | The organization has no quota record for the current billing period. |
| 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. |
