Two quick rules of thumb:
- Building an app that logs members in as themselves? Use OAuth 2.0. It is the supported path for third-party apps and agents.
- Running a backend job that acts as itself, with no end user in the loop? Use an Api-Token.
Every URL below is on your tenant’s host. The host is the tenant selector — the bare
app.orgo.space domain resolves no tenant and returns 404. Examples use acme.orgo.space; substitute your own. See Tenancy.OAuth 2.0 (third-party apps and agents)
Orgo is a standards OAuth 2.0 authorization server with OIDC discovery. Use the authorization code flow with PKCE.Discovery
Read the metadata rather than hardcoding URLs:
The consent screen sits at
/authorize while the machine endpoints sit under /api/v1/oauth/*. That asymmetry is intentional — use the URLs exactly as discovery returns them.
Registering a client
Two kinds of client, differing in whether a secret exists.- Public (self-registered)
- Confidential (admin-created)
Any developer can register a public client. No authentication required, rate limited to 10 registrations per hour per IP:Returns
201 with a client_id, client_id_issued_at, and your echoed metadata. No client secret is issued — PKCE is the only client authentication.Optional metadata: client_uri, logo_uri, policy_uri, tos_uri. When no scopes are requested the defaults are profile and email.Step 1 — send the member to the consent screen
Step 2 — exchange the code
redirect_uri must exactly equal the one used in step 1.
Step 3 — refresh
Token lifetimes
Authorization code rules
- Single use. Redeeming twice fails with
invalid_grant. - A replay by an authenticated client revokes the tokens that code produced. This is the OAuth 2.1 interception defence — never retry a code blindly after a successful exchange.
- A failed client authentication leaves the code redeemable, so a client that fixes its credentials can safely retry the same code.
Client authentication
Use exactly one method:
- Credentials sent by more than one method →
invalid_request. - A
client_idin the Basic header disagreeing with the body →invalid_request. - A confidential client omitting its secret →
invalid_client(401, withWWW-Authenticate: Basic realm="orgo-oauth").
Cache-Control: no-store:
invalid_request, invalid_client, invalid_grant, unsupported_grant_type, rate_limit_exceeded.
Wrong secrets are throttled. Repeated failed client authentications for the same client_id are rate limited: after 10 failures in 5 minutes, further failures return 429 rate_limit_exceeded with a Retry-After header instead of 401. A request presenting the correct secret is never throttled, however many failed attempts preceded it — so a healthy client is unaffected, and a 429 here means your credentials are wrong, not that you are calling too often.
Using the access token
sub, name, email, …). The same token works against the rest of the API:
jwks_uri.
What actually bounds a token
Two limits surprise most integrators. Neither is configurable per request. An app acts as a member, never as an administrator. A token issued to an app that a tenant admin created is capped to member level (ROLE_USER), regardless of what the authorizing member can do in the Orgo UI. If a tenant administrator authorizes your app, your token still cannot perform administrator actions.
Read-only apps are gated by HTTP method. Every app is flagged either read-only or permitted to act on the member’s behalf. A read-only app may use GET, HEAD and OPTIONS only; any POST, PUT, PATCH or DELETE is rejected with 403:
Scopes
profile, email, groups and roles are advertised in discovery, shown on the consent screen, and recorded in the token’s scope claim.
Scopes describe what an application requests and what the member approves when they authorize it. What the resulting token can actually reach is bounded by the member-level cap and the application’s read-only / act-on-behalf setting described above.
Api-Token (server-to-server)
Generate a token at Settings → Developers → API Access. Send it as a raw header — there is noBearer prefix and no encoding:
- Read/write — full access to whatever the owning user can do.
- Read-only — any
POST,PATCH,PUT, orDELETEis rejected with403 Forbidden. Useful for analytics integrations.
JWT (interactive login)
For sessions started by a user typing email + password, exchange those credentials for a JWT:POST /api/v1/token/refresh for a new pair.
JWTs are scoped to the tenant the login was made against — calling a different tenant’s host with the same JWT will fail with 401.
OTP (one-time password)
For users who do not have a password — or who prefer a magic-link flow — Orgo can send a 6-digit code by email. Two steps:X-Contact-Hash (anonymous contacts)
When Orgo emails a Contact (someone without a user account) an event invitation or a form link, the email contains a magic URL with a hash. That hash is also the auth header:Legacy SSO (deprecated)
If you arrived here from aDeprecation header, this is the flow you are using. It is a proprietary handshake that predates Orgo’s OAuth support:
verify-success-token-sso returns a flat member profile — id, cardId, firstName, lastName, email, status, localCenter, town, membership dates — alongside a 1-year access token with no refresh token. The access token is minted once per login; re-reading the profile does not mint a new one or invalidate the existing token.
Only the token exchange is deprecated. Creating apps and regenerating their secrets is not deprecated, and tokens issued this way are subject to the same member-level cap and read-only gate as OAuth tokens.
Migrating to OAuth 2.0
- Keep your existing
client_idandclient_secret. The secret hash format is unchanged, so no rotation is required to migrate. - Replace the
request-token-ssoredirect withGET /authorize, adding PKCE. - Replace
verify-success-token-ssowithPOST /api/v1/oauth/token. - Replace the profile payload from
verify-success-token-ssowithGET /api/v1/oauth/userinfo. - Expect a 1-hour access token plus a rotating refresh token instead of the legacy 1-year token — add refresh handling before you switch.
Picking the right method
I'm building an integration that runs on a server I control
I'm building an integration that runs on a server I control
Api-Token. Read-only if you only need to fetch data; read/write if you need to create or update.
I'm building a mobile or web app that logs users in with their Orgo password
I'm building a mobile or web app that logs users in with their Orgo password
JWT. Call
POST /api/v1/login-check with the user’s credentials, store the returned JWT, and send it on every subsequent call.I want users to click 'Log in with Orgo' on my third-party site
I want users to click 'Log in with Orgo' on my third-party site
OAuth 2.0. Self-register a public client at
/api/v1/oauth/register, or have a tenant admin create a confidential one, then run the authorization code flow with PKCE.I'm building an AI agent that acts for a member
I'm building an AI agent that acts for a member
OAuth 2.0, registered with acting enabled. Remember the two bounds: the token is capped to member level, and a read-only app cannot issue any
POST, PUT, PATCH or DELETE — including reads implemented as POST.I want password-less login by email code
I want password-less login by email code
OTP. Two-step:
request-login-otp then verify-login-otp. Returns a JWT.I'm building the receiving end of an Orgo-sent email link for non-members
I'm building the receiving end of an Orgo-sent email link for non-members
X-Contact-Hash. The hash is in the URL Orgo emails; lift it into the header on subsequent API calls.
I already use the *-sso endpoints
I already use the *-sso endpoints
That is the legacy handshake — still supported, now deprecated. Follow the migration steps when you can; there is no removal date.
Common authentication errors
Token-endpoint errors use the RFC 6749 envelope instead — see client authentication.
Related
- Tenancy — getting the tenant context right is half of getting auth right
- Integrate “Log in with Orgo” — end-to-end OAuth walkthrough
- Errors — full error envelope reference
- OAuth Server — admin-side setup for OAuth applications

