# Analytics overview

`GET https://stylor.ai/api/v1/analytics/overview`

- Authentication: private API key, sent as `Authorization: Bearer sgpt-sk-…::…`
- Rate-limit resource: `api.v1.analytics.overview`
- Demo key: not accepted
- Web page: https://stylor.ai/guides/rest/v1/analytics-overview

The figures on the dashboard's analytics page for the last `days` days,
counted in the organization's reporting timezone: totals, the same
totals for the period before, the funnel from seeing the assistant to
adding to cart, a daily series and breakdowns.

## Freshness

The figures are read from daily rollups the server builds through the
day, not from raw events, so today's numbers trail by up to a few
minutes. `coverage` says how many of the days have been built; a day
not built yet appears in `series` with `missing: true`, which is not
the same as a day with no traffic.

## Example request

```bash
curl -X GET "https://stylor.ai/api/v1/analytics/overview" \
  -H "Authorization: Bearer $STYLOR_API_KEY"
```

## Query parameters

- `days` (integer): How many days, ending today. [default `30`; one of `7`, `30`, `90`]

## Responses

### 200

The overview.

Content type `application/json`:

- `timezone` (string, required): The timezone the days are counted in.
- `range` (integer, required): The number of days.
- `startDay` (string, required): The first day, `YYYY-MM-DD`.
- `endDay` (string, required): The last day, today, `YYYY-MM-DD`.
- `totals` (object, required): Totals for the range, grouped: `audience`, `engagement`, `commerce`, `catalogue`, `health` and `integrity`. A rate is `null` when what it divides by is zero.
- `previous` (object, required): The same totals for the equally long period just before, for comparison.
- `funnel` (array<object>, required): From seeing the assistant to Stylor adding to cart. `rate` is against the first step.
  - `key` (string): The event counted.
  - `label` (string): The step's name.
  - `count` (integer): How many.
  - `rate` (number, nullable): Count over the first step's count.
  - `tier` (integer): How strongly the step is verified; lower is stronger.
- `series` (array<object>, required): One entry per day, oldest first.
  - `day` (string): `YYYY-MM-DD`.
  - `missing` (boolean): The day has not been built yet; its counts are not zeros.
  - `sessions` (integer): Sessions.
  - `turns` (integer): Answers the assistant gave.
  - `searches` (integer): Searches.
  - `emptySearches` (integer): Searches that found nothing.
  - `cartAdds` (integer): Products Stylor added to cart.
  - `productClicks` (integer): Product clicks.
- `breakdowns` (object, required): Top values for `cartByArticleFamily`, `closedByReason`, `messagesByInput`, `requestsByRoute` and `searchesByDataset`.
- `coverage` (object, required): How much of the range has been built.
  - `daysExpected` (integer): Days in the range.
  - `daysBuilt` (integer): Days built so far.
  - `lastComputedAt` (string (date-time), nullable): When a day in the range was last built.

Example:

```json
{
  "timezone": "America/Denver",
  "range": 7,
  "startDay": "2026-09-28",
  "endDay": "2026-10-04",
  "totals": {
    "engagement": {
      "widgetLoads": 18240,
      "widgetOpens": 1312,
      "openRate": 0.0719,
      "messages": 2210
    }
  },
  "previous": {
    "engagement": {
      "widgetLoads": 16980,
      "widgetOpens": 1180,
      "openRate": 0.0695,
      "messages": 1984
    }
  },
  "funnel": [
    {
      "key": "widget.loaded",
      "label": "Saw the assistant",
      "count": 18240,
      "rate": 1,
      "tier": 1
    }
  ],
  "series": [
    {
      "day": "2026-09-28",
      "missing": false,
      "sessions": 2410,
      "turns": 301,
      "searches": 512,
      "emptySearches": 14,
      "cartAdds": 22,
      "productClicks": 188
    }
  ],
  "breakdowns": {
    "searchesByDataset": []
  },
  "coverage": {
    "daysExpected": 7,
    "daysBuilt": 7,
    "lastComputedAt": "2026-10-04T18:15:00.000Z"
  }
}
```

## 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 |
| --- | --- | --- |
| 422 | days must be one of 7, 30, 90. | `days` is something else. |

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