Skip to content
v0.3

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.

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_expires has to be asked for explicitly, which is the right way round.

Or through the port, with iam.api_key.create.

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.

Terminal window
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”.

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:

  1. Rotate. You get a new token; the old one still works.
  2. Deploy the new token wherever it lives.
  3. 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”.

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.

{ "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.

  • 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.