DocsReference
Keys, teams and the management API
Roles, projects, scopes, IP allowlists, scheduled rotation, leaked keys and the key-management API.
Keys belong to an account. An account can have a team, projects that group its keys, and keys narrowed to what they need. A separate kind of key manages keys from code. Every change is in the account's activity log.
Teams and roles
The owner or an admin invites people by email. The link works once, expires after 7 days, and needs a sign-in with that email address, confirmed. An account has up to 50 members and pending invitations. Each person also keeps their own account and switches between accounts in the console.
| Role | Can |
|---|---|
| Viewer | Reads keys, usage, the activity log, projects and members. |
| Developer | Also creates, edits, rotates, disables and deletes keys in every project, and reveals scheduled rotations. |
| Admin | Also invites and removes members, changes their roles, manages projects and management keys, and reads billing. |
| Owner | Also changes the plan, billing and overage, and deletes the account. One per account: whoever created it. |
Keys belong to the account, not to the person who made them, so they keep working when that person leaves. Management keys are the exception. When their maker leaves or stops being an admin, they are disabled.
Projects
Every key is in a project. Keys made before projects existed, and keys made without choosing one, are in the account's Default project. Usage can be filtered by project; it follows each key's current project. A project can be deleted once it has no keys. An account has up to 20 projects.
Scopes
A key gets every scope unless you narrow it. A call outside the key's scopes answers 403 insufficient_scope, and required_scope names the missing scope. Purposes, vocabulary and licenses are open to every key. An endpoint added later needs every scope until it is given its own.
| Scope | Endpoints |
|---|---|
resolve | /v1/resolve |
plan | /v1/plan |
models | /v1/models, /v1/models/{id} |
usage:read | /v1/key (the key's own limits and usage) |
IP allowlists
A key can be limited to IPv4 and IPv6 addresses or CIDR blocks, such as 203.0.113.0/24 or 2001:db8::/32. The address checked is the one our load balancer saw, which is the last entry of X-Forwarded-For, so a client can't claim another address by sending the header. An IPv4 address seen over IPv6 (::ffff:203.0.113.7) matches its IPv4 block. A call from elsewhere answers 403 ip_not_allowed. A management key's allowlist applies only to that management key, and it is empty unless you set one.
Scheduled rotation
A key can rotate every 30, 90, 180 days. When it is due, we make a replacement with the same project, scopes, allowlist, cap and schedule. The old key keeps working for 7 more days. The owner, the admins and the key's creator get an email with a link to the console, never the key. The new key's secret is created when a signed-in developer, admin or owner of the account opens the link, and it is shown once. The link works once and expires with the old key. If it expires unused, rotate the key by hand.
Key-management API
An admin creates a management key (nnm_mgmt_…) in Settings, up to 5 per account. It works like OpenRouter's provisioning keys. It can call only /api/v1/manage, and live and test keys can't call it; the wrong kind of key answers 403 wrong_key_kind. It is limited to 60 requests a minute. It is held to the same plan limits as the console, and its changes appear in the activity log under its name.
It lives under /api/v1/manage, not /v1. /v1 is the metered resolution API: every request there is a live or test key, and it only changes in additive ways. The management API has its own key kind, its own version and its own spec at /api/v1/manage/openapi.json.
| Operation | What |
|---|---|
GET /api/v1/manage/keys | The account's live and test keys (not management keys), newest first. `project` narrows to one project. |
POST /api/v1/manage/keys | Creates a live or test key in a project (the default project when `project_id` is left out), with optional scopes, IP allowlist, expiry, monthly cap and rotation schedule. The answer is the only time `secret` is sent; it is `Cache-Control: no-store`. Counts against the plan's key limit (409 key_limit_reached). |
GET /api/v1/manage/keys/{id} | One key of the account. |
PATCH /api/v1/manage/keys/{id} | Changes the fields sent: name, note, monthly cap, project, scopes, IP allowlist or rotation schedule. |
POST /api/v1/manage/keys/{id}/disable | Calls with the key fail until it is enabled again. |
POST /api/v1/manage/keys/{id}/enable | Enables a disabled key. A key disabled by Nanner Tech or reported as leaked can't be enabled (403 disabled_by_admin, 409 key_leaked). |
POST /api/v1/manage/keys/{id}/rotate | Issues a new secret; the old one keeps working for `grace` (none, 1h, 24h or 7d). Usage, project, scopes and allowlist carry over. |
DELETE /api/v1/manage/keys/{id} | Calls with the key fail at once. Its usage history and audit entries stay. |
GET /api/v1/manage/projects | The account's projects, the default first, with key counts. |
GET /api/v1/manage/usage | Calls per day, endpoint and status class over `range` (24h, 7d, 30d or 90d), for the account, one `project` or one `key`. |
curl -X POST https://rhumbdata.com/api/v1/manage/keys \
-H "Authorization: Bearer $NNM_MANAGEMENT_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"worker-eu","scopes":["resolve"],"ip_allowlist":["203.0.113.0/24"],"rotation_days":90}'Leaked keys
Every key has the form nnm_<live|test|mgmt>_<id>_<secret> with a checksum, so scanners can find keys and tell real ones from typos. We're registering with GitHub's secret scanning partner program. Once that is done, a key pushed to a public repository is reported to us. Anyone who finds a key can also report it, with no sign-in. A reported key that is real is disabled, the activity log records it, and the owner and admins get an email. A leaked key can't be enabled again; rotate it for a new secret. The answer is the same whether or not the key is real, and reports are rate-limited per address.
curl -X POST https://rhumbdata.com/api/leaked-keys \
-H "Content-Type: application/json" \
-d '{"key":"nnm_live_…","source":"https://example.com/where-it-was"}'What never changes
- A key is shown once, when it is created, rotated or revealed. We store only an HMAC of its secret.
- Keys go in
Authorization: BearerorX-API-Key. A key in a URL is refused. - No key is ever logged or sent by email.