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 usageFor almost every service, read and write are the two actions the API enforces (the interactive-session scopes are a separate class — see below). 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 most of the other verbs the catalog carries (action, create, update, delete, list): they can be granted, but the services check :read and :write. Grant those.
The interactive-session scopes are a class of their own. sessions:forward, sessions:pty and sessions:write are real scope strings, because a session is its own privilege rather than an operation on the resource it reaches. Nothing else stands in for them: compute:action is not the scope that opens a shell on an instance, and kubernetes:* is not the one that opens the cluster-API proxy. Grant them to any key that runs fm forward, fm database <type> console, fm shell or fm kubernetes cluster proxy.
They are checked when the key is created: you cannot give a key a scope your own role does not have, so sessions:pty needs the Owner, Administrator, Developer or Member role. They are not yet checked when a session is opened — today a key that lacks them can still open one. Grant them now, so your keys are already right when that check goes live.
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 needs a signed-in person, not a key. Deleting an organization, changing a member's role, removing a member, leaving an organization and transferring ownership are refused for every API key, whatever the key is scoped to — even
*. A key holdingorganizations:writegetsUSER_SESSION_REQUIRED; a key without it getsinsufficient_scopefirst. Do these in the portal while signed in. Leaving is not in the portal yet, so ask an admin or the owner to remove you (an admin must ask the owner); an owner must transfer ownership first and then ask the new owner. Other organization changes, such as inviting members, needorganizations:writeon the key as well as an organization role that allows them. - A key reaches one organization: the one that owns its tenant. Even if you belong to several organizations, a key created in a tenant of one of them cannot read or change another. Listing organizations with the key shows only that one, and any other organization is refused with
ORGANIZATION_ACCESS_DENIED. Create a key in a tenant of each organization you automate. - Most
fm tenantcommands needorganizations:read. Listing tenants, switching withfm tenant useand reading default tags withfm tenant default-tags getneedorganizations:read. Creating or renaming a tenant, or changing its default tags, needsorganizations:write, and creating one without--organizationalso needsorganizations:read.fm tenant currentneeds neither.fmand the portal cannot change an existing key's scopes (the Terraformfrostmoln_api_keyresource can update a key it created): create a new key, switch to it withfm auth api-key set, then revoke the old one withfm account api-key revoke. - Quota change requests need
quotas:write. Submitting or cancelling a request withfm tenant quota requestneedsquotas:write, and listing requests needsquotas:read.compute:writeandcompute:*do not grant it: an approved tenant raise lifts a spending ceiling, which is a different grant from creating resources. Existing keys did not get the new scope, so automation that submits quota requests needs a new key withquotas:write. A workload identity can holdquotas:readbut neverquotas:write. Reading the quotas themselves (fm tenant quota, thefrostmoln_tenant_quotasdata source) needs neither.
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 |
| Request quota changes | quotas:read, quotas:write |
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.