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

> ## Agent Instructions
> The OpenAPI document at /api-reference/openapi.yaml defines the target contract. Its x-reflections-availability field and each operation's availability notice identify supported test and live forms. Do not infer implementation from a schema or code sample. Live keys support account details, reads of the licenses the calling account holds as licensee or issued as talent (GET /v1/licenses?role=issuer), and GET /v1/generations?license=… for the state of outputs under such a license (summaries only: no prompt, reference, bytes or metadata). Live image checks, submissions and receipts are built but not activated: until activation, a live POST /v1/checks or /v1/generations returns 403 live_not_activated, which must not be retried. Discovery and JWKS are at the origin root (https://app.reflections.ai/.well-known/...), not under /v1.
> Three rules hold everywhere: a decision is never an error (deny and escalate are 200 responses, and a live check still evaluating is a 202 with status checking); generate nothing for a named talent without an allow; deliver nothing until the generation is allow.
> The sandbox accepts personal rk_test_ keys. Persist a separate Idempotency-Key and body for each logical /v1 POST; retry an unknown outcome with the same key and body. OAuth and Talent API MCP remain planned.

# Pagination

> Read lists page by page with cursors, and read the revocation and event feeds in order with after.

Every list endpoint returns one page at a time, newest first. Two lists are also feeds: the
revocations list and the events list can be read oldest first from a point you choose, so you can
process each item once.

## The list envelope

```json theme={null}
{
  "object": "list",
  "data": [
    { "id": "lic_k5zba2q8v1m3n7p4r6s9t0wxyh", "object": "license", "status": "active" },
    { "id": "lic_ba2q8v1m3n7p4r6s9t0wxyhk5z", "object": "license", "status": "active" }
  ],
  "has_more": true,
  "next_cursor": "bGljX2JhMnE4djFtM243…"
}
```

| Field | Meaning |
| - | - |
| `data` | The objects on this page. |
| `has_more` | Whether another page exists. |
| `next_cursor` | Pass it as `cursor` to read the next page. `null` on the last page. |

## Parameters

| Parameter | Meaning |
| - | - |
| `limit` | Objects per page, 1 to 100. Default 20. |
| `cursor` | The `next_cursor` from the previous page. Keep every other parameter the same. |
| `after` | Revocations and events only. Return items created after this ID, oldest first. |

Cursors are opaque. Do not build or parse them, and do not reuse one with different filters. Each
list's filters, such as `status`, `talent` or `created_after`, are on its reference page.

## Read every page

<CodeGroup>
  ```typescript TypeScript theme={null}
  async function* listAll(path: string, params: Record<string, string> = {}) {
    let cursor: string | null = null;
    do {
      const query = new URLSearchParams({ ...params, limit: "100", ...(cursor ? { cursor } : {}) });
      const page = await fetch(`https://app.reflections.ai/v1${path}?${query}`, {
        headers: {
          Authorization: `Bearer ${process.env.REFLECTIONS_KEY}`,
          "Reflections-Version": "2026-10-15",
        },
      }).then((r) => r.json());
      yield* page.data;
      cursor = page.has_more ? page.next_cursor : null;
    } while (cursor);
  }

  for await (const license of listAll("/licenses", { status: "active" })) {
    console.log(license.id, license.handle);
  }
  ```

  ```python Python theme={null}
  import os, requests

  def list_all(path, **params):
      headers = {
          "Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}",
          "Reflections-Version": "2026-10-15",
      }
      params = {**params, "limit": 100}
      while True:
          page = requests.get(f"https://app.reflections.ai/v1{path}", headers=headers, params=params).json()
          yield from page["data"]
          if not page["has_more"]:
              break
          params["cursor"] = page["next_cursor"]

  for license in list_all("/licenses", status="active"):
      print(license["id"], license["handle"])
  ```
</CodeGroup>

## Read a feed

The [revocation feed](/api-reference/revocations/list-revocations) and the
[events list](/api-reference/events/list-events) take `after`. Store the ID of the last item you
processed, and pass it as `after` on the next read. Items come oldest first. When `has_more` is
true, read again with `after` set to the last ID in `data`. Before your first read you have no ID:
[Handling revocations](/build/handling-revocations#read-the-feed) shows where to start.

<CodeGroup>
  ```typescript TypeScript theme={null}
  let after: string = await loadLastRevocationId(); // for example "rvk_s9t0wxyhk5zba2q8v1m3n7p4r6"

  async function pollRevocations() {
    let hasMore = true;
    while (hasMore) {
      const query = new URLSearchParams({ limit: "100", after });
      const res = await fetch(`https://app.reflections.ai/v1/revocations?${query}`, {
        headers: {
          Authorization: `Bearer ${process.env.REFLECTIONS_KEY}`,
          "Reflections-Version": "2026-10-15",
        },
      });
      if (!res.ok) throw new Error(`List revocations failed: ${res.status}`); // retry on the next poll
      const page = await res.json();
      for (const revocation of page.data) {
        await applyRevocation(revocation);
        after = revocation.id;
        await saveLastRevocationId(after);
      }
      hasMore = page.has_more;
    }
  }

  setInterval(pollRevocations, 30_000);
  ```

  ```python Python theme={null}
  import os, time, requests

  after = load_last_revocation_id()  # for example "rvk_s9t0wxyhk5zba2q8v1m3n7p4r6"

  def poll_revocations():
      global after
      while True:
          res = requests.get(
              "https://app.reflections.ai/v1/revocations",
              headers={
                  "Authorization": f"Bearer {os.environ['REFLECTIONS_KEY']}",
                  "Reflections-Version": "2026-10-15",
              },
              params={"limit": 100, "after": after},
          )
          res.raise_for_status()  # retry on the next poll
          page = res.json()
          for revocation in page["data"]:
              apply_revocation(revocation)
              after = revocation["id"]
              save_last_revocation_id(after)
          if not page["has_more"]:
              break

  while True:
      poll_revocations()
      time.sleep(30)
  ```
</CodeGroup>

If your platform caches decisions, read the revocation feed at least every 60 seconds. Webhooks are
the fast path; the feed is the source of truth. See
[Handling revocations](/build/handling-revocations).

The events list covers the last 30 days. To recover from a webhook outage, read it with `after`
set to the last event you processed. See [Webhooks](/api-reference/webhooks/overview#replay-missed-events).

## Related

<Columns cols={2}>
  <Card title="Introduction" icon="book" href="/api-reference/introduction">
    The objects and the conventions every endpoint shares.
  </Card>

  <Card title="Rate limits" icon="bolt" href="/api-reference/rate-limits">
    Limits per account, and the headers that report them.
  </Card>
</Columns>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.