Portal
Documentation: all sections

API key controls

When you mint a key you can scope it: cap its monthly spend, restrict it to a set of models, and give it an expiry. These are enforced on every metered call. A key created without any of them behaves exactly as it always has: unlimited, all models, no expiry.

Creating a scoped key

POST /v1/keys accepts these optional fields alongside name:

curl https://api.buildsable.com/v1/keys \
-H "Authorization: Bearer $SABLE_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
  "name": "trading-agent-prod",
  "spend_limit_usd": 50,
  "allowed_models": ["sable-llama-3.3-70b", "sable-deepseek-v3"],
  "expires_in_days": 90
}'

The plaintext key is returned exactly once at creation. Store it immediately. See Authentication.

Enforcement

The controls are checked on every metered route: /v1/chat/completions, /v1/embeddings, /v1/messages (which delegates to the chat handler), and /v1/sandboxes, where the sandbox allowlist entry gates access:

ConditionStatusError type
Monthly spend would exceed spend_limit_usd402spend_limit_exceeded
Requested model not in allowed_models403scope_denied
Call made after the key's expiry401

Spend is measured against the same metered cost that appears in each receipt and rolls up in usage, so a key's cap and your account totals always agree.

A monthly cap binds the key and its whole delegated subtree: usage by every delegated sub-key beneath a key counts against that key's cap, and every ancestor's cap is enforced on each request. A child can never spend past any budget above it, however the tree fans out.

# HTTP 402 Payment Required
{
"error": {
  "type": "spend_limit_exceeded",
  "message": "monthly spend limit reached for this key"
}
}

The spend cap is a monthly window. A key that hits its limit resumes at the start of the next month; revoke and re-mint if you need it sooner, or raise the cap on a fresh key. Expiry is one-way: an expired key returns 401 and can't be reactivated.

Rotating a key

POST /v1/keys/:id/rotate (session-authed, like minting) replaces a key without re-deriving its scopes by hand. It mints a scope-identical replacement (same spend cap, allowlist, rate limit, and tier), returns the new plaintext exactly once, and expires the old key after a grace window so in-flight deployments can switch over without a hard cut.

curl https://api.buildsable.com/v1/keys/$KEY_ID/rotate \
-H "Authorization: Bearer $SABLE_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"grace_minutes": 30}'

grace_minutes defaults to 10 and is capped at 1440 (one day). The grace window never extends an earlier expiry: a key already set to expire sooner still expires at its original time.