Authentication
Spend has no passwords of its own. Each surface uses a different credential, and each is scoped to one tenant.
| Surface | Credential | Where it comes from |
|---|---|---|
REST API (/api/*) | Authorization: Bearer <session token> | Single sign-on, a passkey, or the sandbox’s POST /api/session |
MCP server (/api/mcp) | Authorization: Bearer <agent token> | Administration › Connected agents, shown once |
SCIM (/api/scim/v2) | Authorization: Bearer <SCIM token> | Administration › Sign-in and provisioning, shown once |
| Email intake | x-spend-intake-secret header | INTAKE_EMAIL_SECRET on the server |
| Teams intake | Teams outgoing-webhook HMAC | TEAMS_INTAKE_SECRET_<TENANT> |
| E-invoice delivery | x-spend-einvoice-secret header | EINVOICE_INBOUND_SECRET_<TENANT> |
| Webhooks you receive | x-spend-signature HMAC | The subscription’s secret, shown once |
Sessions
Section titled “Sessions”A session token is 192 random bits, valid for 8 hours by default (SESSION_TTL_MINUTES) and revocable with POST /api/session/logout. The server stores only a SHA-256 hash of it, so a copy of the database holds no usable sessions.
Single sign-on
Section titled “Single sign-on”- Send the browser to
GET /api/auth/sso?tenant=<tenantId>&returnTo=/. The tenant’s configuration decides whether that is OpenID Connect or SAML. - After the identity provider signs the person in, Spend redirects to
/sso/complete#token=...&expiresInMinutes=...&returnTo=.... The token travels in the URL fragment, which never reaches server logs.
GET /api/auth/providers tells a client which tenants have single sign-on, whether the sandbox sign-in is on, and whether passkeys are.
Passkeys
Section titled “Passkeys”POST /api/auth/passkey/options returns a WebAuthn challenge; POST /api/auth/passkey/verify with the device’s assertion returns a session like any other. Passkeys are bound to the host in PUBLIC_URL.
Permissions
Section titled “Permissions”Every call runs as the person (or agent) the token belongs to. Role permissions are checked on each action, and segregation of duties is checked against the ledger history of the record, so the API can never do more than the same person could do in the web app.
Service integrations
Section titled “Service integrations”For system-to-system reads, prefer webhooks or the event stream and the security feed, called with a session for a dedicated service account that holds only the audit or integration duty.