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

# Personal Access Token

> Create personal access tokens with the permissions and expiry you choose, then use them to manage API keys, read your balance and payments, and reconcile per-request usage in USD.

# Personal Access Token

A personal access token is the credential for automating your BeatAPI account. You choose the permissions each token carries, so a script can do only what you allow: read your balance, manage API keys, list payments, or read account data such as what each task or text request cost. Model, task and workflow requests still use an API key.

## Personal access token or API key

|             | Personal access token                                                             | API key                                                |
| ----------- | --------------------------------------------------------------------------------- | ------------------------------------------------------ |
| Starts with | `nap_`                                                                            | `sk-`                                                  |
| Use it for  | Account automation, such as `GET /v1/account` and `GET /v1/account/usage/records` | Model, task and workflow requests                      |
| How many    | Up to 20 per account, each with its own name                                      | As many as you need                                    |
| Limits      | The permissions you choose; no model, IP or spending limits of its own            | Optional model, IP and spending limits per key         |
| Expiry      | Optional: 7, 30, 90 or 365 days, or never                                         | Optional expiry per key                                |
| Accepted by | Endpoints under `/v1/account` and `GET /v1/usage`                                 | Model, task and workflow endpoints and `GET /v1/usage` |

## Create and manage tokens

1. Sign in to the [Dashboard](https://beatapi.io/dashboard/security) and open **Account security**.
2. Choose **Create personal token**, enter a name, tick the permissions the token needs and pick an expiry: 7, 30 (the default), 90 or 365 days, or never.
3. Complete the verification step, then copy the token. The full token is shown only once; closing the window removes it from the page.
4. Store it as a secret where your automation runs, for example in `BEATAPI_PERSONAL_TOKEN`.

An account can hold up to 20 tokens. Give each one a name that says where it runs and grant only the permissions it needs. The token list shows every token's name, permissions, expiry and when it was last used.

You can rename a token at any time without verification. Changing a token's permissions and revoking a token both ask for verification. Edited permissions apply to the same token, so nothing needs redeploying. **Revoke** removes the token, and tools that use it lose access at once. Keep tokens on trusted servers: never put one in browser code, a mobile app or a shared repository.

A token created before multiple tokens were available keeps working with the permissions it already had.

## Permissions

Each token carries only the permissions you ticked. A call that needs a permission the token lacks is refused with `403 forbidden`, and the message names the missing permission.

| Permission       | What it allows                                                                 | Endpoints                                                                              |
| ---------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `usage:read`     | Read usage records and the usage summary                                       | `GET /v1/account/usage/records`, `GET /v1/account/usage/records/{id}`, `GET /v1/usage` |
| `api_key:read`   | List and read API keys; keys are masked                                        | `GET /v1/account/keys`, `GET /v1/account/keys/{id}`                                    |
| `api_key:write`  | Create API keys (the full key is returned once), edit, disable and delete them | `POST /v1/account/keys`, `PATCH /v1/account/keys/{id}`, `DELETE /v1/account/keys/{id}` |
| `api_key:reveal` | Read the full secret of an existing API key                                    | `POST /v1/account/keys/{id}/reveal`                                                    |
| `wallet:read`    | Read payment (top-up) history                                                  | `GET /v1/account/payments`                                                             |
| `profile:read`   | Read your profile, balance and notification settings                           | `GET /v1/account`, `GET /v1/account/notifications`                                     |
| `profile:write`  | Change notification settings                                                   | `PATCH /v1/account/notifications`                                                      |

A token cannot top up your balance, change sign-in or security settings, or manage other tokens; those stay in the Dashboard. Grant `api_key:reveal` only to tokens that really need to read key secrets.

## Endpoints it can call

Send the token as `Authorization: Bearer <token>`. Each endpoint needs the permission shown.

| Endpoint                             | Permission       | Purpose                                                            |
| ------------------------------------ | ---------------- | ------------------------------------------------------------------ |
| `GET /v1/account`                    | `profile:read`   | Account profile and balance in USD                                 |
| `GET /v1/account/notifications`      | `profile:read`   | Read balance notification settings                                 |
| `PATCH /v1/account/notifications`    | `profile:write`  | Change notification type, threshold or email                       |
| `GET /v1/account/payments`           | `wallet:read`    | List payments, newest first                                        |
| `GET /v1/account/keys`               | `api_key:read`   | List API keys, newest first                                        |
| `GET /v1/account/keys/{id}`          | `api_key:read`   | Read one API key                                                   |
| `POST /v1/account/keys`              | `api_key:write`  | Create an API key                                                  |
| `PATCH /v1/account/keys/{id}`        | `api_key:write`  | Edit, disable or re-enable an API key                              |
| `DELETE /v1/account/keys/{id}`       | `api_key:write`  | Delete an API key                                                  |
| `POST /v1/account/keys/{id}/reveal`  | `api_key:reveal` | Read an API key's full secret                                      |
| `GET /v1/account/usage/records`      | `usage:read`     | List your billed requests, newest first                            |
| `GET /v1/account/usage/records/{id}` | `usage:read`     | Read one record by task ID or request ID                           |
| `GET /v1/usage`                      | `usage:read`     | Account totals and breakdowns; see the [Quick Guide](/quick-guide) |

The `/v1/account` endpoints refuse an API key with `403 forbidden`; `GET /v1/usage` accepts either an API key or a token with `usage:read` and returns the same summary. Responses are wrapped in `data`. IDs are strings, times are Unix seconds and money is in USD. Lists return `object`, `data`, `has_more` and `next_cursor`, and take `limit` (1–100, default 20) and `cursor`. An unknown query parameter is refused with `400 bad_request`.

## Read your balance

Needs `profile:read`.

```bash
curl https://api.beatapi.io/v1/account \
  -H "Authorization: Bearer $BEATAPI_PERSONAL_TOKEN"
```

```json
{
  "data": {
    "object": "account",
    "id": "1042",
    "username": "ada",
    "display_name": "Ada",
    "email": "ada@example.com",
    "credit_balance": 42.5,
    "currency": "USD"
  }
}
```

## List API keys

Needs `api_key:read`. Keys are masked: only `key_prefix`, the first characters, is returned. Use `GET /v1/account/keys/{id}` to read one key.

```bash
curl -G https://api.beatapi.io/v1/account/keys \
  -H "Authorization: Bearer $BEATAPI_PERSONAL_TOKEN" \
  --data-urlencode "limit=20"
```

```json
{
  "data": {
    "object": "list",
    "data": [
      {
        "object": "api_key",
        "id": "1287",
        "title": "production",
        "key_prefix": "sk-Ab3d",
        "status": "active",
        "created_at": 1793200000,
        "last_used_at": 1793247312,
        "expires_at": null,
        "remaining_credits": null,
        "credits_used": 18.42,
        "models": [],
        "allowed_ips": []
      }
    ],
    "has_more": false
  }
}
```

## Create an API key

Needs `api_key:write`. Only `title` is required. `expires_at` (Unix seconds), `remaining_credits` (USD), `models` and `allowed_ips` are optional; leave them out for no expiry, no spending cap, every model and any IP address. The full key is in `key` in this response only: later reads return just `key_prefix`, so store the key now.

```bash
curl -X POST https://api.beatapi.io/v1/account/keys \
  -H "Authorization: Bearer $BEATAPI_PERSONAL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title": "ci-runner", "remaining_credits": 20, "models": ["deepseek-v4.1-flash"], "allowed_ips": ["203.0.113.7"]}'
```

```json
{
  "data": {
    "object": "api_key",
    "id": "1304",
    "title": "ci-runner",
    "key_prefix": "sk-Qm8R",
    "key": "sk-Qm8RExampleFullKeyShownOnlyInThisResponse",
    "status": "active",
    "created_at": 1793250000,
    "last_used_at": null,
    "expires_at": null,
    "remaining_credits": 20,
    "credits_used": 0,
    "models": ["deepseek-v4.1-flash"],
    "allowed_ips": ["203.0.113.7"]
  }
}
```

Edit a key with `PATCH /v1/account/keys/{id}`, sending any of `title`, `status` (`active` or `disabled`), `expires_at` (`null` for never), `remaining_credits` (`null` for unlimited), `models` (`[]` for every model) and `allowed_ips`. `DELETE /v1/account/keys/{id}` returns `object`, `id` and `deleted: true`.

## Reveal an API key

Needs `api_key:reveal`, which is separate from `api_key:read`: a token that can list keys cannot read their secrets unless you grant this too.

```bash
curl -X POST https://api.beatapi.io/v1/account/keys/1287/reveal \
  -H "Authorization: Bearer $BEATAPI_PERSONAL_TOKEN"
```

```json
{
  "data": {
    "object": "api_key_secret",
    "id": "1287",
    "key": "sk-Ab3dExampleFullKey"
  }
}
```

## List payments

Needs `wallet:read`. `status` is `paid` (the default), `pending`, `failed`, `expired`, `reviewing` or `all`. `currency` is `USD` or `CNY`, the currency paid in; `credits` is the balance the payment adds, in USD.

```bash
curl -G https://api.beatapi.io/v1/account/payments \
  -H "Authorization: Bearer $BEATAPI_PERSONAL_TOKEN" \
  --data-urlencode "status=paid" \
  --data-urlencode "limit=20"
```

```json
{
  "data": {
    "object": "list",
    "data": [
      {
        "object": "payment",
        "id": "5531",
        "order_no": "ORD20261027153012",
        "status": "paid",
        "amount_paid": 20,
        "currency": "USD",
        "credits": 20,
        "payment_method": "stripe",
        "created_at": 1793100000,
        "paid_at": 1793100042
      }
    ],
    "has_more": false
  }
}
```

## Update the notification threshold

Needs `profile:write`; reading the settings with `GET /v1/account/notifications` needs `profile:read`. Send any of `type` (`email` or `none`), `threshold_credits` and `notification_email`. BeatAPI alerts you when usage takes your balance below the threshold; `0` selects the default. The response is the updated settings, where `effective_threshold_credits` is the threshold in force.

```bash
curl -X PATCH https://api.beatapi.io/v1/account/notifications \
  -H "Authorization: Bearer $BEATAPI_PERSONAL_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"threshold_credits": 5}'
```

```json
{
  "data": {
    "object": "notification_settings",
    "type": "email",
    "threshold_credits": 5,
    "effective_threshold_credits": 5,
    "default_threshold_credits": 1,
    "notification_email": "billing@example.com",
    "account_email": "ada@example.com"
  }
}
```

## Reconcile one task

Save `data.id` from the task-creation response, then look it up:

```bash
curl https://api.beatapi.io/v1/account/usage/records/task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ \
  -H "Authorization: Bearer $BEATAPI_PERSONAL_TOKEN"
```

```json
{
  "data": {
    "object": "usage_record",
    "id": "task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ",
    "type": "image",
    "model": "seedream-5-pro",
    "task_id": "task_7Qm2Xc9LpR4vT8sKa1Nf6YbH3eWdJ0uZ",
    "request_id": "202610290330051234567893f9a1c2eXy7Kp2Qd",
    "api_key": { "id": "1287", "title": "production", "key_prefix": "sk-Ab3d" },
    "status": "succeeded",
    "created_at": 1793244605,
    "currency": "USD",
    "credits_charged": 0.06,
    "credits_refunded": 0,
    "usage": { "images": 2 }
  }
}
```

For a text request, use the request ID from the `X-BeatAPI-Request-Id` response header. When a text response body carries `chatcmpl-<request ID>` as its `id`, that value works as well. The request ID of a task's creation call returns the same record as its task ID.

## List usage records

```bash
curl -G https://api.beatapi.io/v1/account/usage/records \
  -H "Authorization: Bearer $BEATAPI_PERSONAL_TOKEN" \
  --data-urlencode "start=2026-10-01T00:00:00Z" \
  --data-urlencode "end=2026-11-01T00:00:00Z" \
  --data-urlencode "limit=100"
```

```json
{
  "data": {
    "object": "list",
    "data": [
      {
        "object": "usage_record",
        "id": "20261029041512987654321b3f9a1c2eQm8Rt4Lw",
        "type": "text",
        "model": "deepseek-v4.1-flash",
        "request_id": "20261029041512987654321b3f9a1c2eQm8Rt4Lw",
        "api_key": { "id": "1287", "title": "production", "key_prefix": "sk-Ab3d" },
        "status": "succeeded",
        "created_at": 1793247312,
        "currency": "USD",
        "credits_charged": 0.002468,
        "credits_refunded": 0,
        "usage": { "input_tokens": 1200, "output_tokens": 350, "cached_tokens": 1024 }
      }
    ],
    "has_more": true,
    "next_cursor": "eyJ0IjoxNzkzMjQ3MzEyLCJpIjo5ODc2NTQzMn0"
  }
}
```

| Parameter    | Meaning                                                                                    |
| ------------ | ------------------------------------------------------------------------------------------ |
| `start`      | Earliest `created_at` to include. Unix seconds or RFC 3339, such as `2026-10-01T00:00:00Z` |
| `end`        | Exclusive upper bound, in the same formats                                                 |
| `model`      | Public model ID, such as `seedream-5-pro`                                                  |
| `type`       | One of `text`, `image`, `video`, `workflow`, `data`, `realtime` or `other`                 |
| `api_key_id` | Numeric API key ID, as in `api_key.id`                                                     |
| `limit`      | Records per page, from 1 to 100; the default is 20                                         |
| `cursor`     | The `next_cursor` of the previous page                                                     |

While `has_more` is `true`, request the next page with `cursor`. An unknown parameter, a `limit` outside 1–100 or a timestamp in milliseconds is refused with `400 bad_request` instead of being ignored.

## The usage record

| Field              | Meaning                                                                                                                                                 |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `object`           | `usage_record`                                                                                                                                          |
| `id`               | The task ID for a task, otherwise the request ID                                                                                                        |
| `type`             | `text`, `image`, `video`, `workflow`, `data`, `realtime` or `other`                                                                                     |
| `model`            | Public model ID                                                                                                                                         |
| `task_id`          | Present for tasks                                                                                                                                       |
| `request_id`       | Request ID of the call that created the record, when known                                                                                              |
| `api_key`          | `id`, `title` and `key_prefix` of the API key that paid; `null` for Dashboard playground use                                                            |
| `status`           | `queued`, `processing`, `succeeded`, `failed`, or `refunded` when the whole charge came back                                                            |
| `created_at`       | Unix seconds                                                                                                                                            |
| `currency`         | `USD`                                                                                                                                                   |
| `credits_charged`  | What you paid, in USD, after your discounts and any settlement; up to six decimal places                                                                |
| `credits_refunded` | USD returned to your balance                                                                                                                            |
| `usage`            | Billable quantities: `input_tokens`, `output_tokens`, `cached_tokens` and `cache_write_tokens` for text; `images`, `seconds` and `resolution` for media |

A task's amounts match `usage.credits_charged` and `usage.credits_refunded` from `GET /v1/tasks/{task_id}`. When a task settles above or below its reserved amount, the record shows the settled total. You can read only your own records: another account's ID returns `404 not_found`.

## Errors

| Status | `code`         | When                                                                                                                    |
| ------ | -------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `400`  | `bad_request`  | A query parameter or body field is invalid, or the query has an unknown parameter                                       |
| `401`  | `unauthorized` | The token is missing, unknown, revoked or expired                                                                       |
| `403`  | `forbidden`    | The token lacks the permission the endpoint needs (the message names it), or an API key was sent to an account endpoint |
| `404`  | `not_found`    | Your account has no record, payment or API key with that ID                                                             |

Errors use the standard [error object](/errors).