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

# Request logs and usage stream

> Read past requests, fetch a captured payload, page through history with before_id, and stream live usage over SSE as requests complete on the gateway.

The Impossibl AI API keeps one **request log** row for every gateway request,
success or failure, unlike the usage ledger which only records billed successes.
Rows are narrow and hot; the heavy request/response bodies live in a separate
payload fetched only on the detail view.

Three endpoints read this data:

* **`GET /v1/requests`** — recent rows, newest first, with filters
* **`GET /v1/requests/{id}`** — one row plus its captured, redacted payload
* **`GET /v1/usage/stream`** — a live SSE feed of rows as they settle

Authenticate with your gateway API key (`Authorization: Bearer imp-rt-...`, or the
`x-api-key` header). The dashboard calls the same endpoints with a WorkOS session
token. See [Authentication](/docs/authentication) for how keys are minted.

## List requests

Returns request log rows for your account, `ts` descending.

```bash theme={null}
curl "https://api.impossibl.com/v1/requests?limit=50" \
  -H "Authorization: Bearer imp-rt-..."
```

```json Response (row abbreviated) theme={null}
{
  "data": [
    {
      "id": "req_...",
      "ts": "2026-07-21T14:03:11.204Z",
      "endpoint": "/v1/chat/completions",
      "modelRequested": "anthropic/claude-sonnet-4-5",
      "modelServed": "anthropic/claude-sonnet-4-5",
      "provider": "anthropic",
      "status": "ok",
      "httpStatus": 200,
      "streamed": false,
      "durationMs": 842,
      "ttftMs": 391,
      "inputTokens": 24,
      "outputTokens": 108,
      "costCredits": 612,
      "byok": false
    }
  ]
}
```

A failed request logs the same row shape with the token and cost fields `null`,
an `errorMessage`, and an `attempts` failover trail. Costs are in credits, where
1 credit = \$0.000001.

### Filters

| Query       | Type          | Behavior                                                                                                                                                                          |
| ----------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `limit`     | integer       | 1..200, default 50. Values above 200 clamp to 200. A non-integer or `< 1` returns `400`.                                                                                          |
| `since`     | ISO timestamp | Only rows at or after it (inclusive). A value `Date.parse` rejects returns `400`.                                                                                                 |
| `before_id` | request id    | Keyset "load older" cursor: only rows strictly older than this row. An id that does not resolve for your account returns an empty page (not `400`); an empty value returns `400`. |
| `status`    | enum          | One of `ok`, `provider_error`, `insufficient_balance`, `rate_limited`, `invalid_request`, `timeout`, `client_aborted`. An unknown value returns `400`.                            |
| `model`     | string        | Matches the requested **or** the served model id.                                                                                                                                 |

```bash Errors only, one model theme={null}
curl -G "https://api.impossibl.com/v1/requests" \
  --data-urlencode "status=provider_error" \
  --data-urlencode "model=anthropic/claude-sonnet-4-5" \
  -H "Authorization: Bearer imp-rt-..."
```

### Paging with `before_id`

Page backward with a keyset cursor: pass the id of the oldest row you already hold
as `before_id` to get the rows strictly older than it. The cursor keys off the
row's id, not its timestamp, so rows that share a millisecond are never skipped and
the boundary row does not come back:

```bash theme={null}
# First page (newest 50). Note the id of the last (oldest) row.
curl -G "https://api.impossibl.com/v1/requests" --data-urlencode "limit=50" \
  -H "Authorization: Bearer imp-rt-..."

# Next page: pass that id as `before_id` to get the rows just older than it.
curl -G "https://api.impossibl.com/v1/requests" \
  --data-urlencode "limit=50" \
  --data-urlencode "before_id=req_9f3c1e2a-0b4d-4c8e-9a1f-2d6b7c8e5a01" \
  -H "Authorization: Bearer imp-rt-..."
```

`since` and `before_id` compose. Keep a `since` filter on every page to bound how
far back paging runs: the window is at/after `since` and strictly older than the
oldest row you hold.

## One request with its payload

Fetch a single row plus the captured request and response bodies. An id that does
not exist, or belongs to another account, returns `404` (never leaks across
accounts).

```bash theme={null}
curl "https://api.impossibl.com/v1/requests/req_..." \
  -H "Authorization: Bearer imp-rt-..."
```

```json Response theme={null}
{
  "request": { "id": "req_...", "status": "ok", "endpoint": "/v1/chat/completions" },
  "payload": {
    "requestBody": {
      "model": "anthropic/claude-sonnet-4-5",
      "messages": [{ "role": "user", "content": "hello" }]
    },
    "responseBody": "hello! how can I help?",
    "requestHeaders": {
      "authorization": "[redacted]",
      "content-type": "application/json"
    },
    "truncated": false
  }
}
```

The `payload` is `null` if no bodies were captured for that request. What the
gateway captures is scrubbed before it is stored:

* **Sensitive headers are redacted.** `authorization`, `x-api-key`,
  `x-goog-api-key`, `cookie`, and `set-cookie` are stored as `[redacted]`.
* **Inline media is stripped.** `data:` URIs and large base64 blobs (images,
  files) are replaced with a `[media omitted (N chars)]` placeholder.
* **Oversized bodies are truncated.** Bodies are capped near 256 KB; when a body
  is shrunk to fit, `truncated` is `true`. The stored JSON is always valid.

## Live stream

`GET /v1/usage/stream` is a long-lived `text/event-stream`. On connect it sends
one `snapshot` event, then one `request` event per settled or failed request.
Pass `-N` to curl so it prints frames as they arrive.

```bash theme={null}
curl -N "https://api.impossibl.com/v1/usage/stream" \
  -H "Authorization: Bearer imp-rt-..."
```

Every frame is JSON with `v: 1`. **Payloads are never streamed**, only log rows.

```text Stream (abridged) theme={null}
event: snapshot
data: {"v":1,"requests":[ ...up to 20 rows... ],"balanceCredits":4938200,"monthToDate":{"tokens":184203,"spentCredits":61800,"requests":142},"cutoff":"2026-07-21T14:03:00.000Z"}

: hb

event: request
data: {"v":1,"request":{ ...one row... },"balanceCredits":4937588}
```

* **`snapshot`** — the last 20 rows, the current `balanceCredits`, the
  server-authoritative month-to-date totals (`tokens`, `spentCredits`, `requests`,
  ok-only), and `cutoff`, the read time the totals were computed at. Gate any
  streamed rows against `cutoff` so live ticks never re-add rows the snapshot
  already counted.
* **`request` (live)** — a newly settled or failed row plus `balanceCredits`, the
  account balance after that request.
* **`request` (buffered replay)** — a request that settled between subscribe and
  the snapshot read is replayed once right after the snapshot **without**
  `balanceCredits` (its balance could be staler than the snapshot's). The snapshot
  balance stands until the next live frame ticks it forward.
* **`: hb`** — a comment heartbeat about every 25s that keeps the connection under
  the load balancer's idle timeout. It carries no data.

All amounts (`balanceCredits`, `spentCredits`, a row's `costCredits`) are in
credits, where 1 credit = \$0.000001.
