BUZZ AI Gateway
Docs · API Reference · Tokens

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.

This is BUZZ's own management API, not an LLM API. Use these endpoints to script things like provisioning a key per workspace or rotating keys on a schedule. To call Claude or other models, see 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.

HeaderNotes
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.
Don't reuse your 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.

MethodPathPurpose
GET/api/token/List your tokens (paginated)
GET/api/token/searchSearch by name or key fragment
GET/api/token/:idFetch one token (key masked)
POST/api/token/:id/keyReveal full unmasked key
POST/api/token/Create a new token
POST/api/token/batchBatch delete
POST/api/token/batch/keysBatch reveal full keys (max 100)
PUT/api/token/Update a token
DELETE/api/token/:idDelete a token (soft delete)

The Token object

Returned by every list / get / update endpoint:

FieldTypeDescription
idintegerToken ID
user_idintegerOwner user ID
namestringDisplay name. Max 50 characters.
keystringThe 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.
statusinteger1 = Enabled, 2 = Disabled, 3 = Expired, 4 = Exhausted
created_timeint64Unix seconds
accessed_timeint64Unix seconds; updated on each LLM call
expired_timeint64Unix seconds. -1 means never expires.
remain_quotaintegerRemaining quota units. Can go negative if the key has overshot.
unlimited_quotabooleanIf true, remain_quota is informational only and never blocks usage.
used_quotaintegerTotal quota consumed
model_limits_enabledbooleanIf true, restrict this key to the models in model_limits
model_limitsstringComma-separated list of allowed model names
allow_ipsstring | nullNewline-separated allow-list of IPs / CIDRs. null or empty = no restriction.
groupstringDefault routing group for this key (legacy single-group field, kept for backward compatibility)
vendor_routesstringJSON-encoded map {"<vendor>":"<group>"} for per-vendor routing. Empty string falls back to group.
DeletedAtstring | nullSoft-delete timestamp; null for live tokens

Status constants

ValueNameMeaning
1EnabledKey is active and accepts requests
2DisabledManually disabled by the user
3Expiredexpired_time has passed
4Exhaustedremain_quota is depleted (and unlimited_quota is false)

GET /api/token/

List the calling user's tokens, paginated.

GET https://buzzai.cc/api/token/

Query parameters

NameTypeNotes
pintegerPage number, default 1
page_sizeintegerItems 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
      }
    ]
  }
}

Search your tokens by name or key fragment. Carries SearchRateLimit.

GET https://buzzai.cc/api/token/search

Query parameters

NameTypeNotes
keywordstringLIKE match on name
tokenstringLIKE match on the key column. sk- prefix is stripped automatically.
pintegerPage number
page_sizeintegerItems 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.

GET https://buzzai.cc/api/token/:id

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.

POST https://buzzai.cc/api/token/:id/key
Why a separate endpoint? List and get responses always mask the key. Revealing the full string is treated as a sensitive operation and goes through extra middleware (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.

POST https://buzzai.cc/api/token/

Request body

Send a partial Token object. The server only reads the following fields and generates everything else (id, user_id, key, timestamps):

FieldRequiredNotes
nameyes≤ 50 characters
expired_timeyesUnix seconds, or -1 for never
remain_quotayesIf unlimited_quota is false: must be ≥ 0 and ≤ 1_000_000_000 × QuotaPerUnit
unlimited_quotanoDefaults to false
model_limits_enabledno
model_limitsnoComma-separated
allow_ipsnoNewline-separated
groupnoDefault routing group
vendor_routesnoJSON 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": ""
}
The create response does not include the new 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.

PUT https://buzzai.cc/api/token/

Query parameters

NameNotes
status_onlyIf 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:

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).

DELETE https://buzzai.cc/api/token/:id

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.

POST https://buzzai.cc/api/token/batch

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.

POST https://buzzai.cc/api/token/batch/keys

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.

GET https://buzzai.cc/api/usage/token/

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

HTTPCause
400Malformed body, name too long, vendor_routes invalid JSON, batch over 100 ids, illegal status transition
401Missing / invalid Authorization header, missing Buzz-User, or Buzz-User doesn't match the token owner
403User is banned or lacks RoleCommonUser
404Token id doesn't exist or belongs to a different user (queries are scoped to user_id)
429SearchRateLimit or CriticalRateLimit hit
500Internal 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