API Key Scopes
An API key authenticates a program — CI, a script, Terraform — against the Frostmoln API. Every key carries scopes: the list of things it is allowed to do. Frostmoln keys are least-privilege, so a key does nothing until you say what it may do.
The list is served by the platform
There is no fixed list to copy out of this page. The catalog lives in the platform, so it always matches what key creation accepts. Read it on whichever surface you work in:
| Where | How |
|---|---|
| Portal | Settings → API Keys → Create API Key. The Permissions picker lists every scope with a description of what it grants. |
| CLI | fm account api-key scopes (add -o json to script it). |
| Terraform | The frostmoln_api_key_scopes data source — terraform console, then data.frostmoln_api_key_scopes.all.scopes. |
What a scope looks like
A scope is <service>:<action>:
compute:read read compute resources
compute:write create, change and delete them
storage:read read volumes and buckets
billing:read read invoices and usageread and write are the two actions the API enforces today. write covers every mutation — create, update, delete, and actions such as start/stop/restart. There is no separate "create but not delete" scope: when you need that distinction, use an access policy.
Grant read and write, not the finer forms
The catalog also lists three-part entries such as compute:instances:read, and key creation accepts them. They are not enforced as key scopes — they belong to the access policy engine. A key holding only compute:instances:read is created successfully and then denied on every compute request, with a 403 that does not explain why.
The same applies to the other verbs the catalog carries (action, create, update, delete, list): they can be granted, but the services check :read and :write. Grant those.
Wildcards
| Scope | API key | Workload identity |
|---|---|---|
* (everything) | Rejected — WILDCARD_SCOPE_NOT_ALLOWED | Rejected — WILDCARD_SCOPE_FORBIDDEN |
compute:* (one service) | Allowed | Rejected — WILDCARD_SCOPE_FORBIDDEN |
compute:read | Allowed | Allowed |
A Workload Identity binding is deliberately stricter than an API key: a federated pod credential must name what it needs.
Note that compute:* is a genuine grant of everything that service offers, now and in future. Two named scopes are almost always the better choice.
Rules worth knowing
- At least one scope is required. An empty list is rejected with
SCOPES_REQUIRED, and an unrecognised scope withINVALID_SCOPES(on key creation;INVALID_SCOPEwhen updating a key or a workload-identity binding). - A key can never exceed you — at the moment you create it. Scopes are capped at the permissions of the identity creating the key; asking for more fails with
SCOPE_EXCEEDS_PERMISSIONS. This is checked when the key is created or its scopes change. If your permissions are reduced later, keys you already issued keep the scopes they were granted — revoke and re-issue them. - Organization control is not governed by scopes. Deleting an organization, changing members and transferring ownership are authorized by your organization role, not by the key's scopes — a key created by an owner can perform them regardless of what it is scoped to. Do not treat a narrow scope list as protection against organization-level changes: create automation keys from an account that is not an organization owner.
Choosing scopes
Start from the job the key does, not the service it talks to.
| The key does this | Grant |
|---|---|
| Monitoring / inventory collection | compute:read, storage:read, network:read — the reads of what it inspects |
| CI that deploys instances | compute:read, compute:write |
| Restart a service on a schedule | compute:read, compute:write (an action is a mutation) |
| Terraform managing a stack | read and write for each service in the stack — a plan reads before it changes anything |
| Read invoices for accounting | billing:read |
Two habits keep keys safe: give a key one job rather than reusing a broad one, and set an expiry so a forgotten key stops working by itself.
Examples
# See what you can grant, then create the key
fm account api-key scopes
fm account api-key create --name "ci-deploy" \
--scopes compute:read,compute:write \
--expires 2027-01-01data "frostmoln_api_key_scopes" "all" {}
resource "frostmoln_api_key" "ci" {
name = "ci-deploy"
scopes = ["compute:read", "compute:write"]
}The key itself is shown once, at creation. Store it in your secret manager straight away — it cannot be retrieved later, only replaced.
When scopes are not enough
Scopes say what kind of call a key may make, never to which resource or from where. When you need "may create instances but never delete them", "only in one region", "only from the office network", or an explicit deny, use an access policy. That is where the fine-grained service:resource:action operations apply, and a policy attaches to the same API keys.