Console
API reference

Management API

Create and manage API keys from code, with spend limits, expiry and an audit log.

POST/v1/keys

Availability. RouterPlus is enabling this API region by region. On a gateway that does not serve it yet, every route on this page answers 404 not_found to an API key and 401 to a management key. Ask RouterPlus support when your region gets it.

Manage your organization's API keys from code: create keys with a spend limit, an expiry, an IP allowlist and labels; rotate and revoke them; read the audit log and the key security report. Set guardrails and their budgets, decide grants, and change the organization's data settings. It follows the key-management shape other AI gateways use (/api/v1/keys, limit, limit_reset, GET /api/v1/key), so such code works with the base URL changed.

Every route is under https://api.routerplus.com/v1/..., and https://api.routerplus.com/api/v1/... is the same route.

Management keys

The management API takes a management key (tm_mk_ and 48 hex characters), never an inference key. A management key never runs a model: POST /v1/chat/completions with one is a 401.

ScopeWhat a key with it can do
readevery GET: list and read keys, the audit log, the security report, guardrails, budget status, guardrail events, grants, the organization's settings
keysread, and create, change, rotate and revoke API keys, and put a guardrail on a key or take it off
admineverything: management keys, guardrails, grants and the organization's settings included

Where a management key comes from:

  • The console, at Security. The organization's owner can give any scope; an admin can give read and keys.
  • The API: a key with admin can make keys with read and keys (POST /v1/management-keys). No management key can give admin, so a leaked admin key cannot copy itself past a revocation.
  • Support, for a new organization's first key.

The console lists every management key of the organization, the revoked ones too, with who made it and when it was last used, and owners and admins revoke any of them there. A revoked management key is refused at once.

A management key answers for the member who made it. It works while that member is the organization's owner, or an admin (for a key with read and keys). When the member is removed from the organization or becomes a viewer, the key answers 401 key_creator_inactive from its next request, the console shows it as inactive, and creator_active is false in GET /v1/management-keys. A key made with POST /v1/management-keys answers for the member behind the key that made it. If the member gets the role back, or is removed and later added again as an owner or admin, the member's keys work again without anyone approving them: revoke a departed member's management keys if they must stay off. Your API keys are not affected: they belong to the organization.

An organization can have 100 management keys; revoked ones count. At the bound a create answers 409 management_key_limit_reached.

RouteScopeWhat it does
GET /v1/management-keysadminthe organization's management keys, oldest first, 100 a page (limit up to 500, cursor); revoked keys are listed too unless include_disabled=false
POST /v1/management-keysadmina new management key: name, scopes (read, keys; default ["read"]) and an optional expires_at. The answer has the raw key in key, once
DELETE /v1/management-keys/<key_id>adminrevoke a management key. A key cannot revoke itself (400 cannot_delete_self)

A management key works only on the API of your organization's region (for example api-in.routerplus.com for an organization served in India); another region answers 403 wrong_region.

bash
export TM_MANAGEMENT_KEY=tm_mk_...
curl -s https://api.routerplus.com/v1/keys -H "Authorization: Bearer $TM_MANAGEMENT_KEY"

API keys

Create a key

POST /v1/keys (scope keys). The answer carries the raw key in key, once: it is not stored, only its hash. Store it now.

FieldTypeMeaning
namestring, 1 to 80 charactersrequired
workspace_idUUIDthe workspace the key belongs to; default: the organization's default workspace
principal_idUUIDa principal of the organization the key belongs to (it sets the workspace)
limitnumber (USD) or nullthe key's spend limit; null: none
limit_resetdaily, weekly, monthly or nullwhen the limit starts again: 00:00 UTC, Monday 00:00 UTC, the 1st 00:00 UTC; null: never
expires_atISO 8601 time with its zone (2027-01-01T00:00:00Z), or a date (00:00 UTC), in the futurethe key stops working at this instant
ip_allowlistarray of up to 100 CIDR ranges or addressesthe key works only from these addresses; empty or null: from anywhere. See IP allowlist for where it is available
labelsobject of up to 20 stringsyour own attribution (for example {"team": "search"}); keys a-z 0-9 _ . -, values up to 200 characters
finance_categorystring, 1 to 64 charactersa cost center; default: the workspace's
rpm_limitinteger, 1 to 300requests a minute; default 300
bash
curl -s https://api.routerplus.com/v1/keys -H "Authorization: Bearer $TM_MANAGEMENT_KEY" \
  -H "content-type: application/json" \
  -d '{"name": "customer-4711", "limit": 25, "limit_reset": "monthly", "expires_at": "2027-01-01T00:00:00Z", "labels": {"customer": "4711"}}'
json
{
  "data": {
    "key_id": "...", "name": "customer-4711", "key_prefix": "tm_vk_3f9c1a",
    "disabled": false, "expired": false,
    "limit": 25, "limit_reset": "monthly", "limit_remaining": 25, "usage": 0, "usage_in_period": 0,
    "monthly_cap": null, "expires_at": "2027-01-01T00:00:00.000Z", "ip_allowlist": null,
    "labels": {"customer": "4711"}, "finance_category": null, "rpm_limit": 300,
    "created_via": "management_api", "rotated_from": null, "last_used_at": null, "...": "..."
  },
  "key": "tm_vk_3f9c1a..."
}

In a key object, usage is what the key has spent since its first request and usage_in_period what it spent in its limit's current period; limit_remaining is limit minus usage_in_period. Amounts are USD.

How many keys. An organization can have 500 keys and create 120 API keys in any hour. A revoked or expired key still counts: its row is kept for your usage history and audit log. So do the keys the console makes for itself when you use the playground or a model search. At the first bound POST /v1/keys answers 409 key_limit_reached; at the second, 429 key_rate_limited with retry-after. A rotation makes a new key, so both bounds apply to it too: an organization at 500 cannot rotate a key until its limit is raised. Reuse keys where you can (one per customer or service, not one per session). If you need more, ask support: they can raise the limits for your organization.

List, read, change and revoke

RouteScopeWhat it does
GET /v1/keysreadthe organization's API keys, oldest first, 100 a page (limit up to 500); next_cursor names the next page (?cursor=). workspace_id filters; include_disabled=true lists revoked keys too. An expired key is listed with "expired": true.
GET /v1/keys/<key_id>readone key
PATCH /v1/keys/<key_id>keyschange name, disabled, limit, limit_reset, expires_at, ip_allowlist, labels, finance_category or rpm_limit; null clears a setting
DELETE /v1/keys/<key_id>keysrevoke the key (it is disabled, and the console can no longer copy it); revoking is for good, as in the console: a disabled key cannot be changed or enabled again (409 key_disabled). Revoking it again answers 200 and changes nothing

A change applies from the key's next request on every gateway of the region. A request that is already running finishes, a revoked key's too. A change never pauses the organization's other requests. A gateway that is refreshing its copy of the key can serve it on the old settings for about a second more.

A change that takes access away is stricter: revoking a key, rotating it, setting an earlier expiry or an IP allowlist. Once a gateway has seen such a change, it does not serve any key of the organization from what it knew before. If it cannot read the organization's settings again just then, it answers 503 with retry-after until it can, and never serves the revoked key. If such a change is saved but our gateways could not be told at once, the answer is 503, once, and its message says that the change is saved and in place. Nothing needs to be sent again: we go on telling the gateways, and a repeat of the request changes nothing and answers 200. A rotation in that case answers 201 with "announce_pending": true, because the new key is shown only once; the old key stops as soon as the gateways are told.

A PATCH with the values the key already has answers the key and records nothing. An ip_allowlist is a set: the same ranges in another order, or with one named twice, are the values the key already has.

Rotate a key

POST /v1/keys/<key_id>/rotate (scope keys) with an optional grace_seconds (0 to 604,800; default 0) returns a new key with the same settings, and the raw key once. The old key stops at once, or after grace_seconds.

The two keys stay one rotation family: the new key's limit and monthly cap count what the old key spent in the period, and during the grace period the old key counts the new key's spend. Rotating never gives a key a fresh limit.

A key that a rotation replaced stays expired once its grace period is over: a later PATCH of its expires_at answers 409 key_rotated_out.

The calling key's own status

GET /v1/key with an API key (not a management key) answers the key's own status. It gets the same checks as a model call: expiry, IP allowlist, a verified email and the organization's region. The answer is up to 2 seconds old: a key that asks more often gets the same answer again.

json
{ "data": { "label": "customer-4711", "usage": 3.12, "limit": 25, "limit_remaining": 21.88, "limit_reset": "monthly",
  "usage_in_period": 3.12, "expires_at": "2027-01-01T00:00:00.000Z", "is_free_tier": false,
  "rate_limit": { "requests": 300, "interval": "1m" } } }

What the controls do on a request

  • Spend limit. Held like the monthly spend cap: before a request is sent, the gateway holds its maximum cost against the limit, with every other request in flight on the key across the region's gateways. A request that does not fit is refused, before any provider is called, with 429 insufficient_quota ("key spend limit reached"), x-should-retry: false, and the reset time in the message and in x-tm-cap-reset (no header for a limit that never resets). The limit counts what RouterPlus bills, as the monthly cap does; requests served on your own provider keys (BYOK) cost nothing here and are not counted. A request counts in the period it started in: one still running at 00:00 UTC is yesterday's, and takes nothing from today's limit.
  • Expiry. From expires_at on, every request is refused with 401 and error_type: "key_expired". RouterPlus may disable a key that expired more than a day ago; it then answers as any unknown key does (401 auth). Either way the key is finished.
  • IP allowlist. A request from an address outside the list is refused with 403 and error_type: "ip_not_allowed". The address is the client's as our edge saw it (behind Cloudflare and our proxy), never a header your client sets. RouterPlus turns allowlists on region by region; where they are not on yet, the API refuses a non-empty ip_allowlist with 400 ip_allowlist_unavailable. An IPv4 range admits IPv4 callers and an IPv6 range IPv6 callers. Write an IPv4 range as IPv4 (10.0.0.0/8): an IPv4-mapped IPv6 entry (::ffff:10.0.0.0/104) is accepted only with a prefix of 96 or more.
  • Labels and finance category travel with the key's spend. A request cannot override a key's own labels.

Month and lifetime periods count the key's whole history. Day and week periods read a daily rollup that starts when this API was released: in that first week they count only the spend after the release, until our history fill has run.

Audit log

GET /v1/audit (scope read): every change to your organization's keys and management keys, made in the console or with a management key, and the identity, provider connection and routing policy changes, newest first. Rows cannot be changed or deleted.

ParameterMeaning
since, untilISO 8601 times with their zone (2026-11-01T00:00:00Z), or dates (00:00 UTC)
sourcegovernance, identity, connection or routing_policy
actionfor example key.create, key.update, key.rotate, management_key.create
actorfor example mgmt:tm_mk_3f9c1a, console:[email protected], operator
limit, cursor100 a page, up to 200; next_cursor names the next page. With action or actor, a page can hold fewer rows than limit, or none: go on while next_cursor is not null
json
{ "data": [ { "source": "governance", "at": "2026-11-09T12:00:00.000Z", "actor": "mgmt:tm_mk_3f9c1a",
  "action": "key.create", "subject": "...", "ip": "203.0.113.9", "detail": { "name": "customer-4711" } } ],
  "next_cursor": null }

A key.update row says what changed: detail.changes has each changed setting with its old and new value ({"limit": {"from": 5, "to": 7}}). Every changed setting is named, and a short value (a number, a time, a short name or list) is recorded as it is. A value of more than 96 bytes (a long name, many labels, a long allowlist) is recorded as its size in bytes, a short hash and how it starts, with a list's count or a map's keys ({"keys": 20, "bytes": 4901, "sha256": "125fe534580472c5", "starts": "…"}): the same value has the same hash. A key's secret is never in a row. ip is the caller's address in a region whose proxy passes it on to the gateways; until a region is set up for that, it is our own proxy's address.

The console shows the same rows at Audit log, with a CSV export.

Security report

GET /v1/security/keys (scope read): each active key and what is risky about it.

FlagMeaning
no_limitneither a spend limit nor a monthly cap
no_expiryno expires_at
idlenot used for 30 days (a key never used: once it is 7 days old)
expiring_soonexpires within 7 days
no_ip_allowlistusable from any address
rotateda newer key replaced it; it still works in its grace period

The answer also has summary (how many of the organization's active keys have each flag: every key, not only this page's), each key's spend over 30 days (usage_30d_usd) and next_cursor. The console shows it at Security.

Limits on the API itself

Each gateway serves one organization's management requests one at a time, with a short line behind the one that is running, and at most 300 a minute. Past either, the answer is 429 with retry-after: wait that long and send the request again.

Writes (POST, PATCH, DELETE) have a budget for the whole region: 60 a minute and 600 an hour for one organization, whichever gateway takes them. Only a write that changes something counts. A write that is refused (400, 403, 404, 409, 429) or that changes nothing (a PATCH of the values a key already has, a repeated revoke) is not counted; such requests still count in the 300 a minute above. Past the budget the answer is 429 write_rate_limited with retry-after, and nothing was changed.

A revoke is never refused because other writes used the budget. DELETE /v1/keys/<key_id>, PATCH with "disabled": true and DELETE /v1/management-keys/<key_id> have an allowance of their own, ten times the budget (600 a minute, 6,000 an hour), so an organization can always revoke its keys.

A request body must arrive within 3 seconds of its headers, or the answer is 408 request_timeout and the connection is closed. A gateway reads at most two unfinished bodies of one organization at a time.

These bounds are per organization, so that one organization's calls do not use up another's share. The gateways are still shared: when a region is busy, any call can get 429 or 503 with retry-after.

GET /v1/key has the same bounds, counted apart: an organization's API keys can cause 300 status reads a minute on a gateway. An answer that is reused (up to 2 seconds old) is not a read.

Guardrails

The routes of this section, of Grants and of Organization settings exist only where guardrails are on (availability). Elsewhere they answer as a route the gateway does not serve.

The rules are on the Guardrails page. The routes:

RouteScopeWhat it does
GET /v1/guardrailsreadevery guardrail of the organization, with its assignments
POST /v1/guardrailsadmincreate one: name, description and the rules. 201
GET /v1/guardrails/<id>readone guardrail
PATCH /v1/guardrails/<id>adminthe fields you send replace the stored ones
DELETE /v1/guardrails/<id>admindelete it and every assignment of it
POST /v1/guardrails/<id>/assignmentskeys on a key, else admin{"target_kind": "org" | "workspace" | "member" | "key", "target_id": ...}. 201. It replaces another guardrail on the same target
DELETE /v1/guardrails/<id>/assignments/<kind>/<target>keys on a key, else admintake it off a target
GET /v1/guardrails/<id>/statusreadeach budget: limit_usd (raised by an active grant), base_limit_usd, grant_id, spent_usd, remaining_usd, ready, period, resets_at, and suspended with suspended_message while a filter makes the budget count nothing (budgets and dedicated endpoints). Paged by assignment: 25 a page (limit up to 50), and next_cursor names the next page (?cursor=). A guardrail's shared budgets are one row (target_kind group)
GET /v1/guardrails/eventsreadrequests a guardrail refused, newest first

target_id is the organization's id, a workspace id, a member's email or a key id. An organization has at most 200 guardrails. A new key made by rotation gets the old key's guardrail, and both keys share its budgets during the grace period.

GET /v1/guardrails/events takes since, until, action, reason, key_id, guardrail_id, limit (100, up to 500) and cursor. Each event has event_id, key_id, request_id, guardrail_ids, action, reason, model, detail and created_at, and the answer has next_cursor. An event stands for detail.count equal refusals of about a minute (Guardrail events). Events never hold request text.

Grants

A grant is a time-boxed exception to a budget, a key limit or a guardrail's model rules (Grants).

RouteScopeWhat it does
GET /v1/grantsread?status=, state= (pending, scheduled, active, expired, rejected, cancelled), target_type=, key_id=, guardrail_id=, limit=
GET /v1/grants/<id>readone grant and its events
POST /v1/grantsadminrequest one; requested_by names a member who asked
POST /v1/grants/<id>/approveadmin{"note": "..."} optional
POST /v1/grants/<id>/rejectadmin
POST /v1/grants/<id>/canceladminend a pending or approved grant

A request:

bash
curl https://api.routerplus.com/v1/grants \
  -H "Authorization: Bearer $TM_MANAGEMENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target": {"type": "budget", "guardrail_id": "'$GUARDRAIL_ID'", "budget_id": "monthly"},
       "change": {"limit_usd": 800},
       "expires_at": "2026-12-01T00:00:00Z",
       "reason": "launch week"}'
  • target.type is budget (with guardrail_id, budget_id and, optionally, target_kind and target_id for one assignment), key_limit (with key_id) or guardrail (with guardrail_id). change is {"limit_usd": ...}, or {"allow_model": ...} for guardrail.
  • starts_at is optional (now). expires_at is at most 90 days after it.
  • The grant waits, pending, until an owner or admin decides. Whoever asked for it cannot approve it (403 self_approval): not the same management key, the same API key, or the member behind them. A request that names a member with requested_by has two such parties: the member, and the management key that sent it. Neither approves it.
  • One requester may file 10 requests an hour, and an organization may have 50 pending. Past that, the answer is 429 rate_limit.
  • A grant that is missing, or already decided, answers 404 or 409 at once.
  • At most 8 approved guardrail grants whose allow_model has a * or ? apply to one guardrail. Approving one more answers 409 too_many_pattern_grants.

An API key manages its own grants with GET /v1/key/grants, POST /v1/key/grants, GET /v1/key/grants/<id> and POST /v1/key/grants/<id>/cancel, called with the key itself (tm_vk_). It may ask to raise its own limit, or to change a guardrail that covers it; it never approves. One API key may have 5 pending requests, and all API keys of the organization

  1. 40
    An API key may make 20 requests and cancels a minute, and all API keys of the organization together may file 20 requests a minute and 60 an hour; past that, 429 rate_limit. A key made by a rotation lists, reads and cancels the requests the old key filed.

Organization settings

RouteScopeWhat it does
GET /v1/orgreadhipaa_mode, request_log, request_log_until, retention_days, parallel_monitors (the Parallel monitors that turning HIPAA mode on would cancel)
PATCH /v1/orgadminchange them
  • hipaa_mode: true turns HIPAA mode on at once, and sets request_log to "off" with it. When the organization has Parallel monitors, true needs "confirm": "cancel parallel monitors": HIPAA mode cancels them for good. Without it the answer is 409 confirmation_required with their number. false needs "confirm": "turn off hipaa mode", else 409 confirmation_required; request_log stays "off".
  • request_log: only "off", which opts the organization out of the request log.
  • retention_days: 1 to 400, or null for the defaults. It shortens how long guardrail events are kept. A value above 400 is refused: it would be longer than the default.

A change applies to the next request on every gateway.

Errors

The management API answers its own errors as {"error": {"code": <HTTP status>, "message": "...", "type": "..."}}:

StatustypeMeaning
400invalid_request, invalid_expiry, cannot_delete_selfa field, a time, a query parameter or a cursor is wrong; the message names it. invalid_expiry: an expires_at that is not in the future
400invalid_guardrail, unsupported, grant_not_raise, grant_too_longa field of a guardrail or a grant is wrong; the message names it
400ip_allowlist_unavailableIP allowlists are not on in this region yet
401unauthorized, key_expiredno management key, an unknown or revoked one, or an expired one
401key_creator_inactivethe member who made this management key is no longer the owner or an admin of the organization
403insufficient_scope, scope_not_delegable, wrong_regionthe key lacks the route's scope; a management key tried to give admin; another region's organization
403forbidden, self_approvalan API key asked about another key's grant; the requester tried to approve its own grant
404not_foundno such route, or no such key in your organization
405method_not_allowedthe path exists with another method
408request_timeoutthe request body did not arrive in time; the connection is closed
409conflict, key_disabled, workspace_disabled, principal_disabledfor example changing or rotating a revoked key, or a new key in a workspace whose default principal is disabled (name an enabled principal_id)
409key_limit_reachedthe organization has as many keys as it may; revoked and expired keys count, and the console's own. A rotation is refused too
409management_key_limit_reachedthe organization has as many management keys as it may; revoked ones count
409key_rotated_outexpires_at of a key that a rotation replaced, after its grace period
409hard_limit, grant_not_pending, grant_not_open, grant_expired, too_many_pattern_grants, confirmation_required, spend_history_building, budget_filter_unavailablea grant on a hard limit, a grant that is already decided or has ended, a ninth pattern grant on one guardrail, HIPAA mode off without the confirmation, a budget whose spend history is not whole yet (when a budget can be set), or a budget filter that an organization with a dedicated endpoint cannot use (budgets and dedicated endpoints)
413payload_too_largea body over 256 KB
429key_rate_limitedthe organization created as many keys this hour as it may; retry-after says when the next one fits
429write_rate_limitedthe organization made as many management writes that changed something this minute or this hour as it may (revokes have an allowance of their own); retry-after says when the next one fits
429rate_limittoo many grant requests (see Grants)
429rate_limitedthe organization sent more management requests, or its keys read their status more often, than one gateway takes in a minute, or too many of its request bodies are being read; wait retry-after
503unavailablethe API could not read or write its data just now, or two changes to the organization met: nothing was changed; wait retry-after and send it again. Or a change that takes access away is saved and the region's gateways could not be told at once: the message says so, nothing needs to be sent again, and a repeat answers 200
429, 503(as on every route)the region is busy: wait retry-after

These refusals are the management API's own. An API key's own errors on a model call (key_expired, ip_not_allowed, insufficient_quota) are in Errors.

For agents: /docs/api-management.md and /llms.txt