Keys

Pick the right kind of key for the caller, rotate it without downtime, and never let one reach function source.

Docs menu

The key kinds

Kind Reaches Made with
Account key Everything an account administers: create and delete apps, manage members, billing account.key.create
App key One app's data and functions, and that app's administration account.app.create, or tenant.key.rotate
Agent token Whatever the signed-in person's role allows, scoped to that person MCP OAuth, or auth.agent_token.create
Publishable key A narrow, public-safe slice of an account key: a browser request from a declared origin only account.key.publish

See accounts-and-apps for how account and app map to each other, and agents for agent tokens.

Least privilege

Give each caller the narrowest credential it needs:

  • A backend that calls one app's API needs an app key. Do not give it an account key. A function running inside the app needs no key because db is already bound to the app.
  • A browser page needs a publishable key scoped to its web origin. Do not expose a secret key in the browser.
  • An agent acting for a person needs an agent token scoped to that person's role.

An irreversible call from an agent session requires a person's approval. List pending calls with agent.approval.list, then use agent.approval.approve or agent.approval.deny for each one.

Rotation

tenant.key.rotate replaces an app's key and returns the new one once; pass overlap_seconds to keep the old key working for a window so a deploy can switch over without a failed request in between.

curl -s -X POST $SUBSTRIO_URL/v1/_tenant/key -H "Authorization: Bearer $SUBSTRIO_KEY" \
  -H 'content-type: application/json' -d '{"overlap_seconds": 300}'

tenant.key.revoke ends the overlap early, by the old key's id, once every caller has switched. An account key is rotated the same way by revoking it (account.key.revoke) and creating a new one (account.key.create); there is no in-place overlap for an account key, so stage the new key everywhere it is used before revoking the old one.

Where to store keys

Never put a key in function source, a committed file, or a URL. Store keys a function needs as secrets with secret.write, and read them as env.<KEY>. Store deployment keys in your CI secret store and pass them to substrio deploy or the CLI as environment variables. Do not commit them.

What to give an agent

An agent that only calls your app's own API needs an account key limited to that app, with a narrow requests_per_minute or request_budget: pass apps and the limit to account.key.create. An agent acting for a signed-in person should use that person's agent token, not a key at all, so every call it makes is scoped to what that person's role already allows and every irreversible call asks that person before it runs.