Skip to main content
All endpoints live under the environment’s canonical auth.* issuer origin. No client-facing endpoint points at the underlying engine — even /.well-known/* is served from the issuer. Examples below use the production base URL https://auth.bbrands.io; substitute the per-environment base:

Conventions

  • Content-Type: application/json, except OAuth endpoints that use application/x-www-form-urlencoded.
  • Transport: HTTPS only (TLS 1.2+).
  • Caching: Cache-Control: no-store on every OAuth endpoint.
  • Errors: OAuth endpoints return {"error": "...", "error_description": "..."}.
  • Correlation: every response carries X-Request-Id.

Discovery

GET /.well-known/openid-configuration

Returns the OIDC discovery document. Public, no auth. Point your client’s auto-configuration at this URL.
The iss in discovery and in every issued access token is the canonical per-environment auth.* URL — never the underlying engine URL. The registration_endpoint is intentionally not exposed: dynamic client registration is off; clients are onboarded through the identity admin console.
id_token_signing_alg_values_supported lists ["ES256"] by default and ["ES256", "RS256"] on environments where the RS256 id_token lane is enabled. ES256 is always the default algorithm; RS256 only applies to clients registered with id_token_alg: "RS256" (e.g. Odoo).

GET /.well-known/jwks.json

Returns the public keys to validate OAuth JWTs. Public. Served by the IdP (proxy/cache of the engine’s JWKS); the client never sees the engine URL.
Response headers
JWKS applies only to OAuth tokens. The internal HS256 B Brands JWT is validated with a symmetric server-side secret and is never exposed here.
On environments with the RS256 lane enabled, the document contains the engine’s EC P-256 key plus the IdP’s RSA public key (both with kid). Verifiers resolve keys by kid, so the extra key is transparent for ES256 clients.

Authorization endpoint

GET /api/v3/oauth/authorize

Starts the authorization-code flow. Called by the user’s browser after a redirect from the client. The request is validated strictly, forwarded to the engine, and the browser is redirected to the consent UI on the same auth.* origin.
string
required
Must be code.
string
required
The registered OAuth client identifier (UUID provided at onboarding).
string
required
Must exactly match one of the client’s registered redirect URIs.
string
required
Space-separated list. Must include openid. Supported: openid email profile.
string
required
Client anti-CSRF value, echoed back on the redirect.
string
required
PKCE challenge. PKCE is mandatory for every client.
string
required
Must be S256 (plain is rejected).
string
Recommended. ID-token anti-replay value.
Response: 302 redirect to https://auth.bbrands.io/oauth/consent?... (the consent UI). On a valid session and an enabled first-party client, consent auto-approves and the browser is redirected back to the client with ?code&state. Validation errors are returned as 400 JSON (no redirect, since the redirect_uri cannot be trusted before validation): Served by the authentication app (not an API). It validates the B Brands session (redirecting to login with ?next= when missing), shows the client name and requested OIDC scopes, and offers Allow / Deny. first_party + enabled users are auto-approved.

POST /api/v3/oauth/decision

Records the user’s consent decision for audit. It does not continue the OAuth code flow (the engine does, via the authorize_url).
string
required
Stable client handle, e.g. erp-prod.
string
required
approve or deny.
string[]
Requested scopes.
string
User identifier.
A missing client_handle or an invalid decision returns 400.

Token endpoint

POST /api/v3/oauth/token

Exchanges an authorization code for tokens, or refreshes an access token. Called server-to-server by the client. The gateway proxies the request to the engine, which runs the access-token hook (enablement check + iss/aud rewrite). Headers: Content-Type: application/x-www-form-urlencoded and Authorization: Basic <base64(client_id:client_secret)> (or the secret in the body for client_secret_post).
The access token carries the canonical identity claims — validate it against the issuer and the JWKS:
aud is the fixed IdP audience bbrands-idp; the requesting client is identified by https://bbrands.io/client_handle (and client_id). Validate iss, the ES256 signature via JWKS, and that client_handle matches your own handle. The id_token keeps the engine’s issuer/audience and is intended for the nonce check and profile claims; use the access token as the identity assertion.
RS256 id_token lane: clients registered with id_token_alg: "RS256" (e.g. Odoo with the OCA auth_oidc module) receive the same id_token re-signed with the IdP’s RSA key — header alg: RS256 with a kid that resolves through /.well-known/jwks.json, every claim preserved (nonce, at_hash, exp, …). The access token is always ES256.
Typical errors (OAuth mapping):

UserInfo endpoint

GET /api/v3/oauth/userinfo

Returns the user’s identity claims, enriched with the business-account dimension. Proxied to the engine’s userinfo. Headers: Authorization: Bearer <access_token> (required; a missing header returns 401 invalid_token with WWW-Authenticate: Bearer).
No permissions/roles claim. UserInfo is identity plus tenancy context: the https://bbrands.io/profile object carries the account linked to the user through the primary_user role (account), that account’s external system id (odoo, null until the account is mapped by the migration API) and the subject (user, mirrors sub).

Revocation endpoint

POST /api/v3/oauth/revoke

Invalidates a token (RFC 7009), typically on logout. Adds the token jti to the local blacklist store and proxies revocation to the engine. Body (application/x-www-form-urlencoded): token, token_type_hint (access_token | refresh_token), client_id.
Per RFC 7009 the response is always 200 and opaque, regardless of whether the token existed.

Custom error codes

Beyond standard OAuth errors, B Brands-specific endpoints may return these in error.code:

Rate limiting

Responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset; 429 carries Retry-After.