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

# Conventions

> How the ClosedLoop AI REST API behaves across every endpoint: authentication, pagination, error shapes, rate limits, and timestamp formats.

## Shared behavior

Every `/v1` endpoint follows the same rules, so what you learn on one endpoint applies to all of them. The API is **read-only**: every endpoint is a `GET`, and nothing is mutated. Feedback is ingested through your connected [integrations](/docs/integrations/data-sources), not this API.

## Authentication

Send your key on every request as an `X-API-Key` header:

```bash theme={null}
curl https://api.closedloop.sh/v1/themes \
  -H "X-API-Key: clai_live_xxxxxxxxxxxxxxxxxxxx"
```

Keys are **team-scoped** (every response is limited to your team's data) and **region-scoped** (a key issued in the EU only works against `https://eu.api.closedloop.sh/v1`). Create keys in **Settings, API Keys** in the app. See [API keys](/docs/account/api-keys) for the full lifecycle.

## Pagination

List endpoints accept `limit` and `offset`:

| Parameter | Default | Meaning                   |
| --------- | ------- | ------------------------- |
| `limit`   | 50      | Page size (max 200)       |
| `offset`  | 0       | Number of records to skip |

Responses wrap the page in `data` and include a `pagination` object whose `total` is the **full-set count** of matching records, not the size of the page:

```json theme={null}
{
  "data": ["..."],
  "pagination": { "total": 508, "limit": 50, "offset": 0 }
}
```

Page through a result set by increasing `offset` until you have read `total` records.

## Filtering

Filters are query parameters, and multiple filters combine with AND semantics:

* **Free-text search**: most list endpoints accept `q`, a free-text search over titles and content.
* **Date ranges**: `date_from` and `date_to` are inclusive ISO 8601 dates.
* **Resource-specific filters**: each endpoint documents its own. For example, insights filter by `category`, `severity`, and `customer_id`; themes accept `status` and `sort`; features accept `theme_id`; context accepts `type`.

Call `/facets` to discover which filter values exist in your dataset (categories, severities, sources) along with their counts.

## Errors

Non-2xx responses return a JSON body with a user-safe message, a stable code, and an optional hint. Internal details are never exposed.

```json theme={null}
{
  "error": "Invalid or inactive API key.",
  "code": "INVALID_API_KEY",
  "hint": "Create a key in Settings, API Keys."
}
```

| Status | Code              | Meaning                                 |
| ------ | ----------------- | --------------------------------------- |
| `401`  | `INVALID_API_KEY` | Missing or invalid API key              |
| `404`  | `NOT_FOUND`       | Resource not found, or not in your team |
| `429`  | `RATE_LIMITED`    | Rate limit exceeded                     |

## Rate limits

Limits are enforced per API key, by plan:

| Plan          | Requests per hour |
| ------------- | ----------------- |
| Free          | 100               |
| Pay as you go | 500               |
| Enterprise    | 2,000             |

Exceeding the limit returns `429` with `X-RateLimit-*` headers describing the window. Back off and retry after the window resets.

## Timestamps

All timestamps are ISO 8601 in UTC, for example `2026-05-14T10:30:00Z`. Date filters (`date_from`, `date_to`) accept ISO 8601 dates and are inclusive on both ends.

## Linking insights and context

Insight and context records carry the same resolved `customer_id`, so you can line up an insight with the strategic context (churn, competitor, satisfaction) for the same customer. Filter either list by `customer_id`.

New to the API? Start with the [API reference introduction](/docs/api-reference/introduction).
