# Per-key credit limits

Every API key can carry a credit limit. It caps what that one key can spend; the account balance and your other keys are untouched. Use it to fence off a script, a staging environment or a customer-facing integration so a bug or a runaway loop cannot drain the account.

## How it works

- A key has an optional `credit_limit` in credits. `null` means no limit, which is the default.
- Usage is counted per key: every charge the key makes (a fresh fetch, a paid cache hit, a top-up when a dearer data route was needed) adds to its `credits_used`. Every refund (source failure, not found) is given back to the key. Calls made before the key had a limit, or before limits existed, are not counted.
- The limit is enforced **before charging**, in the same atomic check as the account balance, so two concurrent requests cannot both squeeze under it.
- A call the limit cannot cover answers `402 KEY_BUDGET_EXCEEDED` and nothing is charged. Running out of account credits is still `402 INSUFFICIENT_CREDITS`; the `type` tells you which one it was.

## Setting a limit

In the [dashboard](https://onekey.dev/dashboard/keys), each key has a **Set limit** action. Enter a number of credits and save; **Remove limit** clears it. A revoked key cannot be given a limit.

The dashboard calls the account API, which needs a signed-in browser session (cookie plus a CSRF header), not an API key. The same route is available to your own tooling once you are signed in:

```bash
# set the limit (whole or fractional credits, above 0, at most 1,000,000,000)
curl -X PUT "https://api.onekey.dev/v1/keys/KEY_ID/budget" \
  -b "session cookie from the dashboard" -H "x-weft-csrf: 1" -H "origin: https://your-dashboard-origin" \
  -H "content-type: application/json" -d '{"credit_limit": 500}'

# clear it
curl -X PUT "https://api.onekey.dev/v1/keys/KEY_ID/budget" \
  -b "session cookie from the dashboard" -H "x-weft-csrf: 1" -H "origin: https://your-dashboard-origin" \
  -H "content-type: application/json" -d '{"credit_limit": null}'

# read the limit and what the key has used
curl "https://api.onekey.dev/v1/keys/KEY_ID/budget" -b "session cookie from the dashboard"
```

```json
{
  "id": "KEY_ID",
  "label": "ci",
  "prefix": "ok_live_ab12",
  "created_at": "2026-09-01T10:00:00+00:00",
  "last_used_at": null,
  "revoked": false,
  "credit_limit": 500,
  "credits_used": 0
}
```

`KEY_ID` is the key's id as listed by `GET /v1/keys` (and in `/v1/me`), not the secret itself. Both listings show each key's `credit_limit`.

## Reading it from the API side

`GET /v1/account`, called with the key, reports the calling key's own limit and usage, free:

```bash
curl "https://api.onekey.dev/v1/account" -H "x-api-key: YOUR_API_KEY"
```

```json
{
  "account_id": "...",
  "key": { "id": "KEY_ID", "prefix": "ok_live_ab12", "kind": "live", "credit_limit": 500, "credits_used": 40 },
  "balance_credits": 837.8,
  "balance_millicredits": 837800,
  "blocked": false
}
```

## When the limit is reached

```bash
curl "https://api.onekey.dev/v1/github/profile?username=torvalds" -H "x-api-key: YOUR_API_KEY"
# HTTP/1.1 402 Payment Required
```

```json
{
  "success": false,
  "data": null,
  "metadata": { "request_id": "1a0a591e117d41bf8240ca677f7b43cc" },
  "error": {
    "code": "key_budget_exceeded",
    "type": "KEY_BUDGET_EXCEEDED",
    "message": "This API key has reached its credit budget",
    "request_id": "1a0a591e117d41bf8240ca677f7b43cc",
    "doc_url": "https://onekey.dev/errors/KEY_BUDGET_EXCEEDED"
  }
}
```

Raise or clear the limit, or use another key. Do not retry the same call until then: it fails the same way.

## Edge cases

- **Refunds restore the limit.** A call that is refunded (source failure, not found) is not counted against the key, so a run of failures does not eat the budget.
- **Free calls are free here too.** Cache hits at 0 credits, `GET /v1/account` and rejected requests do not count. A cached response over 100 KB (0.2 credit) does.
- **A key near its limit can still make cheaper calls.** The check is per call: a 5-credit search may be refused while a 1-credit lookup on the same key still goes through.
- **Validation.** The limit must be above 0 and at most 1,000,000,000 credits; anything else is `422 INVALID_PARAMS`, as is setting a limit on a revoked key.

## What it costs

Nothing. Setting, reading and clearing a limit are free dashboard operations, and a refused call (`402`) is never charged.
