## Base URL

```text
https://api.stylumia.com
```

TLS only — there is no unencrypted variant to fall back to. Every request and
response body is JSON.

## The envelope

Every `/v1` success response is shaped the same way, regardless of endpoint:

```json
{
  "data": [ "..." ],
  "pagination": { "limit": 100, "total_matching": 2361, "next_cursor": "...", "has_more": true },
  "meta": {
    "request_id": "req_...",
    "resolved": { "retailer": null, "category": "power_tools", "department": null, "rcd": null, "start_date": null, "end_date": null },
    "coverage": { "combinations": 1, "matching_rows": 6, "corpus_rows": 2361, "earliest_date": "2026-06-14", "latest_date": "2026-08-14" }
  }
}
```

| Member | Meaning |
|---|---|
| `data` | [The rows you asked for](/docs/data-dictionary) — an array on every endpoint here. |
| `pagination.limit`, `pagination.total_matching` | Only present on `/v1/catalog` — the page size and the size of the whole filtered set. |
| `pagination.next_cursor` | Opaque cursor for the next page, or `null` if there isn't one. |
| `pagination.has_more` | Whether another page exists. |
| `meta.request_id` | This request's id — also echoed in the `X-Request-Id` header. |
| `meta.resolved` | The six filters the server actually used — `retailer`, `category`, `department`, `rcd`, `start_date`, `end_date` — including ones you didn't send. |
| `meta.coverage` | How much exists for those filters — rides on every response, not just `/v1/coverage`. See [Coverage](/docs/objects/reference-data). |

## `meta.resolved`

If you call `/v1/catalog?retailer=home_depot` and omit every other filter,
`meta.resolved` tells you exactly what the server used:
`{"retailer": "home_depot", "category": null, "department": null, "rcd":
null, "start_date": null, "end_date": null}`. You never have to guess which
filters actually applied. `limit` isn't part of `meta.resolved` — it lives
in `pagination.limit` on `/v1/catalog`, where a silently-applied default of
100 can otherwise cost you a page of data you didn't mean to skip. See
[Pagination](/docs/pagination).

## `meta.coverage`

Every response — `/v1/sample`, `/v1/catalog`, and `/v1/coverage` alike —
carries a `coverage` block: how many RCD combinations your filters matched,
how many rows matched across them, how many rows those combinations hold in
total, and the date range spanned. One request answers both "what did I
get" and "how much exists" — you don't need a second call to `/v1/coverage`
just to see the total. See [Coverage & RCD combinations](/docs/objects/reference-data).

## `request_id`

Every response — success or [`problem+json`](/docs/error-handling) error —
carries a `request_id`, both in the body and in the `X-Request-Id` response
header. It's derived server-side from a SHA-256 digest of the caller, the
request, and the timestamp — you never supply one, and there's no
request-id header to send. Quote the value you're given when you write to
[Support](/docs/support); it's the fastest way for us to find your exact
request server-side.

`request_id` is safe to log and safe to paste into a support ticket. It
identifies a request, not a secret.

## What's safe to log

| Safe to log | Not safe to log |
|---|---|
| `request_id` | The full API key |
| The key's name, from the console | The key itself, in any form |
| Endpoint path and query parameters | — |

Naming your keys is precisely what lets you log and grep for "which key
made this call" without the log itself becoming a credential. See
[Credential security](/docs/key-security) for the rest of the credential-handling rules.
