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

# REST API introduction

> Manage TokenChange from your own code: authenticate with an API token, choose scopes, and handle pagination, rate limits, idempotency and errors.

The REST API lets you manage your TokenChange account from code: browse the catalog, manage inference keys, read usage and billing, and run your provider endpoints. To call models, use the [inference API](https://docs.tokenchange.io/inference/overview.md) instead.

## Base URL

```text theme={null}
https://api.tokenchange.io
```

All routes are under `/v1` and use JSON. The OpenAPI specification is available at `https://api.tokenchange.io/v1/openapi.json`.

## Authentication

Authenticate with an API token as a Bearer token:

```bash theme={null}
curl https://api.tokenchange.io/v1/balance \
  -H "Authorization: Bearer $TOKENCHANGE_API_TOKEN"
```

API tokens start with `tcg_`. They can't make inference requests, and inference keys can't call the REST API.

### Create an API token

**1. Open API tokens**

In the dashboard, open **Account** > **API tokens**.

**2. Name the token and choose scopes**

Use a name that says where the token runs, such as `deploy pipeline`. Pick only the [scopes](#scopes) it needs, and optionally an expiry.

**3. Copy the token**

The token is shown once. Store it in a secret manager.

From the same page you can **Edit** a token's name, scopes and expiry, **Rotate** it to get a new secret and invalidate the old one, or **Revoke** it. You can have up to 25 active tokens. API tokens are managed only in the dashboard, not through the REST API.

### Scopes

Each token carries one or more scopes. A request to a route outside the token's scopes fails with `403 insufficient_scope`. Write scopes don't include the matching read scope, so add both if you need both.

| Scope | Grants |
| - | - |
| `models:read` | Browse catalog models and endpoints |
| `keys:read` | List and read inference keys, never their secrets |
| `keys:write` | Create, rotate and revoke inference keys |
| `usage:read` | Read usage summaries and series |
| `billing:read` | Read your credit balance and transactions |
| `endpoints:read` | Read your provider account, endpoints, validations and earnings |
| `endpoints:write` | Create, update, pause, resume and delete your provider endpoints |

Each route's required scope is listed on its reference page.

## Units

Credit amounts are integers in microcredits, in fields ending in `_micro`. One credit is 1,000,000 microcredits, and one credit is worth \$0.01. Prices are decimal strings in USD per million tokens.

## Pagination

List routes return a page of results and a cursor:

```json theme={null}
{
  "data": [],
  "next_cursor": "<cursor>"
}
```

Pass `next_cursor` as the `cursor` query parameter to get the next page. `limit` sets the page size, from 1 to 100 (default 20). When there are no more results, `next_cursor` is `null`.

## Rate limits

Requests are rate-limited per token and per account in one-minute windows. Every authenticated response includes the limit that's closest to running out:

| Header | Meaning |
| - | - |
| `X-RateLimit-Limit` | Requests allowed in the window |
| `X-RateLimit-Remaining` | Requests left in the window |
| `X-RateLimit-Reset` | Seconds until the window resets |

Over the limit, you get `429 rate_limited` with a `Retry-After` header in seconds.

## Idempotency

`POST` routes accept an optional `Idempotency-Key` header of 1 to 255 printable ASCII characters. Send a unique value, such as a UUID, to retry a request safely:

* Retrying with the same key and the same request returns the original response with `Idempotent-Replayed: true`, for 24 hours.
* Reusing a key with a different request fails with `422 idempotency_key_reused`.
* Retrying while the first request is still running fails with `409 idempotency_in_progress`.
* Responses that contain a secret, such as a new inference key, are never stored. Replaying one fails with `409 idempotency_secret_unavailable`, and `resource_id` holds the ID of the key that was created.

Failed requests don't keep their key, so you can retry them with the same key.

## Request IDs

Every response has an `X-Request-Id` header. Send your own `X-Request-Id` (up to 128 letters, digits, `-`, `_` or `.`) to use it instead. Include it when you contact support.

## Errors

Errors share one format:

```json theme={null}
{
  "error": {
    "type": "permission_error",
    "code": "insufficient_scope",
    "message": "This token is missing the 'keys:write' scope"
  }
}
```

| Status | `code` | Meaning |
| - | - | - |
| `400` | `invalid_request` | The request is invalid. Unknown query and body fields are rejected. |
| `401` | `invalid_token` | The token is missing, malformed, unknown, revoked or expired |
| `403` | `insufficient_scope` | The token lacks the route's scope |
| `403` | `account_suspended` | Your account is suspended |
| `403` | `permission_denied` | The action isn't allowed |
| `404` | `not_found` | The resource doesn't exist or isn't yours |
| `409` | `conflict` | The resource's current state doesn't allow the action, for example pausing an endpoint that's awaiting approval |
| `409` | `idempotency_in_progress`, `idempotency_secret_unavailable` | See [Idempotency](#idempotency) |
| `413` | `payload_too_large` | The request body is larger than 64 KiB |
| `415` | `unsupported_media_type` | The body isn't sent as `application/json` |
| `422` | `idempotency_key_reused` | See [Idempotency](#idempotency) |
| `429` | `rate_limited` | Over the rate limit. Wait for `Retry-After`. |
| `429` | `resource_exhausted` | An account limit was reached |
| `500` | `internal_error` | Internal error. Retry with backoff. |
| `503` | `service_unavailable` | Temporarily unavailable. Retry with backoff. |
| `504` | `timeout` | The request took longer than 30 seconds |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.