Skip to content
Numes

Accounts & billing

Management keys

Provision, limit, rotate and revoke inference keys from your own code with a management key.

On this page

A management key lets your backend administer inference keys through the compatible API: create a key per customer or service, adjust spending limits, disable a leaked key, delete keys you no longer need and read account balances. The endpoints follow OpenRouter's key-management shape, with one addition: every new key names its permanent billing currency.

What a management key can and cannot do

A management key canA management key cannot
List, create, read, update and delete inference keys in its accountRun inference or list models
Read its own metadata with GET /keyCreate, list or revoke other management keys
Read the account's credit totals with GET /creditsChange a key's billing currency or expiry after creation

Management keys are created only in the dashboard: open Keys, choose Create key, and select Management as the Type. They have no currency or spending limit, and the secret is shown once. Disable or delete them on the same page.

Authority follows the creator

A management key acts with the current role of the person who created it:

  • Created by the personal account owner, or by an organization owner or administrator: it manages every inference key in the account.

  • Created by any other organization member: it sees and manages only the inference keys that member created. Keys it creates are attributed to that member.

  • If the creator is removed from the organization or their account is suspended, the management key stops authenticating. A role change applies to the next request.

Signing the creator out of the dashboard doesn't revoke their management keys; disable or delete them in Keys.

A management key can create keys that spend the account's balance. Keep it in the one service that provisions keys, never in application servers that only call models.

Endpoints

All management endpoints take the management key as a bearer token. Using an inference key on them returns 401 invalid_credentials.

{hash} is the key's non-secret hash field: the SHA-256 hex digest of the full key. You can compute it from a leaked key when you have no other record of it.

Every key object has the fields described in API keys, without is_management_key.

List keys

curl "https://api.numes.ai/api/v1/keys?include_disabled=true&offset=0" \  -H "Authorization: Bearer $NUMES_MANAGEMENT_KEY"

Query parameters

include_disabledbooleanDefault false

Include disabled keys. Deleted keys are never listed.

offsetintegerDefault 0

Number of keys to skip.

The response is {"data": [...]} with at most 100 keys per page. Only inference keys are returned. To read everything, advance offset by the number of keys returned until a page has fewer than 100.

Create a key

curl -X POST "https://api.numes.ai/api/v1/keys" \  -H "Authorization: Bearer $NUMES_MANAGEMENT_KEY" \  -H "Content-Type: application/json" \  -d '{    "name": "customer-4821",    "billing_currency": "USD",    "limit": 50,    "limit_reset": "monthly"  }'

Request body

namestringRequired

1–100 characters after trimming.

billing_currencystringRequired

USD or BTC. Permanent. The currency must be available to your account.

limitnumber | null

Spending limit in the key's currency, zero or greater. Omit or send null for no limit. Parsed as an exact decimal.

limit_resetstring | null

daily, weekly or monthly (UTC windows). Requires limit. Omit or send null for a lifetime limit.

expires_atstring | null

A future time in UTC, such as 2026-12-31T23:59:59Z. Times with a non-UTC offset are rejected.

The response is 201 Created:

Response
{  "data": {    "hash": "3b7c9e1f5a2d4c6e8f0a1b3c5d7e9f1a2b4c6d8e0f1a3b5c7d9e1f2a4b6c8d0e",    "label": "sk-example-v1-8d2e...41b7",    "name": "customer-4821",    "billing_currency": "USD",    "disabled": false,    "limit": 50,    "limit_remaining": 50,    "limit_reset": "monthly",    "usage": 0,    "usage_daily": 0,    "usage_weekly": 0,    "usage_monthly": 0,    "created_at": "2026-09-28T10:04:11Z",    "updated_at": null,    "expires_at": null  },  "key": "sk-..."}

key is the secret. It appears only in this response, so store it immediately. Keep data.hash with your own customer record to manage the key later without the secret.

Read a key

curl "https://api.numes.ai/api/v1/keys/3b7c9e1f5a2d4c6e8f0a1b3c5d7e9f1a2b4c6d8e0f1a3b5c7d9e1f2a4b6c8d0e" \  -H "Authorization: Bearer $NUMES_MANAGEMENT_KEY"

Returns {"data": {...}}. A key that doesn't exist, was deleted, or is outside the management key's authority returns 404 not_found.

Update a key

curl -X PATCH "https://api.numes.ai/api/v1/keys/3b7c9e1f5a2d4c6e8f0a1b3c5d7e9f1a2b4c6d8e0f1a3b5c7d9e1f2a4b6c8d0e" \  -H "Authorization: Bearer $NUMES_MANAGEMENT_KEY" \  -H "Content-Type: application/json" \  -d '{    "limit": 75,    "limit_reset": "monthly"  }'

Request body (at least one field)

namestring

New name, 1–100 characters.

disabledboolean

true stops the key authenticating; false re-enables it.

limitnumber | null

New spending limit. null removes the limit and its reset window.

limit_resetstring | null

daily, weekly, monthly or null. Setting a window on a key without a limit is rejected.

billing_currency and expires_at cannot be changed; sending them, or any other unknown field, returns 400 invalid_request. The response is the updated key.

Delete a key

curl -X DELETE "https://api.numes.ai/api/v1/keys/3b7c9e1f5a2d4c6e8f0a1b3c5d7e9f1a2b4c6d8e0f1a3b5c7d9e1f2a4b6c8d0e" \  -H "Authorization: Bearer $NUMES_MANAGEMENT_KEY"
Response
{ "deleted": true }

Deletion is permanent: clients using the key get 401 immediately and a later GET /keys/{hash} returns 404. Past usage remains in Activity.

Errors

Statuserror_typeCause
400invalid_requestUnknown field, invalid value, limit_reset without limit, non-UTC expires_at, currency not available, missing currency on /credits
401invalid_credentialsMissing, invalid, disabled, deleted or wrong-kind key
404not_foundNo key with that hash within the management key's authority
413payload_too_largeBody over 32 KiB
415unsupported_media_typeBody sent without Content-Type: application/json
429rate_limitedToo many requests or repeated failed authentications; honor Retry-After

Automation recipes

The recipes read the management key from NUMES_MANAGEMENT_KEY.

Provision a key per customer with a monthly limit

Create one key per customer so each has its own budget and its own breakdown in Activity.

import osimport requests​BASE_URL = "https://api.numes.ai/api/v1"HEADERS = {"Authorization": f"Bearer {os.environ['NUMES_MANAGEMENT_KEY']}"}​​def provision_customer_key(customer_id: str, monthly_usd: int) -> tuple[str, str]:    """Return (hash, secret). Store the secret in your secret manager."""    response = requests.post(        f"{BASE_URL}/keys",        headers=HEADERS,        json={            "name": f"customer-{customer_id}",            "billing_currency": "USD",            "limit": monthly_usd,            "limit_reset": "monthly",        },        timeout=10,    )    response.raise_for_status()    body = response.json()    return body["data"]["hash"], body["key"]

Rotate a key

Create the replacement with the same settings, deploy it, then delete the old key. The old key keeps working until the final step, so there is no gap.

def rotate_key(old_hash: str) -> tuple[str, str]:    old = requests.get(f"{BASE_URL}/keys/{old_hash}", headers=HEADERS, timeout=10)    old.raise_for_status()    current = old.json()["data"]​    created = requests.post(        f"{BASE_URL}/keys",        headers=HEADERS,        json={            "name": current["name"],            "billing_currency": current["billing_currency"],            "limit": current["limit"],            "limit_reset": current["limit_reset"],        },        timeout=10,    )    created.raise_for_status()    body = created.json()    return body["data"]["hash"], body["key"]​​def retire_key(old_hash: str) -> None:    """Call after every deployment has switched to the new key."""    requests.delete(f"{BASE_URL}/keys/{old_hash}", headers=HEADERS, timeout=10).raise_for_status()

Between the two calls, verify the rollout by calling GET /key with the new secret from each environment.

Disable a leaked key

If a secret appears somewhere it should not, compute its hash and disable it at once. Disabling is reversible, so you can investigate before deleting.

import hashlib​​def disable_leaked_key(plaintext: str) -> None:    key_hash = hashlib.sha256(plaintext.encode()).hexdigest()    response = requests.patch(        f"{BASE_URL}/keys/{key_hash}",        headers=HEADERS,        json={"disabled": True},        timeout=10,    )    response.raise_for_status()

Then review what the key spent in Activity, issue a replacement, and delete the leaked key.

Report spend per key

List every key and read its settled spend for the current UTC month:

Python
def monthly_spend() -> dict[str, float]:    spend, offset = {}, 0    while True:        response = requests.get(            f"{BASE_URL}/keys",            headers=HEADERS,            params={"include_disabled": "true", "offset": offset},            timeout=10,        )        response.raise_for_status()        page = response.json()["data"]        for key in page:            spend[key["name"]] = key["usage_monthly"]        if len(page) < 100:            return spend        offset += len(page)

Values are in each key's own currency; never add USD and BTC keys together. For exact arithmetic, parse the raw JSON numbers as decimals (for example json.loads(text, parse_float=Decimal) in Python).

Read balances

GET /credits reports the account's credit totals in one currency. currency is required (USD or BTC); balances in different currencies are never combined.

curl "https://api.numes.ai/api/v1/credits?currency=USD" \  -H "Authorization: Bearer $NUMES_MANAGEMENT_KEY"
Response
{  "data": {    "currency": "USD",    "total_credits": 250,    "total_usage": 61.402116  }}

Response `data`

currencystring

The requested currency. BTC values are in sats.

total_creditsnumber

Everything credited to the account, net of refunds, disputes and corrections.

total_usagenumber

Lifetime settled usage charges.

total_credits - total_usage is the posted balance. It doesn't subtract funds held for running requests; Available in Billing does. Per-request cost is in GET /generation.

Next steps