Issue & rotate API keys
An API key is not a credential belonging to a person. It is its own actor, with a name you choose, and that name is what the audit trail records on every call it makes — including the ones it is refused.
Issue one
Section titled “Issue one”In the admin: Team → API keys → Create.
- Name — this becomes the actor’s name in the audit trail. Name it after the thing, not the person:
ERP Nimbus,BI panel,Warehouse sync. - Scopes — only what it needs. The scopes page lists every name with the commands and reads under it.
- Expiry — 90 days by default.
never_expireshas to be asked for explicitly, which is the right way round.
Or through the port, with iam.api_key.create.
Give it less than you have
Section titled “Give it less than you have”Key creation is fenced subset-of-issuer: you cannot mint a credential carrying more than the one you are minting it with. Privilege does not escalate by going one level deeper, and an integration that mints keys for its own sub-tasks can only ever narrow.
Check what a key actually holds
Section titled “Check what a key actually holds”curl -H "Authorization: Bearer $FORGE_TOKEN" \ https://your-instance.example/v1/read/internal/whoami{ "actor_id": "act_…", "scopes": ["catalog.admin.read", "logistics.read", "order.read"] }The fastest way to settle “is this key the one I think it is”.
Rotate without a flag day
Section titled “Rotate without a flag day”iam.api_key.rotate mints a new credential on the same actor and gives the previous ones an expiry a
while out, rather than killing them.
So the sequence has no gap:
- Rotate. You get a new token; the old one still works.
- Deploy the new token wherever it lives.
- The old one expires on its own at the end of the overlap.
Your integration survives its own deploy, and because the actor did not change, the audit trail stays continuous across the rotation. Nothing in your history says “a different integration started doing this”.
Revoke
Section titled “Revoke”iam.api_key.revoke is immediate and kills every generation of that key at once. Use it when a token
leaked, when an integration is retired, or when you are not sure — a revoked key is cheap to replace and a
leaked one is not.
When something is refused
Section titled “When something is refused”{ "error": { "kind": "forbidden", "message": "read requires the admin.users.write scope" } }The message names the scope. Add it to the key deliberately, or decide the integration should not have it — those are the only two honest answers, and “give it everything to make the error go away” is neither.
Practical rules
Section titled “Practical rules”- One key per integration, not one key shared by four. Rate limits are per credential, so a shared key makes one runaway job everybody’s problem, and revoking it takes down four things.
- Never in the repository. Environment, or your secret store.
- Read-only when it only reads. A reporting key with write scopes is a write incident waiting for a bug.