CloseRouter

Keys and limits API

Create, revoke and configure keys through the API.

Create a key

curl
curl https://api.closerouter.dev/v1/keys \
  -H "Authorization: Bearer $CLOSEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production worker",
    "is_anonymous": false,
    "max_price": { "input": "1", "output": "4" },
    "max_price_by_model": {
      "openai/gpt-5.4-mini": { "cache_read": "0.1" }
    },
    "spend_limit": {
      "amount_usd": "25",
      "period": "monthly"
    }
  }'

List keys

curl
curl https://api.closerouter.dev/v1/keys \
  -H "Authorization: Bearer $CLOSEROUTER_API_KEY"

Update maximum prices

curl
curl https://api.closerouter.dev/v1/keys/{key_id}/limit \
  -X PATCH \
  -H "Authorization: Bearer $CLOSEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "max_price": { "input": "0.5" },
    "max_price_by_model": {
      "openai/gpt-5.4-mini": { "cache_read": "0.1", "cache_write": "0.5" }
    }
  }'

Clear limits

curl
curl https://api.closerouter.dev/v1/keys/{key_id}/limit \
  -X PATCH \
  -H "Authorization: Bearer $CLOSEROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "spend_limit": null, "max_price": null, "max_price_by_model": {} }'

Revoke a key

curl
curl https://api.closerouter.dev/v1/keys/{key_id} \
  -X DELETE \
  -H "Authorization: Bearer $CLOSEROUTER_API_KEY"

Key Auto

On the dashboard, creation keeps Auto inside Additional settings. The default inherits model and account settings with no Auto label. The three-dot menu lets you change an existing key’s mode.

POST /keys accepts optional auto_strategy: "cheap" | "fast" | "stable" | null. Missing/null inherits. An explicit mode is stored literally, including when equal to the account mode, and displays a badge.

PATCH /keys/{key_id}/auto accepts {"auto_strategy":"fast"}; {"auto_strategy":null} restores inheritance. Missing/unknown values return 400. api.closerouter.dev requires an unrestricted API key; closerouter.dev/v1 requires the owner’s Dashboard JWT. Disabled keys can be edited on the dashboard; revoked keys return 404.

Precedence and effective mode

Priority: explicit request provider/strategy → manual chain → key Auto → model Auto → account Auto → Cheap. Request Auto bypasses a manual chain; a saved key pin preserves it. Changes apply to new requests; current attempts/fallback keep their original plan.

GET /providers and PATCH /providers responses add effective_auto_strategy and auto_strategy_source for the current key; api_key identifies a pin. Both are null for a manual chain. auto_strategy and auto_strategy_override retain account and model meanings. PATCH /providers changes the account’s model setting, not the key. /models reference prices are unchanged.

Allow selected models

The easiest path is selecting models on the dashboard Keys page. REST mutation requires a Dashboard JWT on closerouter.dev; a regular API key cannot broaden its own or a sibling key's access.

curl
export CLOSEROUTER_DASHBOARD_TOKEN="your_dashboard_access_token"

curl https://closerouter.dev/v1/keys/{key_id}/model-access \
  -X PATCH \
  -H "Authorization: Bearer $CLOSEROUTER_DASHBOARD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "model_access": {
      "type": "allowlist",
      "model_ids": [
        "anthropic/claude-haiku-4.5",
        "openai/gpt-5.4-mini"
      ]
    }
  }'

Allow all models

Use the owner’s Dashboard JWT, as in the previous example.

curl
curl https://closerouter.dev/v1/keys/{key_id}/model-access \
  -X PATCH \
  -H "Authorization: Bearer $CLOSEROUTER_DASHBOARD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "model_access": { "type": "all" } }'

Model access

New and existing keys default to model_access.type = "all".

allowlist accepts 1 to 100 unique public model IDs and atomically replaces the previous list; aliases are stored as canonical IDs.

If any candidate in model plus models[] is denied, the entire inference request returns 403 api_key_model_access_denied before provider calls, usage, or debit.

Restricted key permissions

A restricted key may read GET /keys but cannot create, change, or revoke keys: the API returns 403 api_key_management_forbidden.

Spend limits

Field or actionRule
spend_limit.amount_usdspend_limit.amount_usd is a USD string.
spend_limit.periodperiod can be daily, weekly, or monthly.
spend_limit: nullPass spend_limit: null to clear a limit.
DELETE /keys/{key_id}DELETE /keys/{key_id} revokes the key; new requests with that key stop immediately.

Maximum price

On /keys, open Key limits and enable Maximum text price. Search by name or ID above the unified list. Expand key defaults or a model row to edit input, output, cache write and cache read prices together. An empty model field inherits the key default. Click Save. Price settings are independent of the spend budget.

POST /keys, GET /keys and PATCH /keys/{key_id}/limit use max_price for key defaults and max_price_by_model for a map such as {"Model ID": {"input": "1"}}. Up to 100 models; aliases are stored as canonical public Model IDs.

input, output, cache_read and cache_write are optional USD per million token amounts, supplied as strings or numbers, nonnegative, with up to 8 decimal places and 12 integer digits. Zero is a real zero ceiling; an empty object supplies no limits.

Inheritance and updates

Each field follows request → key model override → key default → unrestricted precedence. Overrides can raise or lower the ceiling; omitted fields inherit.

PATCH preserves omitted top-level settings. A supplied max_price replaces all global fields; max_price_by_model replaces the entire model map and its objects. max_price: null clears defaults; max_price_by_model: {} or null clears model overrides. To remove a field or model, supply the complete remaining object. Price-only updates preserve spend_limit and spend-only updates preserve prices.

For /chat/completions, /responses and /messages, send for example "max_price": {"input": 0.5, "cache_read": 0.1}. It applies to ordinary/streaming requests, Auto, explicit providers and all permitted fallback attempts. This local policy is not sent to the supplier.

Rate matching

We compare maximum customer rates over the base tariff and all context tiers. cache_write uses the shortest available TTL rather than the cheapest rate; a longer requested TTL can be billed above that baseline ceiling. Missing separate cache rates fall back to the input rate. Confirmed free promotion admission counts as $0; otherwise the normal tariff applies.

A rate equal to its ceiling qualifies. If no available route qualifies, the API returns 422 max_price_no_route before supplier calls, without a charge. Invalid objects or prices return 400 invalid_request. These settings apply only to text models.

error.metadata.price_limit adds unit: usd_per_million_tokens, comparison: maximum_across_context_tiers and cache_write_comparison: shortest_available_ttl. models contains the public model, effective limits and whole tariffs with base_rates, compared_rates and exceeded_fields. For example, an input ceiling of 0.061 rejects a base rate of 0.06 when the long-context rate is 0.12: compared_rates.input is the string 0.12 and exceeded_fields is [input]. Prices are exact decimal strings, including zero.

scope: request_selection covers only tariffs considered under this request's selection, rather than the whole catalog or guaranteed availability. selection: primary identifies the main plan; recovery_only is reserved for Auto recovery. Diagnostics are limited to five models, five tariffs per model and 16 KiB; truncated marks omitted details. Rates from separate tariffs are never combined. A pre-dispatch fallback guard describes only that attempted tariff and makes no promise about charges from earlier attempts.