Create an API key
Preview Minting a key takes a minute and is where you decide everything it can do: its scopes, an optional resource constraint, and how long it lives. Get this right and the key is least-privilege by construction — it can touch exactly what you intended and nothing more.
What a key sees, and what it can do
Section titled “What a key sees, and what it can do”Before you mint anything, two behaviors decide what your key returns. Lead with these, because they explain responses that otherwise look surprising:
- Missing scope on a resource you can see →
403 insufficient_scope. The resource exists and you’re entitled to know it exists, but this key wasn’t granted the scope for this action. Add the scope (or use a key that has it). - Out of scope on a resource you can’t see →
404 not_found. If a key is constrained to one team or site, anything outside that constraint doesn’t just refuse — it reports as not found. This existence hiding is deliberate: a narrowly-scoped key can’t enumerate what it has no business knowing about. The full rationale is in the security model.
The practical consequence: a 404 from a constrained key often means “outside this key’s
scope,” not “deleted.” Widen the constraint, or use a less-restricted key, to confirm.
Mint a key
Section titled “Mint a key”-
Open the keys page. In app.managed.dev, go to your account settings and open API keys, then Create key.
-
Name the key. Use a name that says where it runs —
ci-deploy-acme,terraform-prod,grafana-readonly. The name shows up in the audit log and on every last-used record, so future-you can tell which key did what. -
Set a TTL. Pick an expiry — the default is 90 days, the maximum is one year. A shorter TTL is safer; rotate before it lapses.
-
Pick scopes. Start from a preset (Read-only, Deploy bot, CI/Terraform, and so on), or search and bulk-select individual scopes. The dangerous scopes —
credentials:*,exec:raw,keys:write— are never in a preset and must be added one at a time, on purpose. -
Constrain the key (optional). Pin the key to a team, a project, or a single site so it can never act outside that boundary. A CI key for one client’s sites should be pinned to that client’s team. You can also add an IP allowlist here.
-
Confirm and copy the secret. The key is shown once. Copy it into your secret store now — you can’t retrieve it later, only revoke and re-mint.
Create a key with POST /v1/api-keys. This requires the keys:write
scope — an isolated scope you grant one key at a time, so minting
keys is itself a privileged action. The plaintext secret is returned only in this
response.
curl -X POST https://api.managed.dev/v1/api-keys \ -H "Authorization: Bearer mfk_live_9aF2…" \ -H "Forge-Version: 2026-06-23" \ -H "Idempotency-Key: 6f1c-…" \ -H "Content-Type: application/json" \ -d '{ "name": "ci-deploy-acme", "preset": "deploy_bot", "ttl_days": 90, "resource": { "type": "site", "id": "site_01J7…" }, "ip_allowlist": ["203.0.113.7/32"] }'{ "data": { "id": "key_01JABC…", "name": "ci-deploy-acme", "prefix": "mfk_live_", "secret": "mfk_live_9aF2…", // shown once, never returned again "scopes": ["sites:read", "deployments:write", "environments:write", "domains:read", "jobs:read", "observability:read"], "resource": { "type": "site", "id": "site_01J7…" }, "expires_at": "2026-09-21T18:04:11Z", "ip_allowlist": ["203.0.113.7/32"] }, "request_id": "req_01J9…"}Note that the preset expanded into an explicit scopes list on the way in — presets are
sugar at mint time, stored expanded, so the stored grant is always
unambiguous.
Choosing scopes deliberately
Section titled “Choosing scopes deliberately”Reach for a preset first — it covers the common shapes (read-only, deploy bot, CI/Terraform, observability, WP automation) and is guaranteed to exclude the dangerous scopes. Drop to custom scopes only when a preset is too broad or too narrow, and remember the down-scoping rule: a key can never exceed the role of the principal that minted it, no matter which scopes you check. See scopes for the grammar and the full intersection rule.