Skip to main content
The Orgo API accepts several authentication methods. The one you should use depends on who is calling and from where. 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.
The discovery document is per tenant: issuer reflects the host you fetched it from. Fetch discovery once per tenant host. Never reuse one tenant’s document for another.

Registering a client

Two kinds of client, differing in whether a secret exists.
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.
PKCE is mandatory for every client, confidential ones included. This is stricter than baseline OAuth 2.0 and matches OAuth 2.1. The server rejects violations with invalid_request:
  • code_challenge_method must be S256. plain is not supported.
  • code_challenge must match [A-Za-z0-9-._~]{43,128}.
  • code_verifier must match the same charset and length rules.
If the member is not signed in, Orgo sends them through login and returns them to the consent screen automatically — including when they sign in with Google, Apple or Microsoft. Your app does nothing for this.

Step 2 — exchange the code

Confidential clients additionally authenticate — see Client authentication below. redirect_uri must exactly equal the one used in step 1.

Step 3 — refresh

Refresh tokens rotate. Every refresh returns a new refresh_token and invalidates the one you sent. Persist the new value on each refresh — a client that keeps replaying its original refresh token will stop working.

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_id in the Basic header disagreeing with the body → invalid_request.
  • A confidential client omitting its secret → invalid_client (401, with WWW-Authenticate: Basic realm="orgo-oauth").
Token-endpoint errors are RFC 6749 shaped and sent with Cache-Control: no-store:
Codes in use: 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

Returns an OIDC-shaped profile (sub, name, email, …). The same token works against the rest of the API:
Access tokens are RS256 JWTs, verifiable against jwks_uri.
Tokens are also revocable server-side, and revocation takes effect on the next request. A token that still looks valid locally — unexpired signature, exp in the future — can be rejected. Do not treat a local expiry check as proof a token still works; handle 401 at any time.

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:
The gate is by HTTP method, not intent. An endpoint that performs a read via POST — some search endpoints do — is still blocked for a read-only app. If your integration needs those, it must be registered with acting enabled.
The consent screen shows the member which of these applies before they approve.

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.
Request only the scopes you need — it keeps the consent screen short and makes it clearer to the member what they are approving. Full walkthrough: Integrate “Log in with Orgo”. Admin-side setup: OAuth Server.

Api-Token (server-to-server)

Generate a token at Settings → Developers → API Access. Send it as a raw header — there is no Bearer prefix and no encoding:
Tokens carry one of two access levels:
  • Read/write — full access to whatever the owning user can do.
  • Read-only — any POST, PATCH, PUT, or DELETE is rejected with 403 Forbidden. Useful for analytics integrations.
Tokens inherit the permissions of the user that created them. If the user loses a role, the token loses the same access. Revoke a token by deleting it in the same UI — it stops working immediately.
Tokens grant durable access. Never commit them, log them, or send them to a client browser. Rotate periodically and prefer one token per integration so you can revoke individually.

JWT (interactive login)

For sessions started by a user typing email + password, exchange those credentials for a JWT:
Response:
Send the JWT on every subsequent call:
JWTs are valid for approximately 11 days. After expiry, exchange the refresh token at 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:
OTP requests are rate-limited per email — typically 20 requests per 60 seconds — to prevent abuse. From there, the returned JWT is used exactly like the password-login JWT. OTP is also the auth method used by the event-app surface (members logging in from inside an event landing page) and for password resets.

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:
This authenticates the contact for that specific event and that specific contact identity — they cannot use the hash to access other contacts’ data. Most server-to-server integrations will not use this; it is here for completeness.

Legacy SSO (deprecated)

Deprecated. The /api/v1/*-sso token exchange is deprecated in favour of OAuth 2.0. Existing integrations continue to work and no removal date has been set. New integrations must use the authorization code flow. Responses from these endpoints now carry a Deprecation header and a Link: rel="deprecation" pointer to this section.
If you arrived here from a Deprecation 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

  1. Keep your existing client_id and client_secret. The secret hash format is unchanged, so no rotation is required to migrate.
  2. Replace the request-token-sso redirect with GET /authorize, adding PKCE.
  3. Replace verify-success-token-sso with POST /api/v1/oauth/token.
  4. Replace the profile payload from verify-success-token-sso with GET /api/v1/oauth/userinfo.
  5. 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

Api-Token. Read-only if you only need to fetch data; read/write if you need to create or update.
JWT. Call POST /api/v1/login-check with the user’s credentials, store the returned JWT, and send it on every subsequent call.
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.
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.
OTP. Two-step: request-login-otp then verify-login-otp. Returns a JWT.
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.