> ## Documentation Index
> Fetch the complete documentation index at: https://docs.revdesk.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 RevDesk 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>"
    }
  }
}
```

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. Call-failure errors additionally carry
a `category`, a generalized bucket you can show an end user.

See **[Errors](/api-reference/errors)** for the full list of codes and categories, including
every reason a call can fail to be placed or connect.

***

## 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.revdesk.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 RevDesk 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.

### With the SDK

[`@revdesk/sdk`](/api-reference/sdks) takes an `idempotencyKey` on any mutation and sends it as the
`Idempotency-Key` header. Use `generateIdempotencyKey()` or supply your own:

```ts theme={null}
import { generateIdempotencyKey } from "@revdesk/sdk";

await revdesk.sms.send(
  { from_number, to_number, body: "Hi!" },
  { idempotencyKey: generateIdempotencyKey() },
);
```

***

## 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.revdesk.com/v1/calls?limit=50" \
  -H "Authorization: Bearer $KEY"

# Next page
curl "https://api.revdesk.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.

### With the SDK

[`@revdesk/sdk`](/api-reference/sdks) handles cursors for you. `list()` returns one page; `listAll()`
is an async iterator that walks every page:

```ts theme={null}
for await (const number of revdesk.phoneNumbers.listAll()) {
  console.log(number.phone_number);
}
```

`listAll()` is available on `phoneNumbers`, `subEntities`, and `callerTrust.reputation.numbers`.
