API keys
This resource manages the keys that authenticate every other call. You can list, create, update, and revoke API keys programmatically, roll a key to a new secret with a grace window so nothing breaks mid-rotation, and read your current rate-limit budget. Creating a key reveals its secret exactly once — there’s no way to read it back later.
For what keys are, the credential families (mfk_live_…, mfk_test_…), and how to
mint your first one in the dashboard, see API keys and
creating keys. This page is the API for managing them
once you’re automating.
Authorization
Section titled “Authorization”Two isolated scopes split read from lifecycle:
- keys:read — list and get key metadata (never the secret). A compliance reader can introspect keys without mint/revoke power.
- keys:write — mint, update, revoke, and roll keys.
There is no admin scope. Keys always act as clients, so a key — even one minted by an admin — can never act as platform admin. See the security model.
Endpoints
Section titled “Endpoints”| Method + path | Scope | Does |
|---|---|---|
GET /v1/api-keys |
keys:read |
list your keys (metadata only — never the secret) |
POST /v1/api-keys |
keys:write |
create a key → reveals the secret once |
GET /v1/api-keys/{keyID} |
keys:read |
get one key’s metadata |
PATCH /v1/api-keys/{keyID} |
keys:write |
rename a key and/or narrow its scopes |
DELETE /v1/api-keys/{keyID} |
keys:write |
revoke a key immediately |
POST /v1/api-keys/{keyID}/roll |
keys:write |
rotate to a new secret; the old one stays valid 24h |
GET /v1/rate-limits |
any | introspect your current rate-limit budget |
Create parameters
Section titled “Create parameters”| Name | Type | Required | Description |
|---|---|---|---|
name |
string |
no | a human label, e.g. ci-deploy-bot |
scopes |
string[] |
yes | the scopes to grant — write implies read, admin implies write |
resource |
object |
no | pin the key to one resource: { "type": "site", "id": "site_01J7…" } |
expires_in_days |
integer |
no | TTL in days, 1–365; default 90 |
ip_allowlist |
string[] |
no | exact IPs and/or CIDRs the key may be used from |
Worked example — create a key (reveal-once)
Section titled “Worked example — create a key (reveal-once)”The secret comes back once, in the create response, in the secret field. Store
it immediately; subsequent reads of the key return only its metadata.
curl -X POST https://api.managed.dev/v1/api-keys \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23" \ -H "Content-Type: application/json" \ -d '{ "name": "ci-deploy-bot", "scopes": ["sites:read", "deployments:write", "environments:write", "jobs:read"], "resource": { "type": "site", "id": "site_01J7Q2" }, "expires_in_days": 90 }'name, days := "ci-deploy-bot", 90reveal, err := client.APIKeys.Create(ctx, &forge.APIKeyCreateParams{ Name: &name, Scopes: []string{"sites:read", "deployments:write", "environments:write", "jobs:read"}, ExpiresInDays: &days,})// *reveal.Data.Secret is present only on this response — persist it nowmf keys create \ --name ci-deploy-bot \ --scopes sites:read,deployments:write,environments:write,jobs:read \ --resource-type site --resource-id site_01J7Q2 \ --ttl 2160h{ "data": { "id": "key_01JF8R", "name": "ci-deploy-bot", "secret": "mfk_live_b1c4d8e2f6a0…", // shown once — store it now, it is hashed at rest "prefix": "mfk_live", "last4": "f6a0", "scopes": ["sites:read", "deployments:write", "environments:write", "jobs:read"], "resource": { "type": "site", "id": "site_01J7Q2" }, "expires_at": "2026-09-22T01:10:00Z", "last_used_at": null, "revoked_at": null, "created_at": "2026-06-24T01:10:00Z" }, "request_id": "req_01JAC1"}Updating a key — down-scope only
Section titled “Updating a key — down-scope only”PATCH /v1/api-keys/{keyID} takes a new name and/or a new scopes list. Scopes may
only be narrowed: the new list must be a subset of what the key already holds, and
a broadening scope is a 403. To widen a key’s reach, mint a new key.
curl -X PATCH https://api.managed.dev/v1/api-keys/key_01JF8R \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Content-Type: application/json" \ -d '{ "scopes": ["sites:read", "jobs:read"] }'Rolling a key
Section titled “Rolling a key”POST /v1/api-keys/{keyID}/roll issues a new secret for the same key id while
keeping the old secret valid for a fixed 24-hour grace window. Deploy the new
secret to your runners, confirm traffic has moved over, and the old one expires on its
own — no window where every caller is broken at once.
curl -X POST https://api.managed.dev/v1/api-keys/key_01JF8R/roll \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23"reveal, err := client.APIKeys.Roll(ctx, "key_01JF8R")// *reveal.Data.Secret is the new secret — the old one stays valid for 24hmf keys roll key_01JF8R{ "data": { "id": "key_01JF8R", "name": "ci-deploy-bot", "secret": "mfk_live_9d3a71c0b8f4…", // the new secret — store it now "prefix": "mfk_live", "last4": "b8f4", "scopes": ["sites:read", "deployments:write", "environments:write", "jobs:read"], "created_at": "2026-06-24T01:10:00Z" }, "request_id": "req_01JAC9"}- Roll the key and capture the new
secret. - Deploy the new secret to every caller.
- Confirm the old secret has gone quiet in the audit log.
- Let the 24-hour grace lapse, or
DELETEthe key to cut both secrets off now.
Introspecting rate limits
Section titled “Introspecting rate limits”GET /v1/rate-limits reports your current budget without spending a request against
a real endpoint — useful for a client that wants to back off before it’s throttled.
Buckets are per key by request class (read, write, create), plus a team ceiling
aggregated across your team’s keys. See rate limits for
the headers on every response.
curl https://api.managed.dev/v1/rate-limits \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23"{ "data": { "buckets": [ { "class": "read", "limit": 1000, "remaining": 991, "reset": 1782192060 }, { "class": "write", "limit": 100, "remaining": 100, "reset": 1782192060 }, { "class": "create", "limit": 5, "remaining": 5, "reset": 1782192060 } ], "team_ceiling": { "limit": 5000, "remaining": 4982, "reset": 1782192060 } }, "request_id": "req_01JACF"}