Help Developers
Authentication
Every authenticated request sends Authorization: Bearer <credential>, plus the Substratal-Version header. Which credential you send depends on who's calling.
| Caller | Credential | Lifetime | How you get it |
|---|---|---|---|
| A person, in a Hub app or your own first-party app | Platform access token (JWT) | 15 minutes, refreshed with a rotating refresh token (30 days idle, 90 days absolute) | POST /auth/login, then POST /auth/mfa/verify if two-factor is on |
| Your server or backend job | API key (satk_live_… or satk_test_…) |
Until revoked, or until its optional expires_at |
Created by a person in the Hub (API Keys), or with POST /api-keys |
| A person opening your Application | App token (JWT, aud = your application_id) |
5 minutes | Minted by the Hub at launch (POST /auth/app-tokens) or through the hosted PKCE flow |
Signing in as a person
curl -s https://api.substratalapps.com/auth/login \
-H 'Substratal-Version: 1' -H 'Content-Type: application/json' \
-d '{"email":"dana@example.com","password":"correct horse battery staple"}'
If two-factor is on, the response is a challenge:
{ "mfa_required": true, "mfa_token": "mfa_01JAG…", "methods": ["totp", "recovery_code"], "expires_in": 300 }
Finish with POST /auth/mfa/verify {"mfa_token": "…", "code": "123456"}. Both paths end with the same session: access_token, refresh_token, session_id.
Refresh tokens are single-use. Refresh once, persist the new refresh token atomically, and never run two refreshes in parallel. Reusing a rotated refresh token revokes the whole session and raises a security.session_reuse_detected alert.
API keys are for servers only
- Never put an API key in a mobile app, desktop app or browser. The API refuses a key sent with an
Originheader (400 api_key_in_browser). - A key's secret is shown once, when it's created. Store it in your secret manager straight away.
- An app-scoped key can only reach its own Application's rows. A platform key can't hold more permissions than the person who created it.
- Mark keys used by AI agents as
intended_use: agent. They default torestrict_destructive: true, so an agent can't run destructive operations unless a person turns that on.
Verifying app tokens
Applications verify app tokens locally against the keys at https://api.substratalapps.com/.well-known/jwks.json (cache them for an hour). Check iss, aud, exp and the signature, then read effective_permissions. Subscribe to the access.revoked and role.removed webhooks so access you turn off ends immediately, not when the token expires. That subscription is required.