?system=S02, with omission
targeting original system 1. The resulting token’s binding is its default and
ceiling, so existing unqualified client calls stay in S02. A conflicting explicit
system is unavailable, even for an admin token. Scope, active membership and
document permissions remain additional gates. Use a new token to change systems;
membership removal revokes old credentials and re-admission never revives them.
Prerequisites
- A running suchi instance.
- A user account that can authenticate: a local account with a password, or an OIDC identity. See Service accounts.
curlandjqfor the walkthrough; whatever your app uses in production.
1. Get a token
Three ways. Pick the one that fits your setup.From the browser session
If you’re logged in as a normal user, Settings can create a named Read only or Read & write token. The equivalent API call uses/api/tokens/:
token value now. It is returned exactly once and
never persisted in plaintext — only sha256(token) hits the DB.
From the CLI (email + password)
The direct credential-exchange endpoint is useful for a one-shot setup when OIDC is not configured.documents:read,documents:write
scopes. Use /api/tokens/ from a browser or OIDC session if you need narrower
scopes.
From OIDC
If OIDC is enabled, complete the OIDC login flow in a browser first (cookies get set). Then use the “browser session” recipe above —/api/tokens/ accepts browser and OIDC sessions, while API-token credentials
are intentionally rejected. OIDC deployments do not expose the local
password-exchange endpoint.
2. Attach the token to requests
Two accepted headers:Token is suchi’s preferred scheme. Bearer is accepted for
convenience since many HTTP clients default to it. When OIDC is
also enabled, disambiguation is by shape: suchi tokens are exactly
64 lowercase hex chars; anything else with Bearer falls through
to the OIDC authenticator.
Every response carries an X-Request-Id header — capture it in
your app’s error logs. When you file a bug, that’s the string that
lets us grep the server side.
3. Verify
Quick sanity check:kind=token confirms the auth chain resolved via the API token.
role=member reflects the underlying user’s role.
Now exercise a real endpoint:
401 unauthorized — token is wrong or has been revoked.
If you get 403 forbidden — token’s scopes don’t cover this action;
generate a wider token.
4. Scope narrowing
The closed scope vocabulary:
Browser and OIDC sessions can mint any scope in this vocabulary. API tokens
cannot call the token-management endpoints.
Best practice: generate the narrowest possible scope. A backup cron that only
reads gets
documents:read; an importer that files new documents gets
documents:write.
5. Service accounts
For non-human integrations, use a dedicated identity rather than an admin’s personal account. Create a member with a strong random local password, sign in as that account once, and mint its token. A dedicated OIDC identity works too. Suchi has no user impersonation or admin-side token minting. A local account without a password cannot sign in, and disabling the token’s owner immediately invalidates the token. Store the bootstrap password like any other credential, or remove local-password access after establishing a supported OIDC login.6. Rotation
Tokens have no built-in expiry. Rotate from an authenticated browser or OIDC session by generating a new one and deleting the old: The commands below assumeSUCHI_SESSION contains the signed-in browser’s
suchi_session cookie value.
- Personal integrations: rotate yearly.
- Service accounts: rotate on team turnover or every 90 days, whichever comes first.
- After a suspected leak: revoke immediately, generate a new one, investigate.
7. Revocation
Two paths:- Self-service:
DELETE /api/tokens/{id}from the user’s browser or OIDC session. - Admin override: an admin browser or OIDC session can revoke any token via the same endpoint.
8. Audit trail
Every generation and revocation lands inaudit_events with the session user
as actor, the api_token.create or api_token.revoke action, and the request
id.
Every mutation performed by a token is audit-logged with
Actor.kind = token, Actor.user_id = <the owner>. When you’re
answering “who did X” a week later, the token’s name (recorded
at generation time on the row) makes it obvious which integration was
responsible.
Common pitfalls
- Storing the token in the app’s own DB unencrypted. The token
IS a credential. Treat it like a password. Env vars, k8s
secrets, macOS keychain,
_FILEmount — any of those. - Sharing a single token across N integrations. When one leaks, you rotate everyone. One integration, one token.
- Using an admin’s token for automation. Every action becomes attributable to a human who didn’t do it. Create a service account.
Related
- Connect an MCP client — same token flow, MCP-shaped consumer.
- API reference — the full endpoint list.
- Permissions — ACL layer that sits on top of the auth chain. A token authenticates you; ACLs decide what you can see once you’re through.