# REST API

The REST API is the way in for your own code. MCP is the way in for an agent: see [Connect an agent](/docs/connect-an-agent.md). Both reach the same tools, balance and calls.

Every request and response is JSON. The base URL is `https://api.voxoria.ai`, and every path starts with the version, `/v1`.

## Authentication

Send an API key as a bearer token in the `Authorization` header. Create keys in the console: see [Account and API key](/docs/get-started.md). The REST API takes API keys only, not OAuth tokens.

A missing, wrong or revoked key answers 401 with `INVALID_AUTH_TOKEN`. `GET /v1/tools` and `GET /v1/tools/{tool}` also answer without a key.

A key's scope is All endpoints or Read only. A read-only key is refused with 403 and `NOT_ALLOWED` by the two endpoints that change something: calling a tool and labelling a call. It can still quote a call.

A test key (`vox_test_...`) cannot call tools yet: it answers 403 with `TEST_KEY_NOT_ALLOWED`. Use a live key (`vox_live_...`).

```bash
curl https://api.voxoria.ai/v1/ping \
  -H "Authorization: Bearer $VOXORIA_API_KEY"
```

## Requests and responses

Send a body as JSON with `Content-Type: application/json`. Field names are `snake_case`.

Every field of a response is always present. A field marked nullable can be `null`. New fields may be added to a response, so ignore those you do not know.

Money is a decimal string in US dollars, such as `"0.0214"`, never a float. Times are ISO 8601 in UTC.

The API calls a workspace an organization. They are the same thing.

## Errors

A request that fails answers with an HTTP error status and a JSON body with `detail` and `code`. Branch on `code`, not on `detail`, which may change.

An invalid request adds `errors`, one per problem. A call that ran and failed adds `call_id`. Each endpoint's page lists the statuses it answers with. [The call response](/docs/call-response.md#errors) has the fields of each error body.

Each `code` an endpoint can answer with, and the status it usually comes with.

| Code | Status | Meaning |
| --- | --- | --- |
| `INVALID_AUTH_TOKEN` | 401 | The API key is missing, wrong or revoked. |
| `INSUFFICIENT_BALANCE` | 402 | Your available balance cannot cover the call. |
| `NOT_ALLOWED` | 403 | The key's scope does not permit the endpoint, or the account needs a subscription. |
| `TEST_KEY_NOT_ALLOWED` | 403 | Test keys cannot call tools yet. Use a live key. |
| `MODEL_NOT_FOUND` | 404 | The tool or call does not exist. |
| `TOOL_NOT_CALLABLE` | 409 | The tool cannot be called yet. |
| `VALIDATION` | 422 | The request is not valid. `errors` lists each problem. |
| `NO_ELIGIBLE_PROVIDER` | 422 | No provider can serve the request as sent. |
| `IDEMPOTENCY_KEY_REUSED` | 422 | The idempotency key was already used with a different request. |
| `RATE_LIMITED` | 429 | Too many requests. Retry after `Retry-After` seconds. |
| `PROVIDER_RATE_LIMITED` | 429 | Every provider that could serve the call refused it for its own rate limit. Not charged. |
| `UNKNOWN` | 500 | Something went wrong on our side. |
| `TOOL_CALL_FAILED` | 502 | No provider served the call. Not charged, unless its provider billed the failure. |
| `TOOL_UNAVAILABLE` | 503 | The tool or the call cannot be served right now. Not charged. Retry shortly. |
| `CALL_RESULT_DEFERRED` | 503 | The call ran, but its result could not be returned now. Read it with `GET /v1/calls/{call_id}`. |
| `TOOL_CALL_TIMED_OUT` | 504 | The call ran out of time. Not charged. |

## Pagination

`GET /v1/calls` returns one page at a time, newest first. Set the page size with `limit`: 50 by default, 1 to 100.

A page has `items`, `has_more` and `next_cursor`. Pass `next_cursor` as `cursor` to read the next page, with the same filters. Stop when `has_more` is `false`. A cursor is opaque: do not build or change one.

`GET /v1/tools` returns the whole list in one response.

## Rate limits

With a key: 600 requests a minute per API key, REST and MCP together. Without a key: 60 requests a minute per IP address.

Over the limit, a request answers 429 with `RATE_LIMITED` and a `Retry-After` header: the seconds until the limit resets.

Providers have rate limits of their own. When every provider that could serve a call refuses it for one, the call fails with 429 and `PROVIDER_RATE_LIMITED`. It is not charged. Retry after the `Retry-After` header's seconds.

## Retries

Reads are safe to retry. A call is not: two requests make two calls, and two charges. Send an `Idempotency-Key` header to retry a call safely. See [Call a tool](/docs/api/create-call.md) and [Retry a call safely](/docs/connect-an-agent.md#retry-a-call-safely).

## Versioning

The version is in the path: `/v1`. Each tool's input and output has a contract version of its own, listed on its page under [Tools](/docs/tools.md).

## Endpoints

Every endpoint of the REST API.

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/ping`](/docs/api/check-key.md) | Check a key |
| [`GET /v1/tools`](/docs/api/list-tools.md) | List tools |
| [`GET /v1/tools/{tool}`](/docs/api/get-tool.md) | Get a tool |
| [`POST /v1/tools/{tool}/quote`](/docs/api/quote-call.md) | Quote a call |
| [`POST /v1/tools/{tool}/calls`](/docs/api/create-call.md) | Call a tool |
| [`GET /v1/calls`](/docs/api/list-calls.md) | List calls |
| [`GET /v1/calls/{call_id}`](/docs/api/get-call.md) | Get a call |
| [`PATCH /v1/calls/{call_id}`](/docs/api/label-call.md) | Label a call |
| [`GET /v1/balance`](/docs/api/get-balance.md) | Get the balance |
| [`GET /v1/usage`](/docs/api/get-usage.md) | Get usage |
