Token Management
Account-level endpoints for managing your BUZZ API keys (the sk- tokens you pass to /v1/messages and /v1/chat/completions). These are the same endpoints the BUZZ dashboard calls when you create, rename, or revoke a key.
POST /v1/messages or POST /v1/chat/completions instead.
Authentication
All /api/token/* endpoints sit behind UserAuth middleware. They accept either a session cookie (used by the dashboard) or an Access Token (used for scripting). The Access Token is a separate, longer-lived credential you can mint from the user settings page; it is not the same as your sk- API key.
| Header | Notes |
|---|---|
Authorization: <ACCESS_TOKEN> | Required. Bare access token string. No Bearer prefix — this is the bare value, intentionally different from the /v1/* relay scheme. |
Buzz-User: <USER_ID> | Required. Numeric user ID matching the access token's owner. Mismatch returns 401. |
sk- key here. The sk- key only authenticates LLM relay traffic on /v1/* and the single self-check endpoint GET /api/usage/token/. Calling /api/token/* with an sk- key returns 401.
Headers
Authorization: <YOUR_ACCESS_TOKEN>
Buzz-User: 1
Content-Type: application/json
Endpoints
Nine endpoints for the full lifecycle of a key, plus one self-check endpoint that uses a different auth scheme.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/token/ | List your tokens (paginated) |
| GET | /api/token/search | Search by name or key fragment |
| GET | /api/token/:id | Fetch one token (key masked) |
| POST | /api/token/:id/key | Reveal full unmasked key |
| POST | /api/token/ | Create a new token |
| POST | /api/token/batch | Batch delete |
| POST | /api/token/batch/keys | Batch reveal full keys (max 100) |
| PUT | /api/token/ | Update a token |
| DELETE | /api/token/:id | Delete a token (soft delete) |
The Token object
Returned by every list / get / update endpoint:
| Field | Type | Description |
|---|---|---|
| id | integer | Token ID |
| user_id | integer | Owner user ID |
| name | string | Display name. Max 50 characters. |
| key | string | The sk- key. Masked in list/get responses (head 4 + ********** + tail 4 for keys longer than 8 chars). Use POST /api/token/:id/key to fetch the full value. |
| status | integer | 1 = Enabled, 2 = Disabled, 3 = Expired, 4 = Exhausted |
| created_time | int64 | Unix seconds |
| accessed_time | int64 | Unix seconds; updated on each LLM call |
| expired_time | int64 | Unix seconds. -1 means never expires. |
| remain_quota | integer | Remaining quota units. Can go negative if the key has overshot. |
| unlimited_quota | boolean | If true, remain_quota is informational only and never blocks usage. |
| used_quota | integer | Total quota consumed |
| model_limits_enabled | boolean | If true, restrict this key to the models in model_limits |
| model_limits | string | Comma-separated list of allowed model names |
| allow_ips | string | null | Newline-separated allow-list of IPs / CIDRs. null or empty = no restriction. |
| group | string | Default routing group for this key (legacy single-group field, kept for backward compatibility) |
| vendor_routes | string | JSON-encoded map {"<vendor>":"<group>"} for per-vendor routing. Empty string falls back to group. |
| DeletedAt | string | null | Soft-delete timestamp; null for live tokens |
Status constants
| Value | Name | Meaning |
|---|---|---|
| 1 | Enabled | Key is active and accepts requests |
| 2 | Disabled | Manually disabled by the user |
| 3 | Expired | expired_time has passed |
| 4 | Exhausted | remain_quota is depleted (and unlimited_quota is false) |
GET /api/token/
List the calling user's tokens, paginated.
Query parameters
| Name | Type | Notes |
|---|---|---|
| p | integer | Page number, default 1 |
| page_size | integer | Items per page. Aliases: ps, size. Hard cap: 100. |
Example
curl 'https://buzzai.cc/api/token/?p=1&page_size=10' \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1"
Response
{
"success": true,
"message": "",
"data": {
"page": 1,
"page_size": 10,
"total": 4,
"items": [
{
"id": 21,
"user_id": 1,
"name": "cc",
"key": "OnyH**********esAm",
"status": 1,
"created_time": 1779358699,
"accessed_time": 1779778939,
"expired_time": -1,
"remain_quota": 0,
"unlimited_quota": true,
"used_quota": 18009,
"model_limits_enabled": false,
"model_limits": "",
"allow_ips": null,
"group": "default",
"vendor_routes": "",
"DeletedAt": null
}
]
}
}
GET /api/token/search
Search your tokens by name or key fragment. Carries SearchRateLimit.
Query parameters
| Name | Type | Notes |
|---|---|---|
| keyword | string | LIKE match on name |
| token | string | LIKE match on the key column. sk- prefix is stripped automatically. |
| p | integer | Page number |
| page_size | integer | Items per page (cap 100) |
% is supported as a wildcard. Restrictions: %% is rejected; at most two % per pattern; if % is present the keyword must contain at least 2 non-% characters; users at or above MaxUserTokens cannot use wildcard search.
Example
curl 'https://buzzai.cc/api/token/search?keyword=AWS&p=1' \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1"
Response shape is identical to GET /api/token/.
GET /api/token/:id
Fetch one token. The key field is masked.
Example
curl https://buzzai.cc/api/token/21 \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1"
Response
{
"success": true,
"message": "",
"data": {
"id": 21,
"name": "cc",
"key": "OnyH**********esAm",
"status": 1,
"...": "..."
}
}
Cross-user access is not possible — the lookup is scoped to (id, user_id), so requesting another user's token id returns a not-found error.
POST /api/token/:id/key
Return the full, unmasked key.
CriticalRateLimit + DisableCache) so a CDN can never cache the response. Splitting the read out also keeps the bulk listing endpoint cheap to serve.
Example
curl -X POST https://buzzai.cc/api/token/21/key \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1"
Response
{
"success": true,
"message": "",
"data": {
"key": "OnyHCHFmoxWEB0bIHS6Kc8RMI9ezNyBjG07kumzorg9tesAm"
}
}
POST /api/token/
Create a new token.
Request body
Send a partial Token object. The server only reads the following fields and generates everything else (id, user_id, key, timestamps):
| Field | Required | Notes |
|---|---|---|
| name | yes | ≤ 50 characters |
| expired_time | yes | Unix seconds, or -1 for never |
| remain_quota | yes | If unlimited_quota is false: must be ≥ 0 and ≤ 1_000_000_000 × QuotaPerUnit |
| unlimited_quota | no | Defaults to false |
| model_limits_enabled | no | |
| model_limits | no | Comma-separated |
| allow_ips | no | Newline-separated |
| group | no | Default routing group |
| vendor_routes | no | JSON map; validated against ValidateVendorRoutes |
Example
curl -X POST https://buzzai.cc/api/token/ \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1" \
-H "Content-Type: application/json" \
-d '{
"name": "ci-runner",
"expired_time": -1,
"remain_quota": 0,
"unlimited_quota": true,
"model_limits_enabled": false,
"model_limits": "",
"group": "default",
"vendor_routes": ""
}'
Response
{
"success": true,
"message": ""
}
id or key. To finish provisioning, call GET /api/token/ to find the newly created row by name, then call POST /api/token/:id/key to fetch the full key.
Server-side validation will reject the request if the user already has at least MaxUserTokens tokens, or if vendor_routes is malformed.
PUT /api/token/
Update an existing token.
Query parameters
| Name | Notes |
|---|---|
| status_only | If 1, only the status field is written; all other fields in the body are ignored. |
Request body
id is required. Writable fields: name, expired_time, remain_quota, unlimited_quota, model_limits_enabled, model_limits, allow_ips, group, vendor_routes. Other fields in the body are ignored.
Status transition rules:
- Switching from
3(Expired) back to1(Enabled) is rejected ifexpired_timeis still in the past. - Switching from
4(Exhausted) back to1(Enabled) is rejected ifremain_quota ≤ 0andunlimited_quotais false.
Example: rename a token
curl -X PUT https://buzzai.cc/api/token/ \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1" \
-H "Content-Type: application/json" \
-d '{
"id": 21,
"name": "ci-runner-prod",
"expired_time": -1,
"remain_quota": 0,
"unlimited_quota": true
}'
Example: disable a token (status_only)
curl -X PUT 'https://buzzai.cc/api/token/?status_only=1' \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1" \
-H "Content-Type: application/json" \
-d '{"id": 21, "status": 2}'
Response
{
"success": true,
"message": "",
"data": { "id": 21, "name": "ci-runner-prod", "key": "OnyH**********esAm", "status": 1, "...": "..." }
}
DELETE /api/token/:id
Soft-delete one token (GORM DeletedAt).
Example
curl -X DELETE https://buzzai.cc/api/token/21 \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1"
Response
{ "success": true, "message": "" }
POST /api/token/batch
Soft-delete multiple tokens in one transaction. Redis caches are flushed.
Request body
{ "ids": [21, 133, 656] }
Response
{
"success": true,
"message": "",
"data": 3
}
data is the number of rows actually deleted. The query is scoped to user_id = current_user, so ids belonging to other users are silently skipped.
POST /api/token/batch/keys
Reveal full keys for many tokens at once. Carries CriticalRateLimit + DisableCache.
Request body
{ "ids": [21, 133] }
Maximum 100 ids per call; over-limit requests return a structured MsgBatchTooMany error.
Response
{
"success": true,
"message": "",
"data": {
"keys": {
"21": "OnyHCHFmoxWEB0bIHS6Kc8RMI9ezNyBjG07kumzorg9tesAm",
"133": "..."
}
}
}
GET /api/usage/token/ (self-check)
A separate endpoint that does use the sk- key — handy for letting an SDK ask "how much quota does this key still have?" without exposing the management API.
Authentication
Authorization: Bearer sk-<YOUR_BUZZ_KEY>
Response
{
"code": true,
"message": "ok",
"data": {
"object": "token_usage",
"name": "cc",
"total_granted": 513586360,
"total_used": 515813135,
"total_available": -2226775,
"unlimited_quota": true,
"model_limits": {},
"model_limits_enabled": false,
"expires_at": 0
}
}
Note this endpoint uses the {code, message, data} envelope rather than the {success, message, data} shape used elsewhere. expires_at is 0 when expired_time = -1.
Workflows
Provision a new key end-to-end
# 1. Create
curl -X POST https://buzzai.cc/api/token/ \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1" \
-H "Content-Type: application/json" \
-d '{"name":"ci-runner","expired_time":-1,"remain_quota":0,"unlimited_quota":true,"group":"default"}'
# 2. Find the new id (sort by created_time desc client-side, or filter by name)
curl 'https://buzzai.cc/api/token/search?keyword=ci-runner&p=1' \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1"
# 3. Reveal the full key (one-time read for your secret store)
curl -X POST https://buzzai.cc/api/token/<id>/key \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1"
Rotate keys: disable old, create new
# 1. Disable the old token (status=2)
curl -X PUT 'https://buzzai.cc/api/token/?status_only=1' \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1" \
-H "Content-Type: application/json" \
-d '{"id": 21, "status": 2}'
# 2. Create a replacement (see "Provision" workflow above)
# 3. Once the new key is deployed and verified, hard-revoke the old:
curl -X DELETE https://buzzai.cc/api/token/21 \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1"
Bulk cleanup of stale keys
# 1. List, filter client-side for status=3 (Expired) or status=4 (Exhausted)
# 2. Batch delete
curl -X POST https://buzzai.cc/api/token/batch \
-H "Authorization: $BUZZ_ACCESS_TOKEN" \
-H "Buzz-User: 1" \
-H "Content-Type: application/json" \
-d '{"ids":[101,102,103]}'
Errors
| HTTP | Cause |
|---|---|
| 400 | Malformed body, name too long, vendor_routes invalid JSON, batch over 100 ids, illegal status transition |
| 401 | Missing / invalid Authorization header, missing Buzz-User, or Buzz-User doesn't match the token owner |
| 403 | User is banned or lacks RoleCommonUser |
| 404 | Token id doesn't exist or belongs to a different user (queries are scoped to user_id) |
| 429 | SearchRateLimit or CriticalRateLimit hit |
| 500 | Internal error |
Error envelope:
{
"success": false,
"message": "Unauthorized, New-Api-User header not provided"
}
Note: the i18n string still says New-Api-User, but the actual header read is Buzz-User. Send Buzz-User.
See also
POST /v1/messages— call Claude with the keys you provision hereGET /api/user/self— current user account infoGET /api/user/dashboard— quota and usage dashboard- Authentication guide