Choose an integration mode
Almost every integration question resolves once you know which credential your code carries. Pick from this table, then read the matching guide.
| You are building | Credential | Guide |
|---|---|---|
| A browser app that signs users in | web OIDC client, authorization code + PKCE | Web app |
| A CLI, desktop, or mobile app | native OIDC client (public, no secret) + PKCE | Native and CLI |
| A backend calling another service as itself | service OIDC client, client credentials | Machine to machine |
| An API that receives Gate tokens | No credential — verify against JWKS | Verifying tokens |
| A backend that administers Gate | Service API key (gate_sk_…) | Service API keys |
The distinction that matters
There are two fundamentally different things you might mean by "integrate with Gate."
Acting on behalf of a user. Your app sends someone to Gate, they sign in, and you receive tokens
that represent them. Use an OIDC flow. The token's sub is a Gate user id, and your API decides
what that user may do.
Acting as yourself. Your backend needs to read configuration, mint credentials, record usage, or manage billing. No user is involved. Use a service API key, or client credentials if you need a short-lived bearer token that another service can verify.
Mixing these up is the most common integration mistake. A service API key can never represent a user;
requireUserPrincipal rejects it with 403 A user session is required on every endpoint that mints
credentials.
Which endpoints refuse service accounts
These require a real user session, because they issue credentials or long-lived secrets:
/projects/{projectId}/service-accounts/projects/{projectId}/service-accounts/{accountId}/disable/projects/{projectId}/oidc-clients/projects/{projectId}/oidc-clients/projects/{projectId}/oidc-clients/{clientId}/disable/environments/{environmentId}/stripe-connectionEvery organization endpoint also requires a user session.
Token shapes at a glance
| Authorization code | Client credentials | Service API key | |
|---|---|---|---|
| Access token lifetime | 600 s | 300 s | Until revoked or expired |
| Refresh token | With offline_access, 30 days | Never issued | Not applicable |
| Represents | A user | A client | A service account |
| Verified by | Your API, against JWKS | Your API, against JWKS | Gate, on every request |
| Sent as | Authorization: Bearer | Authorization: Bearer | X-API-Key |
Cross-origin constraints
Browser callers must originate from an allow-listed origin. Gate hard-codes the list in
src/server/origins.ts: its own GATE_BASE_URL, plus https://shelltime.xyz,
https://rawback.app, https://athena.annatarhe.com, and https://athena.annatarhe.cn. This
applies to both CORS on /api/v1 and better-auth's trusted origins.