OAuth Flow Endpoints

These are the OAuth 2.0 protocol endpoints clients use to obtain access tokens and revoke them. They implement RFC 6749 (OAuth 2.0 Authorization Framework), RFC 7009 (Token Revocation), RFC 7636 (PKCE), and RFC 8707 (Resource Indicators).

All endpoints are mounted on the public blueprint and require no bearer token. The locations are also advertised by the OAuth Discovery endpoints so clients can configure themselves automatically.

Endpoints

Method Endpoint Description
GET, POST /oauth/authorize Begin an authorization code flow
POST /oauth/token Exchange a code, refresh token, or credentials for an access token
POST /oauth/revoke Revoke a refresh token

Authorization Endpoint

GET /public/v2/oauth/authorize
POST /public/v2/oauth/authorize

Browser-redirect endpoint that initiates the authorization code grant. The user is asked to log in (if not already authenticated via an OAuth session cookie) and then to grant or deny consent. On success the user-agent is redirected to the client’s redirect_uri with an authorization code query parameter.

Query Parameters

Parameter Type Required Description
client_id string Yes Client identifier issued during client registration
redirect_uri string Yes Redirect URI registered for this client. Must exactly match a registered URI
response_type string Yes Must be code. Any other value redirects with error=unsupported_response_type
scope string No Space-separated list of requested scopes
state string No Opaque value echoed back on redirect so the client can correlate the request and defend against CSRF
resource string No RFC 8707 resource indicator. If supplied, the same value must be sent to the token endpoint and will be bound as the access token’s audience
code_challenge string No PKCE code challenge derived from the client’s code_verifier
code_challenge_method string No PKCE method used to derive code_challenge. plain (default) or S256

Form Parameters (POST)

When the user submits the consent form, the endpoint accepts:

Field Type Description
confirm string yes to issue an authorization code, anything else to deny

Successful Response

The user-agent is redirected to:

{redirect_uri}?code={authorization_code}&state={state}

state is included only when the original request supplied one. The code is single-use and short-lived.

Denial Response

If the user denies access, the user-agent is redirected to:

{redirect_uri}?error=access_denied&state={state}

Error Responses

Status Response Description
400 {"error": "invalid_request"} Missing client_id or redirect_uri
400 {"error": "invalid_client"} Unknown client_id
400 {"error": "invalid_redirect_uri"} The supplied redirect_uri is not registered for this client
302 Redirect with error=unsupported_response_type response_type is not code

Login Flow

If the user is not authenticated when the endpoint is reached, the response is a 302 to /public/v2/oauth/login with the original authorize URL preserved in the next query parameter. After successful login the user-agent is sent back to the authorize endpoint and the consent form is shown.


Token Endpoint

POST /public/v2/oauth/token

Returns a bearer access token. Supports three grant types — authorization_code, refresh_token, and client_credentials. The request body is application/x-www-form-urlencoded.

Client Authentication

The client may authenticate either by including client_id and client_secret in the form body (client_secret_post) or by HTTP Basic auth (client_secret_basic). Both methods are advertised by the discovery endpoint.

Common Form Parameters

Field Type Required Description
grant_type string Yes One of authorization_code, refresh_token, client_credentials
client_id string Conditional Required when not using HTTP Basic auth
client_secret string Conditional Required when not using HTTP Basic auth

The grant type must be listed in the client’s registered grant_types; otherwise the response is 400 {"error": "unauthorized_client"}.

Grant: authorization_code

Exchanges a single-use authorization code obtained from the authorize endpoint for an access token and a refresh token.

Field Type Required Description
code string Yes The authorization code received on the redirect URI
redirect_uri string Yes Must exactly match the redirect_uri sent to the authorize endpoint
code_verifier string Conditional Required when the authorization request included a code_challenge
resource string Conditional Required when the authorization request included a resource parameter, and must equal that value

Response

{
  "access_token": "<access_token>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "<refresh_token>",
  "scope": "read:accounting"
}

Grant: refresh_token

Exchanges a refresh token for a new access token. Refresh tokens are rotated — the old token is revoked and a new one is returned.

Field Type Required Description
refresh_token string Yes A valid, unrevoked refresh token issued to this client
scope string No A subset of the originally granted scopes. Defaults to the original scope when omitted
resource string No RFC 8707 resource indicator to bind as the audience of the new access token

Response

Same shape as the authorization_code response. The refresh_token field contains the rotated refresh token — clients must store it and discard the previous one.

Grant: client_credentials

Server-to-server flow. The token is associated with the client’s owning user.

Field Type Required Description
scope string No Space-separated list of requested scopes
resource string No RFC 8707 resource indicator to bind as the audience of the access token

Response

{
  "access_token": "<access_token>",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "read:accounting"
}

No refresh_token is issued for the client credentials grant — clients re-request a token as needed.

Error Responses

Status Body Description
401 {"error": "invalid_client"} Missing or incorrect client credentials
400 {"error": "unauthorized_client"} The grant type is not enabled for this client
400 {"error": "unsupported_grant_type"} The grant_type value is not recognised
400 {"error": "invalid_grant", "error_description": "Authorization code is required"} code missing on an authorization_code request
400 {"error": "invalid_grant", "error_description": "Invalid authorization code"} Code not found
400 {"error": "invalid_grant", "error_description": "Authorization code expired"} Code TTL elapsed
400 {"error": "invalid_grant", "error_description": "Authorization code was issued to another client"} Cross-client code reuse attempt
400 {"error": "invalid_grant", "error_description": "Redirect URI mismatch"} redirect_uri does not match the original authorize request
400 {"error": "invalid_grant", "error_description": "Code verifier is required"} PKCE expected but code_verifier not supplied
400 {"error": "invalid_grant", "error_description": "Code verifier is invalid"} PKCE verifier does not match the original challenge
400 {"error": "invalid_grant", "error_description": "Resource parameter is required"} resource was sent at authorize but missing at token exchange
400 {"error": "invalid_grant", "error_description": "Resource parameter mismatch"} resource value differs from the authorize request
400 {"error": "invalid_grant", "error_description": "Refresh token is required"} refresh_token missing
400 {"error": "invalid_grant", "error_description": "Invalid refresh token"} Refresh token not found, expired, or revoked
400 {"error": "invalid_grant", "error_description": "Refresh token was issued to another client"} Cross-client refresh attempt

Revocation Endpoint

POST /public/v2/oauth/revoke

Revokes a token per RFC 7009. Only refresh tokens are durably revocable — access tokens are short-lived JWTs and are not tracked server-side, so revoking one returns success without effect.

The request body is application/x-www-form-urlencoded.

Form Parameters

Field Type Required Description
token string Yes The token to revoke
token_type_hint string No refresh_token or access_token. The server still looks the token up if the hint is wrong, per RFC 7009
client_id string Conditional Required when not using HTTP Basic auth
client_secret string Conditional Required when not using HTTP Basic auth

Response

Returns 200 OK with an empty JSON body on success, including when the token is unknown — the spec requires that revocation requests for invalid tokens still succeed so that clients cannot probe for token existence.

{}

A refresh token is only actually revoked when both:

  • The token is recognised.
  • The token belongs to the authenticated client.

If those conditions are not met the response is still 200 {}.

Error Responses

Status Body Description
400 {"error": "invalid_request"} token field is missing
401 {"error": "invalid_client"} Missing or incorrect client credentials

  • OAuth Discovery — well-known metadata endpoints that advertise these URLs
  • OAuth Clients — register and manage OAuth client applications
  • OAuth Admin — admin endpoints for listing and revoking tokens across users