> This page is for Voice AI Platform.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.hams.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.hams.ai/_mcp/server.

# API conventions

> Pagination, the error format, rate limits, caching and CORS, as they apply to every endpoint.

## Pagination

List endpoints take `offset` and `limit` query parameters and return the page in an envelope with
the total number of matching items:

```bash
curl "https://api.hams.ai/outbound-batch/batches?status=ACTIVE&offset=0&limit=100" \
  -H "X-Secret-Key: $HAMS_SECRET_KEY"
```

```json
{ "success": true, "data": [ … ], "count": 100, "total": 342, "limit": 100, "offset": 0 }
```

| Endpoint                                       | `limit` default |
| ---------------------------------------------- | --------------- |
| `GET /outbound-batch/batches`                  | 100             |
| `GET /outbound-batch/batches/{batch_id}/leads` | 100             |

`offset` defaults to `0`. Iterate by adding the page size to `offset` until it reaches `total`.

## Errors

Every error is a JSON object with a human-readable `detail` and a stable `error_code` you can branch
on:

```json
{ "detail": "Agent not found", "error_code": "AGENT_NOT_FOUND" }
```

| Status | Meaning                                                                                               |
| ------ | ----------------------------------------------------------------------------------------------------- |
| `400`  | The request is well-formed but cannot be carried out, for example an extension with no outbound agent |
| `401`  | No valid key in the request                                                                           |
| `403`  | The credential is valid but not allowed to do this                                                    |
| `404`  | The resource does not exist in this entity                                                            |
| `422`  | Validation failed; `detail` is a list of `{ "loc", "msg", "type" }` entries naming each field         |
| `429`  | Rate limited; wait for the number of seconds in the `Retry-After` header                              |
| `500`  | Something went wrong on our side; retry with backoff                                                  |

`error_code` is specific where it helps (`AGENT_NOT_FOUND`, `CALL_NOT_FOUND`, `CHAT_ALREADY_ENDED`,
`KEY_SCOPE_NOT_ALLOWED`, `RATE_LIMITED`, …) and falls back to a generic code for the status
otherwise. Validation messages are sanitised: they name the field and the rule, never an internal
provider.

## Rate limits

Limits are counted in a sliding window and answered with `429` and `Retry-After`:

| Scope                             | Endpoints                                                           | Limit                                    |
| --------------------------------- | ------------------------------------------------------------------- | ---------------------------------------- |
| Per public key                    | `/v1/ai-agents/{agent_id}/chat/init`, `…/stream`, `…/end`, `…/rate` | 20 requests per minute each              |
| Per entity, public traffic        | `…/chat/init` and `…/chat/stream` under `/v1/`                      | 500 requests per minute each             |
| Per entity, authenticated traffic | `/ai-agents/{agent_id}/chat/init` and `…/stream`                    | 500 requests per minute each             |
| Per entity, secret keys           | All secret-key requests                                             | A per-minute allowance set on the entity |

The per-key limits are keyed on the public key, so two pages using the same key share them.

## Caching

`GET /v1/ai-agents/{agent_id}/widget-config` returns an `ETag` and `Cache-Control` headers. Send
the tag back in `If-None-Match` to get `304 Not Modified` while the configuration is unchanged, so a
widget can check for changes on every page load without re-downloading it.

## CORS

| Key        | Allowed origins                                                            |
| ---------- | -------------------------------------------------------------------------- |
| Public key | Any origin; the key's allowed domains decide whether the request is served |
| Secret key | None; call from a server                                                   |

## Timestamps and identifiers

Timestamps such as `created`, `updated` and `scheduled_date` are ISO 8601 in UTC. Identifiers are
opaque strings; do not parse them.