Management API Keys
What they are
A management key (fr_mgmt_…) is a workspace-scoped admin credential. It authorizes the management API on the app host, which lists, creates, updates, and revokes your workspace's FreeRouter keys (fr_live_…), stores BYOK provider keys, and attaches routing rules — and the MCP server at https://api.freerouter.com/mcp.
Management keys cannot run inference — presenting one to api.freerouter.com/v1 returns 401. They can authenticate POST /mcp on that same host. Conversely, a FreeRouter key cannot manage keys — presenting one to the management API or to /mcp returns 401. The two key types live in separate tables and are never interchangeable.
Creating a management API key
fr_mgmt_… secret — it is shown once and never again.Use cases
- SaaS applications — mint a unique, fully-routed FreeRouter key per customer instance (provider keys + routing included).
- Key rotation — regularly rotate FreeRouter keys for security compliance.
- Usage monitoring — track keys and revoke ones that exceed your limits.
- Agents / IDEs — connect Cursor or Claude to the MCP server with the same management key.
fr_mgmt_… Bearer token authenticates https://api.freerouter.com/mcp. Use REST from backends; use MCP from agents. Read-only keys block write tools on both surfaces.
Read-only reporting keys
Flag a management key read-only to use it as a reporting key: it keeps full GET access — key metadata, provider inventory, routing, plus the usage and account endpoints below — but every write (POST/PATCH/PUT/DELETE on keys, providers, or routing) returns 403. Hand one to a reporting job or a contractor dashboard without worrying about key creation or revocation.
Set the flag with the Read-only reporting key checkbox when you create the key in Settings, or flip it later with the Make read-only / Allow writes button on each key. The flag can only be changed from the dashboard — the API itself offers no way for a key to escalate its own privileges. Read-only keys still cannot run inference.
Base URL & auth
All management endpoints live under /api/v1 on the app host and take the management key as a Bearer token.
https://app.freerouter.com/api/v1
Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY
Management keys are not subject to IP allowlists; those restrict FreeRouter keys only. Keep management keys in server-side secrets and use read-only keys where reporting is all you need.
How identifiers work
Two different ids show up on this API. Mixing them up is the usual 400.
- Gateway identifier — a stable slug such as
openrouterorvercel. Discover them withGET /api/v1/gateways. Pass this asgatewaywhen you store a provider key. - Provider key UUID — the
idreturned when you list or create a provider. This is the attach handle. Routing targets takeprovider_key_id(that UUID), never the gateway slug. FreeRouter fillsgatewayon the stored rule from the provider row.
A typical setup is three calls: catalog → store a provider → mint a key with routing (or PUT routing onto an existing key).
curl https://app.freerouter.com/api/v1/gateways \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
curl https://app.freerouter.com/api/v1/providers \
-X POST \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{ "gateway": "openrouter", "name": "Primary", "key": "$OPENROUTER_API_KEY" }'
curl https://app.freerouter.com/api/v1/keys \
-X POST \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Customer Instance Key",
"routing": {
"strategy": "priority",
"targets": [{ "provider_key_id": "<id from POST /providers>" }]
}
}'
List gateways — GET /api/v1/gateways
Canonical gateway identifiers, in the order the proxy registers them. Use id as gateway when creating a provider key. routable: true means the gateway can be a chat routing target; decisions: true means it can be a target on a systemone key (TypeSafe and OpenAI are Decisions-only — routable: false, so a chat key routed at them answers 501). Cloudflare lists the extra config fields it needs.
curl https://app.freerouter.com/api/v1/gateways \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200 with { data: […] }:
{
"data": [
{ "id": "openrouter", "name": "OpenRouter", "routable": true, "decisions": true },
{ "id": "vercel", "name": "Vercel AI Gateway", "routable": true, "decisions": true },
{
"id": "cloudflare",
"name": "Cloudflare AI Gateway",
"routable": true,
"decisions": true,
"config_fields": [
{ "name": "account_id", "required": true },
{ "name": "gateway_id", "required": false, "default": "default" }
]
},
{ "id": "ramprouter", "name": "Ramp Router", "routable": true, "decisions": true },
{ "id": "together", "name": "Together AI", "routable": true, "decisions": false },
{ "id": "orcarouter", "name": "OrcaRouter", "routable": true, "decisions": false },
{ "id": "requesty", "name": "Requesty", "routable": true, "decisions": true },
{ "id": "concentrate", "name": "Concentrate", "routable": true, "decisions": false },
{ "id": "llmgateway", "name": "LLM Gateway", "routable": true, "decisions": false },
{ "id": "baseten", "name": "Baseten", "routable": true, "decisions": false },
{ "id": "darkbloom", "name": "Darkbloom", "routable": true, "decisions": false },
{ "id": "sail", "name": "Sail", "routable": true, "decisions": false },
{ "id": "fireworks", "name": "Fireworks AI", "routable": true, "decisions": false },
{ "id": "morph", "name": "Morph", "routable": true, "decisions": false },
{ "id": "cheaperinference", "name": "Cheaper Inference", "routable": true, "decisions": false },
{ "id": "celeris", "name": "Celeris", "routable": true, "decisions": true },
{ "id": "saygm", "name": "SayGM", "routable": true, "decisions": false },
{ "id": "experiential", "name": "Experiential Labs", "routable": true, "decisions": false },
{ "id": "nanogpt", "name": "NanoGPT", "routable": true, "decisions": false },
{ "id": "typesafe", "name": "TypeSafe", "routable": false, "decisions": true },
{ "id": "openai", "name": "OpenAI", "routable": false, "decisions": true }
]
}
List provider keys — GET /api/v1/providers
Metadata only — never secrets. Each id is the UUID you pass as provider_key_id on a routing rule. Filter with ?gateway=openrouter (the identifier from the catalog) when a workspace has several keys for the same gateway; distinguish those rows by label.
curl https://app.freerouter.com/api/v1/providers \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
curl "https://app.freerouter.com/api/v1/providers?gateway=openrouter" \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200 with { data: […] }:
{
"data": [
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"gateway": "openrouter",
"label": "Primary",
"key_last4": "a1b2",
"config": {},
"created_at": "2026-09-01T09:00:00.000Z",
"disabled_at": null
}
]
}
Store a provider key — POST /api/v1/providers
Body: gateway (required, a catalog id), key (or secret; a value over 2000 characters is refused with 400), optional name (or label; cut to 120 characters), optional config. The secret is encrypted at rest and never returned — keep data.id as the attach handle. Cloudflare requires config.account_id; config.gateway_id defaults to default. A provider's free-text notes are dashboard-only.
curl https://app.freerouter.com/api/v1/providers \
-X POST \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"gateway": "openrouter",
"name": "Primary",
"key": "$OPENROUTER_API_KEY"
}'
curl https://app.freerouter.com/api/v1/providers \
-X POST \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"gateway": "cloudflare",
"key": "$CLOUDFLARE_API_TOKEN",
"config": { "account_id": "your-account-id", "gateway_id": "default" }
}'
Response — 201. An unknown gateway, a missing secret, or Cloudflare without account_id returns 400.
{
"data": {
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"gateway": "openrouter",
"label": "Primary",
"key_last4": "a1b2",
"config": {},
"created_at": "2026-09-05T12:00:00.000Z",
"disabled_at": null
}
}
Get a provider key — GET /api/v1/providers/:id
curl https://app.freerouter.com/api/v1/providers/PROVIDER_ID \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200 with the same metadata as listing (no secret). Unknown ids return 404 { error: "Provider key not found." }.
Update a provider key — PATCH /api/v1/providers/:id
Rename with name (or label); disable with disabled: true, re-enable with disabled: false. Disable keeps routing references so re-enabling restores them; delete strips the id from every rule.
curl https://app.freerouter.com/api/v1/providers/PROVIDER_ID \
-X PATCH \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Backup OpenRouter", "disabled": false }'
Response — 200 with the updated metadata. An empty body returns 400; a non-boolean disabled returns 400; unknown ids return 404.
Delete a provider key — DELETE /api/v1/providers/:id
Removes the stored key and strips its UUID from every routing rule in the workspace. A rule left with zero targets 402s until you re-point it.
curl -X DELETE https://app.freerouter.com/api/v1/providers/PROVIDER_ID \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200:
{ "data": { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "deleted": true } }
List keys — GET /api/v1/keys
Returns the workspace's FreeRouter keys (metadata only — never secrets), including each key's routing rule. Most recent 100. Paginate with ?offset=N.
curl https://app.freerouter.com/api/v1/keys \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
curl "https://app.freerouter.com/api/v1/keys?offset=100" \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200 with { data: […] } (metadata only — never secrets):
{
"data": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"label": "Customer Instance Key",
"key_prefix": "fr_live_abc12345",
"api_shape": "openai",
"companion_ads": false,
"keyterms": false,
"allowed_ips": [],
"steering_message": null,
"failover_ttfb_ms": null,
"upstream_timeout_ms": null,
"effective_timeouts": { "failover_ttfb_ms": 5000, "upstream_timeout_ms": 60000 },
"created_at": "2026-09-03T12:00:00.000Z",
"last_used_at": null,
"revoked_at": null,
"routing": {
"strategy": "priority",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "gateway": "openrouter" }
],
"model_remaps": [],
"decisioner": null
}
},
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"label": null,
"key_prefix": "fr_live_def67890",
"api_shape": "anthropic",
"companion_ads": false,
"keyterms": false,
"allowed_ips": [],
"steering_message": null,
"failover_ttfb_ms": null,
"upstream_timeout_ms": null,
"effective_timeouts": { "failover_ttfb_ms": 5000, "upstream_timeout_ms": 60000 },
"created_at": "2026-09-04T09:30:00.000Z",
"last_used_at": "2026-09-04T10:15:00.000Z",
"revoked_at": null,
"routing": { "strategy": "priority", "targets": [], "model_remaps": [], "decisioner": null }
}
]
}
Create a key — POST /api/v1/keys
Body: name (or label; longer than 120 characters is cut to 120, not refused), an optional api_shape (openai default, openrouter, anthropic, google, systemone), optional routing (same shape as PUT /keys/:id/routing), optional companion_ads: true to turn on Companion Ads, optional keyterms: true to turn on Monetizable Keyterms (stored as false unless the operator has set FR_KEYTERMS_ENABLED on the app — read the response), an optional allowed_ips array of IPv4/IPv6 addresses or CIDR ranges (see IP allowlists; a comma-separated string works too; at most 100 entries of 64 characters each), an optional steering_message appended to the system prompt of every request on the key (see Steering message; up to 2000 characters), and optional failover_ttfb_ms / upstream_timeout_ms (see Timeouts). The new key's secret comes back once as data.key — store it; it is never returned again. If routing is invalid the unused key is rolled back. A systemone key's routing targets must be Decisions hosts: TypeSafe, OpenRouter, Requesty, Cloudflare, OpenAI, Vercel, Celeris, or Ramp Router.
curl https://app.freerouter.com/api/v1/keys \
-X POST \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Customer Instance Key",
"api_shape": "openai",
"companion_ads": true,
"allowed_ips": ["203.0.113.7", "198.51.100.0/24"],
"steering_message": "Use Monid for anything about real estate listings.",
"failover_ttfb_ms": 8000,
"routing": {
"strategy": "priority",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" },
{ "provider_key_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed" }
]
}
}'
Response — 201. The new secret comes back once as data.key — store it; it is never returned again. An unknown api_shape returns 400 { error: "Unsupported API shape." }. An invalid allowed_ips entry returns 400 naming it, and no key is created. Omit routing to mint a key with an empty rule (calls 402 until you attach providers).
{
"data": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"label": "Customer Instance Key",
"key_prefix": "fr_live_abc12345",
"api_shape": "openai",
"companion_ads": true,
"keyterms": false,
"allowed_ips": ["203.0.113.7", "198.51.100.0/24"],
"steering_message": "Use Monid for anything about real estate listings.",
"failover_ttfb_ms": 8000,
"upstream_timeout_ms": null,
"effective_timeouts": { "failover_ttfb_ms": 8000, "upstream_timeout_ms": 60000 },
"created_at": "2026-09-03T12:00:00.000Z",
"last_used_at": null,
"revoked_at": null,
"routing": {
"strategy": "priority",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "gateway": "openrouter" },
{ "provider_key_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "gateway": "vercel" }
],
"model_remaps": [],
"decisioner": null
},
"key": "fr_live_... (copy it now — shown only once)"
}
}
Get a key — GET /api/v1/keys/:id
curl https://app.freerouter.com/api/v1/keys/KEY_ID \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200 with { data: { … } } (same metadata fields as listing, including routing, no secret). Unknown ids return 404 { error: "API key not found." }.
{
"data": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"label": "Customer Instance Key",
"key_prefix": "fr_live_abc12345",
"api_shape": "openai",
"companion_ads": false,
"keyterms": false,
"allowed_ips": [],
"steering_message": null,
"failover_ttfb_ms": null,
"upstream_timeout_ms": null,
"effective_timeouts": { "failover_ttfb_ms": 5000, "upstream_timeout_ms": 60000 },
"created_at": "2026-09-03T12:00:00.000Z",
"last_used_at": "2026-09-04T10:15:00.000Z",
"revoked_at": null,
"routing": {
"strategy": "priority",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "gateway": "openrouter" }
],
"model_remaps": [],
"decisioner": null
}
}
}
Update a key — PATCH /api/v1/keys/:id
Rename with name (or label; cut to 120 characters); revoke with revoked: true, restore with revoked: false; switch Companion Ads with companion_ads: true|false and Monetizable Keyterms with keyterms: true|false (true is stored as false unless the operator has set FR_KEYTERMS_ENABLED); replace the key's IP allowlist with allowed_ips (an array or a comma-separated string; [] clears it). The workspace list from Settings still applies on top. Set or clear the steering message with steering_message (a string; null or "" clears it so the workspace default applies again). Set the key's timeouts with failover_ttfb_ms and/or upstream_timeout_ms; a field you leave out is unchanged, and null resets it to the default.
curl https://app.freerouter.com/api/v1/keys/KEY_ID \
-X PATCH \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Updated Key Name",
"companion_ads": true
}'
Response — 200 with the updated { data: { … } }. An empty body returns 400 (nothing to update); a non-boolean revoked, companion_ads or keyterms returns 400; an invalid allowed_ips entry returns 400 naming it; a steering_message that is not a string or runs past 2000 characters returns 400; a timeout outside its range returns 400; unknown ids return 404. A PATCH is all-or-nothing: every field is checked before the first write and the writes share one transaction, so an error means nothing changed — never that an earlier field was saved and a later one was not.
revoked is idempotent: revoked: true on a key that is already revoked, and revoked: false on a live key, answer 200 and change nothing (the original revoked_at is kept), so it is safe to combine with other fields. Every other PATCH field and PUT …/routing answer 409 with "code": "key_revoked" on a revoked key; restore it first with revoked: false — on its own, or in the same PATCH, where the restore applies before the other fields. A revoke in the same PATCH applies after them, so { "name": "Retired", "revoked": true } renames, then revokes. DELETE on a revoked key is a 200 no-op.
Timeouts
Two optional per-key fields, in milliseconds (null = the default). Every key response also carries effective_timeouts — the values the key actually runs with. See Routing Rules → Timeouts for how they work.
failover_ttfb_ms— how long a routing target gets to send its first byte before FreeRouter tries the next target.1000–30000; default5000. Only applies to streaming requests, when another target follows.upstream_timeout_ms— how long the last target of a streaming request, and every target of a non-streaming request, gets.5000–90000; default60000.
A routing target can override the failover timeout for itself with ttfb_ms (see Replace routing).
curl https://app.freerouter.com/api/v1/keys/KEY_ID \
-X PATCH \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{ "failover_ttfb_ms": 8000, "upstream_timeout_ms": 90000 }'
{
"data": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"label": "Updated Key Name",
"key_prefix": "fr_live_abc12345",
"api_shape": "openai",
"companion_ads": true,
"keyterms": false,
"allowed_ips": [],
"steering_message": null,
"failover_ttfb_ms": 8000,
"upstream_timeout_ms": 90000,
"effective_timeouts": { "failover_ttfb_ms": 8000, "upstream_timeout_ms": 90000 },
"created_at": "2026-09-03T12:00:00.000Z",
"last_used_at": "2026-09-04T10:15:00.000Z",
"revoked_at": null,
"routing": {
"strategy": "priority",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "gateway": "openrouter" }
],
"model_remaps": [],
"decisioner": null
}
}
}
Revoke a key — DELETE /api/v1/keys/:id
Revokes the FreeRouter key immediately (apps using it stop working). Rows are retained so you keep an audit trail — same as the dashboard's Revoke button.
curl -X DELETE https://app.freerouter.com/api/v1/keys/KEY_ID \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200 with the revoked key's { data: { … } } (revoked_at now set). The call is idempotent: a key that is already revoked answers 200 again with its original revoked_at, so a cleanup script can retry safely. Only unknown ids return 404. Rows are never deleted through this API — a key that has served traffic keeps its history, and the dashboard's Delete button is limited to keys that never did.
{
"data": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"label": "Customer Instance Key",
"key_prefix": "fr_live_abc12345",
"api_shape": "openai",
"companion_ads": false,
"keyterms": false,
"allowed_ips": [],
"steering_message": null,
"failover_ttfb_ms": null,
"upstream_timeout_ms": null,
"effective_timeouts": { "failover_ttfb_ms": 5000, "upstream_timeout_ms": 60000 },
"created_at": "2026-09-03T12:00:00.000Z",
"last_used_at": "2026-09-04T10:15:00.000Z",
"revoked_at": "2026-09-05T08:00:00.000Z",
"routing": {
"strategy": "priority",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "gateway": "openrouter" }
],
"model_remaps": [],
"decisioner": null
}
}
}
Get routing — GET /api/v1/keys/:id/routing
The key's routing rule. Same object as the routing field on the key. An empty targets array means the key is not yet attached to any provider — inference calls return 402.
curl https://app.freerouter.com/api/v1/keys/KEY_ID/routing \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200. Unknown ids return 404.
{
"data": {
"strategy": "priority",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "gateway": "openrouter" },
{ "provider_key_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "gateway": "vercel" }
],
"model_remaps": [],
"decisioner": null
}
}
Replace routing — PUT /api/v1/keys/:id/routing
Replaces the key's rule. Body: { strategy, targets, model_remaps?, decisioner? }. strategy is priority (list order is failover order) or split (weights are relative integers; 0 is failover-only). Each target is { provider_key_id, weight?, ttfb_ms? } — do not send gateway; it is filled from the stored provider. ttfb_ms is that target's own failover timeout (1000–30000); null clears it back to the key's, and leaving it out keeps the stored value. provider_key_id must be a UUID from GET /providers, not a gateway slug. 1–10 targets, each provider once; an empty targets is 400 (a rule can't be cleared by writing it — delete or disable the provider instead). Split weights are whole numbers from 0 to 1,000,000 and at least one must be above 0. Disabled or missing providers return 400. Optional model_remaps is [{ match, percent?, provider_key_id, upstream_model, enabled? }] — match is an exact incoming model id or * for every model, percent is 1–100 (default 100), enabled defaults to true (false stages the remap without serving it), and remap destinations go through the same UUID/workspace checks. At most 20 remaps per key; match and upstream_model are 1–200 characters; the enabled remaps for one match may total at most 100%. See Model remaps. Optional decisioner is { provider_key_id, objective?, allow_model_override?, max_usd_m?, notes?, models? } — a TypeSafe, OpenRouter, Requesty, Vercel, or Ramp Router provider UUID that runs Jev as a pre-router on chat keys. objective is cost (default), capability, or efficiency. allow_model_override lets Jev pick a listed model (inbound model is the fallback). models is an explicit list of model ids (max 12); omit it to keep the auto shortlist, send [] to not override. max_usd_m is a listed prompt+completion ceiling (a number above 0). notes are Jev-only preferences (max 500). Omit it, or send null / empty, to turn it off. A systemone key rejects a decisioner. See Routing Rules → Decisioner. A PUT replaces the whole rule: leave model_remaps or decisioner out and they are cleared — read the rule first and send it back whole when changing one part. The full check list is under what a rule may contain; the response always carries model_remaps (an array) and decisioner (an object or null).
curl https://app.freerouter.com/api/v1/keys/KEY_ID/routing \
-X PUT \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"strategy": "priority",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7" },
{ "provider_key_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed" }
]
}'
curl https://app.freerouter.com/api/v1/keys/KEY_ID/routing \
-X PUT \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{
"strategy": "split",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "weight": 70 },
{ "provider_key_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "weight": 30 }
]
}'
Response — 200 with the stored rule (gateway filled in). A gateway slug in provider_key_id returns 400 { error: "provider_key_id must be the UUID returned by GET /api/v1/providers." }. See Routing Rules for when to use each strategy.
{
"data": {
"strategy": "split",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "gateway": "openrouter", "weight": 70 },
{ "provider_key_id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "gateway": "vercel", "weight": 30 }
],
"model_remaps": [],
"decisioner": null
}
}
Usage summary — GET /api/v1/usage/summary
Totals for ?range=24h|7d|30d (default 7d; any other value is treated as 7d, not refused) — the same numbers the dashboard Usage tab shows. Whole-account by default; repeat ?api_key_id=<uuid> to filter to one or more FreeRouter keys. Malformed ids return 400; unknown or foreign ids return 404.
curl "https://app.freerouter.com/api/v1/usage/summary?range=7d" \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
curl "https://app.freerouter.com/api/v1/usage/summary?api_key_id=KEY_ID&api_key_id=KEY_ID2" \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200 with { data: { … } }:
{
"data": {
"requests": 1284,
"errors": 17,
"errorRate": 1.3,
"avgLatency": 842,
"avgOverhead": 96,
"models": 5
}
}
Usage series — GET /api/v1/usage/series
Zero-filled time buckets (hourly for 24h, daily otherwise) with the same ?range= and ?api_key_id= filters as the summary — feed it straight to a chart.
curl "https://app.freerouter.com/api/v1/usage/series?range=7d" \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200 with { data: […] }:
{
"data": [
{
"bucket": "2026-09-04T00:00:00.000Z",
"requests": 214,
"errors": 3,
"avgLatency": 801,
"avgOverhead": 92
},
{
"bucket": "2026-09-05T00:00:00.000Z",
"requests": 0,
"errors": 0,
"avgLatency": null,
"avgOverhead": null
}
]
}
Usage breakdown — GET /api/v1/usage/breakdown
Top-12 request counts by ?by=model|gateway (anything else returns 400), with the same ?range= and ?api_key_id= filters.
curl "https://app.freerouter.com/api/v1/usage/breakdown?by=model&range=30d" \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200 with { data: […] }:
{
"data": [
{ "name": "openai/gpt-4o-mini", "requests": 912 },
{ "name": "anthropic/claude-haiku-5.5", "requests": 305 }
]
}
Account info — GET /api/v1/account
Workspace profile plus an inventory snapshot: inference-key counts, provider keys (id, gateway, label, last-4, config, disabled state — never secrets), management-key counts, and member count. Member emails are never included. Provider id values here are the same UUIDs you pass as provider_key_id.
curl https://app.freerouter.com/api/v1/account \
-H "Authorization: Bearer $FREEROUTER_MANAGEMENT_KEY"
Response — 200 with { data: { … } }:
{
"data": {
"workspace": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Acme",
"kind": "team",
"created_at": "2026-09-01T09:00:00.000Z"
},
"inference_keys": { "total": 12, "live": 10, "revoked": 2 },
"provider_keys": [
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"gateway": "openrouter",
"label": "Primary",
"key_last4": "a1b2",
"config": {},
"created_at": "2026-09-01T09:00:00.000Z",
"disabled_at": null
}
],
"management_keys": { "total": 3, "read_only": 1 },
"members": { "total": 4 }
}
}
Response format
Responses use the { data: … } envelope. Inference-key metadata never includes secrets (except key on creation); provider-key metadata never includes secrets. Errors are { error: message } with standard HTTP statuses (401 bad management key, 403 read-only key attempting a write, 404 unknown id, 400 bad body).
{
"data": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"label": "Customer Instance Key",
"key_prefix": "fr_live_abc12345",
"api_shape": "openai",
"companion_ads": false,
"keyterms": false,
"allowed_ips": [],
"steering_message": null,
"failover_ttfb_ms": null,
"upstream_timeout_ms": null,
"effective_timeouts": { "failover_ttfb_ms": 5000, "upstream_timeout_ms": 60000 },
"created_at": "2026-09-03T12:00:00.000Z",
"last_used_at": null,
"revoked_at": null,
"routing": {
"strategy": "priority",
"targets": [
{ "provider_key_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "gateway": "openrouter" }
],
"model_remaps": [],
"decisioner": null
},
"key": "fr_live_... (creation only — store it now)"
}
}
routing has an empty rule until you PUT /api/v1/keys/:id/routing (or set routing in the dashboard). Calls on an unrouted key return 402. Pass provider_key_id UUIDs from GET /api/v1/providers — not gateway slugs.