> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orderprotection.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Analytics API

> Read-only access to your store's order and claim analytics — raw tables and pre-computed metrics.

The Analytics API gives you programmatic, read-only access to your store's analytics: raw data tables (orders, claims) and pre-computed metrics (attach rate, claim rate, and more). Use it to feed OrderProtection data into your own dashboards, BI tools, or reports.

## Authentication

Every request needs a Bearer token carrying the `analytics:read` scope. Two token types work:

* A **[Personal Access Token](/developer/personal-access-tokens)** (`op_pat_`) — for accessing your own account's stores.
* An **[OAuth access token](/developer/authentication)** (`op_at_`) — for an app a merchant installed and granted `analytics:read`.

```bash theme={null}
curl -X GET "https://api.production.orderprotection.com/v1/analytics/metrics/order_attach_rate/timeseries?granularity=month" \
  -H "Authorization: Bearer op_pat_8508b7432c45f79827460fea_ajhnyH..."
```

<Warning>
  A token without the `analytics:read` scope receives `403 Forbidden`. Requesting a store outside your token's authorized set receives `403` with code `STORE_OUT_OF_SCOPE`.
</Warning>

### Store scoping

Results are always limited to your account and the stores your token is authorized for. By default an endpoint returns data for **all** of the token's authorized stores. Pass `store_ids` to narrow to specific stores:

```
?store_ids=store_abc123&store_ids=store_def456
```

The `store_ids` you receive back in the response `meta` reflect the exact scope that was applied.

## Data freshness

Analytics tables are rebuilt on a batch schedule (roughly every 8 hours), so data is **not** real-time and can be **up to \~8 hours behind** your live store data. Every response includes a `data_freshness_at` timestamp in its `meta` block — the time the underlying table was last rebuilt by the batch job (not a per-row timestamp):

```json theme={null}
"meta": { "data_freshness_at": "2026-07-08T06:00:00.000Z" }
```

## Tables

Tables return raw, row-level records.

<Note>
  **No PII is exposed.** Each table has a fixed allowlist of safe columns; personally identifiable fields (customer name, email, etc.) are never included and cannot be requested. The `fields` parameter can only ever *narrow* to columns within that allowlist — asking for a field outside it is dropped, and a request for *only* disallowed fields returns `400 INVALID_FIELDS`.
</Note>

### List tables

```
GET /v1/analytics/tables
```

Returns the available tables, each with its selectable columns.

```json theme={null}
{
  "data": [
    { "key": "orders", "safeColumns": ["order_id", "store_id", "created_at", "..."], "sortColumns": ["created_at", "order_total"] },
    { "key": "claims", "safeColumns": ["claim_id", "order_id", "claim_state", "..."], "sortColumns": ["created_at", "updated_at"] }
  ],
  "meta": { "data_freshness_at": "2026-07-08T06:00:00.000Z" }
}
```

### Get rows

```
GET /v1/analytics/tables/{name}
```

| Parameter   | Description                                                                  |
| ----------- | ---------------------------------------------------------------------------- |
| `store_ids` | Repeatable. Restrict to specific stores within your scope.                   |
| `from`      | Start of the time window (ISO 8601). Defaults to 30 days ago.                |
| `to`        | End of the time window (ISO 8601). Defaults to now.                          |
| `fields`    | Comma-separated subset of the table's columns. Defaults to all safe columns. |
| `limit`     | Page size. Defaults to 1000, maximum 10000.                                  |
| `cursor`    | Opaque cursor from a previous response's `meta.next_cursor`.                 |

```json theme={null}
{
  "data": [
    { "order_id": "ord_...", "store_id": "store_abc123", "created_at": "2026-07-01T12:00:00.000Z", "order_total": 84.99, "currency": "USD" }
  ],
  "meta": {
    "data_freshness_at": "2026-07-08T06:00:00.000Z",
    "store_ids": ["store_abc123"],
    "next_cursor": "eyJ2Ijpbl...",
    "has_more": true
  }
}
```

To page through results, resend the request with `cursor` set to `meta.next_cursor` until `has_more` is `false`.

#### Available tables

| Table       | Columns                                                                                                                                                                                                  |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `orders`    | `order_id`, `store_id`, `account_id`, `policy_id`, `created_at`, `order_total`, `currency`, `platform`, `source_order_number`                                                                            |
| `claims`    | `claim_id`, `order_id`, `store_id`, `account_id`, `claim_type`, `claim_state`, `claim_category`, `created_at`, `updated_at`                                                                              |
| `addresses` | `order_id`, `claim_id`, `customer_id`, `store_id`, `account_id`, `city`, `state`, `state_abr`, `zip_code`, `country`, `country_abr`, `latitude`, `longitude`, `address_type`, `order_date`, `claim_date` |

<Note>
  **`addresses` is geo-only.** Customer identity fields — name, email, phone, and street lines (`address1`/`address2`) — are excluded and cannot be requested. Coordinates are **rounded to \~1.1 km** (2 decimal places), so `latitude`/`longitude` support regional mapping but never pinpoint an individual address. Its time column is `order_date`.
</Note>

## Metrics

Metrics are pre-defined aggregations computed at query time over the batch-refreshed tables. Query them as a time series or fetch the latest single value.

### List metrics

```
GET /v1/analytics/metrics
```

```json theme={null}
{
  "data": [
    { "key": "order_attach_rate", "description": "Share of orders that carry an Order Protection policy.", "granularitySupport": ["day", "week", "month", "quarter", "year"] }
  ],
  "meta": { "data_freshness_at": "2026-07-08T06:00:00.000Z" }
}
```

### Time series

```
GET /v1/analytics/metrics/{key}/timeseries
```

| Parameter     | Description                                                                    |
| ------------- | ------------------------------------------------------------------------------ |
| `store_ids`   | Repeatable. Restrict to specific stores within your scope.                     |
| `from`        | Start of the window (ISO 8601). Defaults to 30 days ago.                       |
| `to`          | End of the window (ISO 8601). Defaults to now. Windows are capped at 366 days. |
| `granularity` | Bucket size: `day`, `week`, `month`, `quarter`, or `year`.                     |
| `group_by`    | Set to `store_id` to return one series per store instead of an aggregate.      |

```json theme={null}
{
  "data": [
    { "bucket": "2026-05-01T00:00:00.000Z", "value": 0.3712 },
    { "bucket": "2026-06-01T00:00:00.000Z", "value": 0.3302 }
  ],
  "meta": {
    "data_freshness_at": "2026-07-08T06:00:00.000Z",
    "store_ids": ["store_abc123"],
    "granularity": "month"
  }
}
```

When `group_by=store_id`, each data point also includes a `store_id` field.

### Latest value

```
GET /v1/analytics/metrics/{key}
```

Returns the single most recent value for the metric.

```json theme={null}
{
  "data": { "key": "order_attach_rate", "value": 0.3302 },
  "meta": {
    "data_freshness_at": "2026-07-08T06:00:00.000Z",
    "store_ids": ["store_abc123"],
    "from": "2026-06-08T00:00:00.000Z",
    "to": "2026-07-08T00:00:00.000Z"
  }
}
```

#### Available metrics

| Metric                  | Description                                            |
| ----------------------- | ------------------------------------------------------ |
| `order_attach_rate`     | Share of orders that carry an Order Protection policy. |
| `protected_order_count` | Count of protected orders (distinct policies).         |
| `claim_count`           | Count of claims filed.                                 |
| `claim_rate`            | Claims per protected order, bucketed by granularity.   |

## Limits

| Limit                   | Value      |
| ----------------------- | ---------- |
| Default time window     | 30 days    |
| Maximum metric window   | 366 days   |
| Default table page size | 1000 rows  |
| Maximum table page size | 10000 rows |

<Tip>
  For pulls larger than these limits, page through tables with the `cursor` parameter, or narrow your `from`/`to` window and combine the results.
</Tip>
