# The sizes card

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

- 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-sizes-card

The "Which brands do you wear?" card the Stylor chat widget shows, as
data, for your app to draw the same card outside a chat. The widget
and this endpoint build it with the same code, so the two always
match: the same brands in the same order, the same size chips per
brand and garment, the same likely bottoms for each top size.

## How the card works

The shopper picks the brands they wear (up to five) from `brands`,
popular ones first (`popular: true`), searching the rest by `name`
and `aliases`. Then, for each brand, a size in each garment in
`garments`, from that brand's chips: `brands[].sizes` is keyed
`<department>:<garment>` (`mens:jeans`), each entry a size system
(`letters`, `numbers`, `tailored`) with its chips. When a brand offers
more than one system for a garment, show a switch. Once a top size is
picked, `brands[].likely["<department>:<top>><bottom>"]` names the
bottom sizes that usually go with it, to suggest first. Finally, how
that size fits them, from `fits` (`tight`, `right`, `loose`).

Without `department` the card holds both departments' sizes and
should ask womenswear or menswear first; with it, only that one's.

## Saving the answer

Send what the shopper picked to
[remember](https://stylor.ai/guides/rest/v1/remember-for-shopper.md) as `sizes`, one row
per brand and garment, as the card has them:
`{ "brand": "Uniqlo", "garment": "tshirt", "kind": "tops", "label": "M", "fit": "tight" }`.
From then on [size](https://stylor.ai/guides/rest/v1/shopper-size.md) and the chat read
them, and this card comes back with them in `saved`.

## Logos

A brand with `logo: true` has its logo at
`https://stylor.ai/brand-logos/<slug>.png` (the host you call, a
public PNG that needs no key). Show logos only when the card's
`logos` is `true`; otherwise show names.

## Store rules

The card is your whole organization's, with no one store in scope, so
it is built under the default rules: logos on and no brand left out.
A store's own sizing settings (brands it leaves out, logos off) apply
in that store's chat and when its products are sized with
[size](https://stylor.ai/guides/rest/v1/shopper-size.md), which never reads a size at a
brand the product's store left out.

The token is optional: with one, the sizes the profile keeps come
back in `saved` to show already picked. No profile is made.

## Example request

```bash
curl -X POST "https://stylor.ai/api/v1/shoppers/me/size/card" \
  -H "Authorization: Bearer $STYLOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "department": "mens",
  "garments": [
    "tshirt",
    "jeans"
  ],
  "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`.

- `department` (string, nullable): Whose sizes. Leave it out for a card that asks. [one of `"womens"`, `"mens"`, `null`]
- `garments` (array<string>, nullable): The garments to ask a size in, one card row each, for what the shopper is shopping for: `[tshirt, jeans]` for everyday, `[suit, trousers]` for a wedding, `[dress]`. At most one top, one bottom and one one-piece is kept, in the order sent; menswear is never asked a dress, a skirt or leggings (left out, and the default asked when nothing is left). [default `["tshirt","jeans"]`; min items 1; max items 3; one of `"tshirt"`, `"shirt"`, `"top"`, `"jacket"`, `"suit"`, `"jeans"`, `"trousers"`, `"shorts"`, `"leggings"`, `"skirt"`, `"dress"`]
- `country` (string, nullable): The shopper's market, a two-letter ISO 3166 code. Brands' size systems follow it (a UK dress size in GB).

### Examples

#### Everyday menswear

A t-shirt and jeans in menswear, for the US.

```json
{
  "department": "mens",
  "garments": [
    "tshirt",
    "jeans"
  ],
  "country": "US"
}
```

#### A suit for a wedding

A suit jacket and trousers in menswear.

```json
{
  "department": "mens",
  "garments": [
    "suit",
    "trousers"
  ]
}
```

## Responses

### 200

The card.

Content type `application/json`:

- `card` (ShopperSizesCard, required): The sizes card, as the Stylor chat widget shows it.
  - `cardId` (string, required): A new id per card.
  - `title` (string, required): The card's question, "Which brands do you wear?".
  - `department` (string, nullable, required): Whose sizes. `null`, the card asks womenswear or menswear first. [one of `"womens"`, `"mens"`, `null`]
  - `kind` (null, required): Always `null` here (in a chat, the kind of piece the agent asked about).
  - `garments` (array<object>, required): The rows, one per garment, in order.
    - `value` (string): The garment
    - `label` (string): What the row says ("T-shirt", "Suit jacket").
    - `part` (string) [one of `"top"`, `"bottom"`, `"one-piece"`]
    - `kind` (string): The `kind` a size in this row is kept as.
  - `kinds` (array<object>, required): Every kind a size can be for, with its label.
    - `value` (string)
    - `label` (string)
  - `fits` (array<object>, required): How a size fits them, as the card asks it (`tight`, `right`, `loose`).
    - `value` (string)
    - `label` (string)
  - `brands` (array<object>, required): Every brand the card offers, popular ones first, then by name.
    - `slug` (string, required): The brand's id; its logo is `/brand-logos/<slug>.png` when `logo` is `true`.
    - `name` (string, required): The name to show.
    - `aliases` (array<string>, required): Other names to match a search on, lowercase with no spaces or punctuation.
    - `logo` (boolean): Present and `true` when a logo is held.
    - `popular` (boolean): Present and `true` for the brands to show before anything is searched.
    - `sizes` (object, required): Per `<department>:<garment>`, the size systems this brand offers for it, each `{ id, name, chips }` (`id` letters, numbers or tailored). The first is the usual one.
    - `likely` (object): Per `<department>:<top>><bottom>`, each top size mapped to the bottom sizes that usually go with it at this brand.
  - `logos` (boolean, required): Whether to show brand logos. Always `true` here.
  - `saved` (array<object>, required): The sizes the profile keeps, to show already picked, in the card's words. Empty without a token.
    - `brand` (string): The brand's `name` on the card when known
    - `kind` (string, nullable)
    - `label` (string)
    - `fit` (string) [one of `"tight"`, `"right"`, `"loose"`]

Example:

```json
{
  "card": {
    "cardId": "1d7ed9f7-290b-45eb-8ee1-49a010ef61ef",
    "title": "Which brands do you wear?",
    "department": "mens",
    "kind": null,
    "garments": [
      {
        "value": "tshirt",
        "label": "T-shirt",
        "part": "top",
        "kind": "tops"
      },
      {
        "value": "jeans",
        "label": "Jeans",
        "part": "bottom",
        "kind": "jeans"
      }
    ],
    "kinds": [
      {
        "value": "tops",
        "label": "Tops"
      },
      {
        "value": "jeans",
        "label": "Jeans"
      },
      {
        "value": "trousers",
        "label": "Trousers"
      },
      {
        "value": "dresses",
        "label": "Dresses"
      },
      {
        "value": "outerwear",
        "label": "Jackets"
      },
      {
        "value": "suits",
        "label": "Suit jackets"
      }
    ],
    "fits": [
      {
        "value": "tight",
        "label": "Tight"
      },
      {
        "value": "right",
        "label": "Right"
      },
      {
        "value": "loose",
        "label": "Loose"
      }
    ],
    "brands": [
      {
        "slug": "uniqlo",
        "name": "Uniqlo",
        "aliases": [],
        "logo": true,
        "sizes": {
          "mens:tshirt": [
            {
              "id": "letters",
              "name": "Letters",
              "chips": [
                "XXS",
                "XS",
                "S",
                "M",
                "L",
                "XL",
                "XXL",
                "3XL"
              ]
            }
          ],
          "mens:jeans": [
            {
              "id": "numbers",
              "name": "Waist",
              "chips": [
                "27",
                "28",
                "29",
                "30",
                "31",
                "32",
                "33",
                "34",
                "35",
                "36",
                "38",
                "40"
              ]
            }
          ]
        },
        "likely": {
          "mens:tshirt>jeans": {
            "S": [
              "27",
              "28",
              "29"
            ],
            "M": [
              "30",
              "31",
              "32"
            ],
            "L": [
              "34",
              "35",
              "36"
            ]
          }
        },
        "popular": true
      },
      {
        "slug": "levis",
        "name": "Levi's",
        "aliases": [
          "levi",
          "levistrauss",
          "levis501"
        ],
        "logo": true,
        "sizes": {
          "mens:tshirt": [
            {
              "id": "letters",
              "name": "Letters",
              "chips": [
                "XS",
                "S",
                "M",
                "L",
                "XL",
                "XXL",
                "3XL",
                "4XL",
                "5XL"
              ]
            }
          ],
          "mens:jeans": [
            {
              "id": "numbers",
              "name": "Waist",
              "chips": [
                "28",
                "29",
                "30",
                "31",
                "32",
                "33",
                "34",
                "36",
                "38",
                "40"
              ]
            }
          ]
        },
        "likely": {
          "mens:tshirt>jeans": {
            "S": [
              "28",
              "29",
              "30"
            ],
            "M": [
              "31",
              "32",
              "33"
            ],
            "L": [
              "34",
              "35",
              "36"
            ]
          }
        },
        "popular": true
      }
    ],
    "logos": true,
    "saved": [
      {
        "brand": "Uniqlo",
        "kind": "tops",
        "label": "M",
        "fit": "tight"
      },
      {
        "brand": "Levi's",
        "kind": "jeans",
        "label": "32",
        "fit": "loose"
      }
    ]
  }
}
```

## 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. Leave it out for a card with nothing saved. |
| 422 | department must be womens or mens. | `department` is another value. |
| 422 | garments must be an array. | `garments` was sent (non-null) but is not an array. |
| 422 | garments must not be empty; leave it out for the default. | `garments` is an empty array. |
| 422 | garments can hold at most 3. | `garments` holds more than 3 entries. |
| 422 | Each garment must be one of tshirt, shirt, top, jacket, suit, jeans, trousers, shorts, leggings, skirt, dress. | An entry of `garments` is another value. |
| 422 | country must be a two-letter ISO 3166 country code, such as 'US'. | `country` was sent (non-null) but is not two letters. |
| 422 | Validation failed. | More than one field is invalid. `details` lists each, by path.Body: `{"error":"Validation failed.","details":[{"path":"department","message":"department must be womens or mens."},{"path":"garments","message":"Each garment must be one of tshirt, shirt, top, jacket, suit, jeans, trousers, shorts, leggings, skirt, dress."}]}` |

### 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. |
