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 key that does its job:

  • A backend that calls one app's API needs that app's key, never an account key. A function running inside the app needs no key at all: db is already bound to its app.
  • A page in a browser needs a publishable key, scoped to its web origin, never a secret key of any kind.
  • An agent acting for a person needs an agent token (scoped to that person's role), not a raw account key handed to it.

An irreversible call from an agent session waits for a person's approval. List the waiting calls with agent.approval.list and answer each with agent.approval.approve or agent.approval.deny.

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. Keys you must read from a function go through a secret (secret.write, read back as env.<KEY>); keys your deploy pipeline needs go in your own CI secret store and are passed to substrio deploy or the CLI as an environment variable, never checked in.

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.