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

# Conventions

> Errors, idempotency, and pagination — the HTTP-level conventions that apply to every endpoint.

The LumisReach v1 API follows a consistent shape across every endpoint. The conventions on this page apply everywhere, so you can write your client code once and reuse the patterns.

## Errors

Every error response uses the same envelope:

```json theme={null}
{
  "error": {
    "code": "validation_error",
    "message": "caller_id must be E.164 format",
    "fields": {
      "caller_id": "Expected +<country><number>"
    }
  }
}
```

### Error codes

| Code                     | HTTP status | Description                                                                   |
| ------------------------ | ----------- | ----------------------------------------------------------------------------- |
| `validation_error`       | 400         | Request body or parameters are invalid                                        |
| `unauthorized`           | 401         | Missing or invalid API key                                                    |
| `forbidden`              | 403         | API key does not have access to this resource                                 |
| `not_found`              | 404         | The requested resource does not exist                                         |
| `idempotency_conflict`   | 409         | A request with this `Idempotency-Key` already succeeded with a different body |
| `caller_id_not_verified` | 422         | The caller ID is not verified for your account                                |
| `internal_error`         | 500         | An unexpected server error occurred                                           |

When a request fails validation, the `fields` map contains a per-field explanation so you can surface the right error next to the right input.

***

## Idempotency

Every `POST`, `PUT`, `PATCH`, and `DELETE` endpoint supports the `Idempotency-Key` header. Set it to a unique value (a UUID is recommended) on your first request, and retry the same request with the same header to safely replay.

```bash theme={null}
curl -X POST https://api.lumisreach.com/v1/calls \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Idempotency-Key: 9b8cfa7a-1c2d-4a88-b3c0-1d2e3f4a5b6c" \
  -H "Content-Type: application/json" \
  -d '{"caller_id": "+14155551234", "destination": "+14155556789"}'
```

### Semantics

* **First request with a key.** We run the handler and persist the response under the key.
* **Repeat with the same key and same body.** We replay the saved response verbatim, including status code. The replay carries an `Idempotency-Replay: true` response header so you can tell.
* **Repeat with the same key but a different body.** We return `409 idempotency_conflict` with the original key's request id in `fields.conflicts_with`.

### TTL

Idempotency records live for **24 hours** after the first successful response. After the window, reusing the key is treated as a fresh request.

### Best practices

* Use a new key for each new logical operation. Reusing keys across different calls drops traffic.
* The key is scoped to the API key it was submitted under. Rotating keys resets the idempotency window.
* Idempotency only protects the LumisReach side. Don't rely on it for cross-process transactions (for example, a call placed plus a Stripe charge).

### When to set one

* Retrying a request after a network timeout — critical.
* Background jobs that may re-run after a worker crash — critical.
* Interactive UIs with a double-click-vulnerable button — nice to have.

Client SDKs generate idempotency keys automatically on mutations. Set yours manually only if you need to correlate retries across a longer window than the SDK's in-memory dedupe.

***

## Pagination

Every list endpoint returns the same envelope:

```json theme={null}
{
  "data": [ /* items */ ],
  "meta": {
    "cursor": "opaque-string",
    "total_results": 42,
    "page_size": 20
  }
}
```

Pass `?cursor=<meta.cursor>` on the next request to walk to the next page. `meta.cursor` is omitted on the last page. Cursors are opaque to clients — treat the value as a bag of bytes, do not parse it.

### Example

```bash theme={null}
# First page
curl "https://api.lumisreach.com/v1/calls?limit=50" \
  -H "Authorization: Bearer $KEY"

# Next page
curl "https://api.lumisreach.com/v1/calls?limit=50&cursor=eyJpZCI6MTIzfQ" \
  -H "Authorization: Bearer $KEY"
```

### Limits

* `limit` default is **20**, max **100**.
* Cursors are valid for **24 hours** from issuance. Requesting an expired cursor returns `400 validation_error` with `fields.cursor`.
* Ordering is **by creation time, newest first** for every list endpoint. Use `?order=asc` where supported to reverse.
