# Management API

**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](/docs/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`.

| Scope | What a key with it can do |
|---|---|
| `read` | every `GET`: list and read keys, the audit log, the security report, guardrails, budget status, guardrail events, grants, the organization's settings |
| `keys` | `read`, and create, change, rotate and revoke API keys, and put a guardrail on a key or take it off |
| `admin` | everything: management keys, guardrails, grants and the organization's settings included |

Where a management key comes from:

- **The console**, at [Security](https://app.routerplus.com/console/security#management-keys). 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`.

| Route | Scope | What it does |
|---|---|---|
| `GET /v1/management-keys` | `admin` | the 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-keys` | `admin` | a 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>` | `admin` | revoke 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.

| Field | Type | Meaning |
|---|---|---|
| `name` | string, 1 to 80 characters | required |
| `workspace_id` | UUID | the workspace the key belongs to; default: the organization's default workspace |
| `principal_id` | UUID | a principal of the organization the key belongs to (it sets the workspace) |
| `limit` | number (USD) or `null` | the key's spend limit; `null`: none |
| `limit_reset` | `daily`, `weekly`, `monthly` or `null` | when the limit starts again: 00:00 UTC, Monday 00:00 UTC, the 1st 00:00 UTC; `null`: never |
| `expires_at` | ISO 8601 time with its zone (`2027-01-01T00:00:00Z`), or a date (00:00 UTC), in the future | the key stops working at this instant |
| `ip_allowlist` | array of up to 100 CIDR ranges or addresses | the key works only from these addresses; empty or `null`: from anywhere. See [IP allowlist](#what-the-controls-do-on-a-request) for where it is available |
| `labels` | object of up to 20 strings | your own attribution (for example `{"team": "search"}`); keys `a-z 0-9 _ . -`, values up to 200 characters |
| `finance_category` | string, 1 to 64 characters | a cost center; default: the workspace's |
| `rpm_limit` | integer, 1 to 300 | requests 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

| Route | Scope | What it does |
|---|---|---|
| `GET /v1/keys` | `read` | the 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>` | `read` | one key |
| `PATCH /v1/keys/<key_id>` | `keys` | change `name`, `disabled`, `limit`, `limit_reset`, `expires_at`, `ip_allowlist`, `labels`, `finance_category` or `rpm_limit`; `null` clears a setting |
| `DELETE /v1/keys/<key_id>` | `keys` | revoke 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.

| Parameter | Meaning |
|---|---|
| `since`, `until` | ISO 8601 times with their zone (`2026-11-01T00:00:00Z`), or dates (00:00 UTC) |
| `source` | `governance`, `identity`, `connection` or `routing_policy` |
| `action` | for example `key.create`, `key.update`, `key.rotate`, `management_key.create` |
| `actor` | for example `mgmt:tm_mk_3f9c1a`, `console:you@example.com`, `operator` |
| `limit`, `cursor` | 100 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](https://app.routerplus.com/console/audit), with a CSV export.

## Security report

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

| Flag | Meaning |
|---|---|
| `no_limit` | neither a spend limit nor a monthly cap |
| `no_expiry` | no `expires_at` |
| `idle` | not used for 30 days (a key never used: once it is 7 days old) |
| `expiring_soon` | expires within 7 days |
| `no_ip_allowlist` | usable from any address |
| `rotated` | a 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](https://app.routerplus.com/console/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](#grants) and of
[Organization settings](#organization-settings) exist only where guardrails are on
([availability](/docs/guardrails)). Elsewhere they answer as a route the gateway does not serve.

The rules are on the [Guardrails](/docs/guardrails) page. The routes:

| Route | Scope | What it does |
|---|---|---|
| `GET /v1/guardrails` | `read` | every guardrail of the organization, with its assignments |
| `POST /v1/guardrails` | `admin` | create one: `name`, `description` and the rules. `201` |
| `GET /v1/guardrails/<id>` | `read` | one guardrail |
| `PATCH /v1/guardrails/<id>` | `admin` | the fields you send replace the stored ones |
| `DELETE /v1/guardrails/<id>` | `admin` | delete it and every assignment of it |
| `POST /v1/guardrails/<id>/assignments` | `keys` 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 `admin` | take it off a target |
| `GET /v1/guardrails/<id>/status` | `read` | each 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](/docs/guardrails#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/events` | `read` | requests 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](/docs/guardrails#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](/docs/guardrails#grants)).

| Route | Scope | What it does |
|---|---|---|
| `GET /v1/grants` | `read` | `?status=`, `state=` (`pending`, `scheduled`, `active`, `expired`, `rejected`, `cancelled`), `target_type=`, `key_id=`, `guardrail_id=`, `limit=` |
| `GET /v1/grants/<id>` | `read` | one grant and its events |
| `POST /v1/grants` | `admin` | request one; `requested_by` names a member who asked |
| `POST /v1/grants/<id>/approve` | `admin` | `{"note": "..."}` optional |
| `POST /v1/grants/<id>/reject` | `admin` | |
| `POST /v1/grants/<id>/cancel` | `admin` | end 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
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

| Route | Scope | What it does |
|---|---|---|
| `GET /v1/org` | `read` | `hipaa_mode`, `request_log`, `request_log_until`, `retention_days`, `parallel_monitors` (the Parallel monitors that turning HIPAA mode on would cancel) |
| `PATCH /v1/org` | `admin` | change them |

- `hipaa_mode`: `true` turns [HIPAA mode](/docs/data-policy#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": "..."}}`:

| Status | `type` | Meaning |
|---|---|---|
| 400 | `invalid_request`, `invalid_expiry`, `cannot_delete_self` | a 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 |
| 400 | `invalid_guardrail`, `unsupported`, `grant_not_raise`, `grant_too_long` | a field of a guardrail or a grant is wrong; the message names it |
| 400 | `ip_allowlist_unavailable` | IP allowlists are not on in this region yet |
| 401 | `unauthorized`, `key_expired` | no management key, an unknown or revoked one, or an expired one |
| 401 | `key_creator_inactive` | the member who made this management key is no longer the owner or an admin of the organization |
| 403 | `insufficient_scope`, `scope_not_delegable`, `wrong_region` | the key lacks the route's scope; a management key tried to give `admin`; another region's organization |
| 403 | `forbidden`, `self_approval` | an API key asked about another key's grant; the requester tried to approve its own grant |
| 404 | `not_found` | no such route, or no such key in your organization |
| 405 | `method_not_allowed` | the path exists with another method |
| 408 | `request_timeout` | the request body did not arrive in time; the connection is closed |
| 409 | `conflict`, `key_disabled`, `workspace_disabled`, `principal_disabled` | for example changing or rotating a revoked key, or a new key in a workspace whose default principal is disabled (name an enabled `principal_id`) |
| 409 | `key_limit_reached` | the organization has as many keys as it may; revoked and expired keys count, and the console's own. A rotation is refused too |
| 409 | `management_key_limit_reached` | the organization has as many management keys as it may; revoked ones count |
| 409 | `key_rotated_out` | `expires_at` of a key that a rotation replaced, after its grace period |
| 409 | `hard_limit`, `grant_not_pending`, `grant_not_open`, `grant_expired`, `too_many_pattern_grants`, `confirmation_required`, `spend_history_building`, `budget_filter_unavailable` | a 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](/docs/guardrails#when-a-budget-can-be-set)), or a budget filter that an organization with a dedicated endpoint cannot use ([budgets and dedicated endpoints](/docs/guardrails#budgets-and-dedicated-endpoints)) |
| 413 | `payload_too_large` | a body over 256 KB |
| 429 | `key_rate_limited` | the organization created as many keys this hour as it may; `retry-after` says when the next one fits |
| 429 | `write_rate_limited` | the 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 |
| 429 | `rate_limit` | too many grant requests (see [Grants](#grants)) |
| 429 | `rate_limited` | the 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` |
| 503 | `unavailable` | the 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](/docs/errors).
