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

# API keys

> Your API keys in the calling environment: ids, names, scopes, state and timestamps. Never a secret.

The API keys entity lists **your own keys** in the environment you call it from, so a program can see which key it runs as, which keys are about to expire, and whether a rotation is under way. Keys are issued, rotated and revoked by Bullionwale on your request (see [Getting access](/getting-access) and [Credentials](/credentials)); the API only reads them.

| Call | Endpoint | Scope |
| - | - | - |
| [List API keys](#read-your-keys) | `GET /keys` | `account:read` |

| Environment | Endpoint |
| - | - |
| Test | `/api/trading/test/v1/keys` |
| Production | `/api/trading/v1/keys` |

A test key lists your **test** keys, and a live key your **production** keys: each environment sees only its own.

## The key object

| Field | Type | Meaning |
| - | - | - |
| `key_id` | string | The key's public id: the third part of the key. Safe to log and to quote to Bullionwale. |
| `prefix` | string | The key without its secret: `bwk_test.<broker>.<key id>` or `bwk_live.<broker>.<key id>`. |
| `name` | string | The name the key was issued with, for example `order-bot`. |
| `environment` | string | `test` or `production`. |
| `scopes` | array of strings | What the key may do (see the [Scope reference](/scopes)). |
| `state` | string | Whether the key is accepted (below). |
| `current` | boolean | `true` for the key that made this request. |
| `created_at` | string or null | When it was issued. |
| `last_used_at` | string or null | When it was last used. Updated at most once a minute. |
| `expires_at` | string or null | When it expires; `null` = never. |
| `revoked_at` | string or null | When it was revoked; `null` if it was not. |
| `overlap_until` | string or null | For a replaced key: until when it is still accepted. |

| `state` | Accepted? | Meaning |
| - | - | - |
| `active` | Yes | In use. |
| `rotating` | Yes, until `overlap_until` | Replaced by a new key; both work during the overlap. Switch to the new key. |
| `rotated` | No | Replaced, and its overlap has ended: refused with `401 credential_rotated`. |
| `expired` | No | Reached `expires_at`: refused with `401 credential_expired`. |
| `revoked` | No | Revoked: refused with `401 credential_revoked`. |

The secret is never returned, nor anything that could rebuild it: it was shown once, when the key was issued or rotated.

## Read your keys

```bash curl theme={null}
curl -s https://api.bullionwale.com/api/trading/test/v1/keys \
  -H "X-API-Key: $BULLIONWALE_API_KEY"
```

```python Python theme={null}
import os
import requests

BASE = "https://api.bullionwale.com/api/trading/test/v1"
HEADERS = {"X-API-Key": os.environ["BULLIONWALE_API_KEY"]}

for k in requests.get(f"{BASE}/keys", headers=HEADERS, timeout=10).json()["items"]:
    print(k["key_id"], k["name"], k["state"], k["expires_at"])
```

```json 200 OK theme={null}
{
  "items": [
    {"key_id": "3c1f0a9b7d2e4c6a8b10", "prefix": "bwk_test.bullionwale.3c1f0a9b7d2e4c6a8b10",
     "name": "order-bot", "environment": "test",
     "scopes": ["account:read", "orders:read", "orders:write"], "state": "active",
     "current": true, "created_at": "2026-10-14T06:00:00+00:00",
     "last_used_at": "2026-10-21T09:31:05+00:00", "expires_at": null, "revoked_at": null,
     "overlap_until": null}
  ],
  "total": 1
}
```

The list is newest first and includes keys that no longer work, so you can see their history. It takes `limit` (1 to 200, default 50) and `offset`, like `GET /orders`.

During a rotation, the production list shows both keys:

```json 200 OK theme={null}
{
  "items": [
    {"key_id": "9e7d5c3b1a0f2e4d6c80", "prefix": "bwk_live.bullionwale.9e7d5c3b1a0f2e4d6c80",
     "name": "order-bot-live", "environment": "production",
     "scopes": ["account:read", "orders:read", "orders:write"], "state": "active",
     "current": false, "created_at": "2026-10-21T08:00:00+00:00", "last_used_at": null,
     "expires_at": null, "revoked_at": null, "overlap_until": null},
    {"key_id": "8d2e4f6a0b1c3d5e7f90", "prefix": "bwk_live.bullionwale.8d2e4f6a0b1c3d5e7f90",
     "name": "order-bot-live", "environment": "production",
     "scopes": ["account:read", "orders:read", "orders:write"], "state": "rotating",
     "current": true, "created_at": "2026-10-14T06:00:00+00:00",
     "last_used_at": "2026-10-21T08:15:00+00:00", "expires_at": null, "revoked_at": null,
     "overlap_until": "2026-10-21T09:00:00+00:00"}
  ],
  "total": 2
}
```

Here the request was made with the old key (`current`, `rotating`): it keeps working until 09:00 UTC, and the new key has not been used yet.

## What to check

* **At start-up:** the `current` key's `state` is `active`. If it is `rotating`, switch to the new key before `overlap_until`.
* **On a schedule:** any key whose `expires_at` is close. Ask Bullionwale for a rotation in good time (see [Credentials](/credentials#expiry)).
* **Unused keys:** a key whose `last_used_at` stays `null` or old may belong to a retired system; ask Bullionwale to revoke it.

## Errors

| Code | Status | Meaning |
| - | - | - |
| `insufficient_scope` | 403 | The key does not have `account:read`. |
| `rate_limited` | 429 | The key's read budget is used up. Wait `Retry-After` seconds. |

A key that is not accepted cannot call `GET /keys` at all: it is refused with its `401` code first (see [Authentication](/authentication#when-authentication-fails)). Every code is in the [Error reference](/api-reference/error-reference).


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