OAuth and OpenID Connect endpoints

Look up every endpoint an application calls to sign people in, with its parameters, responses, scopes and errors.

Every endpoint on this page is on your project's issuer, the project's URL. The examples use https://acme.authfi.io. An OpenID Connect library reads all of them from the OpenID configuration.

EndpointUse it to
GET /oauth/authorizeSend a person to sign in
POST /oauth/tokenExchange a code, refresh, or sign in as a service
GET /oauth/userinfoRead the signed-in person's profile
POST /oauth/token/introspectAsk whether an access token is active
POST /oauth/token/revokeRevoke a refresh token
GET /oauth/logoutEnd the person's session
GET /.well-known/jwks.jsonFetch the keys that sign tokens

An AI agent signs in at a separate token endpoint. See Give an AI agent an identity.

Authorize

Starts the authorization code flow: the browser goes here, the person signs in, and the browser returns to your application with a code.

GET

/oauth/authorize

Authentication. None. The browser makes this request, and the application is identified by client_id.

Parameters. Sent in the query string.

ParameterRequiredDescription
client_idYesYour application's client ID
redirect_uriYesMust match a registered redirect URI exactly. See Application types.
response_typeNocode, the only value
scopeNoSpace-separated scopes. openid if left out.
stateNoA value returned to you unchanged, to match the response to the request
code_challengeFor an application with no secretThe PKCE (RFC 7636) challenge. Accepted from an application with a secret.
code_challenge_methodNoS256, the only method accepted. S256 if left out.
nonceNoA value returned in the ID token
promptNologin for a fresh sign-in, consent to ask again for consent
max_ageNoSeconds since the last sign-in that you accept. 0 means now.
acr_valuesNoThe sign-in strength you require. See acr values.
login_hintNoAn account name to fill in on the sign-in page

Example request.

https://acme.authfi.io/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=https%3A%2F%2Fapp.acme.com%2Fcallback&scope=openid%20profile%20email&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256

$CLIENT_ID is shown in the console when you register the application. $STATE and $CHALLENGE are values your application creates. See Add sign-in to an app.

Example response. The browser returns to redirect_uri.

https://app.acme.com/callback?code=Xq3v...&state=5f2a...&iss=https%3A%2F%2Facme.authfi.io

Response fields.

FieldDescription
codeThe authorization code. Works once and expires after 5 minutes.
stateThe state you sent, unchanged
issThe project's issuer

Errors. Where the error appears depends on what is wrong.

What is wrongResponse
client_id is unknownA 401 page with invalid_client, shown on your project's issuer
redirect_uri is missing or not registeredA 400 page with invalid_request, shown on your project's issuer
Anything elseThe browser returns to redirect_uri with error, error_description and state

The browser is never sent to a URI that is not registered.

error at the redirect URICause
access_deniedThe person is not assigned to the application
unmet_authentication_requirementsThe acr_values you asked for could not be reached

Every code is listed under Errors.

Token

Issues tokens. One endpoint serves three grants, chosen by grant_type.

POST

/oauth/token

Authentication. An application with a client secret authenticates with HTTP Basic, or with client_id and client_secret in the body. An application without a secret sends client_id only.

Parameters. Sent form-encoded in the body. Every request carries grant_type, and each grant adds its own parameters.

ParameterRequiredDescription
grant_typeYesauthorization_code, refresh_token or client_credentials

Authorization code grant

Exchanges the code from Authorize for tokens. See Add sign-in to an app.

ParameterRequiredDescription
codeYesThe code from the redirect
redirect_uriYesThe same URI the code was issued to
code_verifierWhen a challenge was sentThe PKCE verifier
curl https://acme.authfi.io/oauth/token \
  -d grant_type=authorization_code \
  -d code="$CODE" \
  -d redirect_uri=https://app.acme.com/callback \
  -d client_id="$CLIENT_ID" \
  -d code_verifier="$VERIFIER"

$CODE is the code from the redirect. $VERIFIER is the PKCE verifier whose challenge you sent to Authorize.

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9...",
  "token_type": "Bearer",
  "expires_in": 900,
  "refresh_token": "...",
  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ii4uLiJ9...",
  "scope": "openid profile email"
}

Refresh token grant

Exchanges a refresh token for new tokens. Each refresh token works once, and every use returns a new one. See Refresh tokens.

ParameterRequiredDescription
refresh_tokenYesThe most recent refresh token
scopeNoA subset of the scopes first granted
curl https://acme.authfi.io/oauth/token \
  -d grant_type=refresh_token \
  -d refresh_token="$REFRESH_TOKEN" \
  -d client_id="$CLIENT_ID"

The response has the same shape as for the authorization code grant. sid, amr and auth_time in the new tokens are those of the original sign-in.

Client credentials grant

Issues an access token to a backend service, for one API. Only a machine-to-machine (m2m) application may use it. See Service to service.

ParameterRequiredDescription
resourceNoThe client ID of the one API the token is for. Without it, the token is for the calling application itself.
scopeNoScopes to carry in the token, unchanged
curl https://acme.authfi.io/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d resource="$API_CLIENT_ID"

$API_CLIENT_ID is the client ID of the API's application. The calling application must hold a grant on it.

{
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ii4uLiJ9...",
  "token_type": "Bearer",
  "expires_in": 900
}

Token response

Response fields. The response carries Cache-Control: no-store.

FieldPresentDescription
access_tokenAlwaysThe access token
token_typeAlwaysBearer
expires_inAlwaysSeconds until the access token expires
id_tokenWhen the scope included openidThe ID token
refresh_tokenWhen a person signed in and the application may hold oneThe refresh token
scopeAs in the examples aboveThe scopes granted, space-separated

The client credentials grant returns an access token only.

Errors.

errorCause
invalid_grantThe code expired, was already used, or the verifier does not match
invalid_grantThe refresh token was already used. Every refresh token from the same sign-in is revoked.
invalid_clientThe client ID is unknown, or the client secret is missing or wrong
unauthorized_clientclient_credentials from an application that is not of type m2m
unsupported_grant_typegrant_type is not one of the three
invalid_scopeA refresh asked for more than was granted
invalid_targetresource is not an application, or the caller has no grant on it
access_deniedThe person is not assigned to the application. Assignment is checked each time a token is issued.
insufficient_user_authenticationA stronger sign-in is needed before this token can be issued

Userinfo

Returns the profile of the person an access token was issued for.

GET

/oauth/userinfo

Authentication. The access token, as Authorization: Bearer.

Parameters. None. POST is accepted too.

Example request.

curl https://acme.authfi.io/oauth/userinfo \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example response.

{
  "sub": "[email protected]",
  "email": "[email protected]",
  "email_verified": true,
  "name": "Ada Lovelace",
  "groups": ["engineering"],
  "roles": ["developer"]
}

Response fields. The fields returned depend on the scopes of the access token.

FieldReturned with scope
subAny
email, email_verifiedemail
name, picture, locale, groups, rolesprofile

The profile is read on each call, so a changed name shows at once. groups and roles are those in the access token.

Errors. None specific to this endpoint. See Errors.

Check a token

Tells an application whether an access token is active (token introspection).

POST

/oauth/token/introspect

Authentication. The client ID and client secret of an application that has a secret. The example sends them as HTTP Basic.

Parameters. Sent form-encoded in the body.

ParameterRequiredDescription
tokenYesThe access token to check

Example request.

curl https://acme.authfi.io/oauth/token/introspect \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d token="$ACCESS_TOKEN"

Example response.

{
  "active": true,
  "scope": "openid profile email",
  "client_id": "3f9c2b7e...",
  "sub": "[email protected]",
  "iss": "https://acme.authfi.io",
  "exp": 1760000900,
  "iat": 1760000000,
  "token_type": "Bearer"
}

Response fields.

FieldDescription
activetrue when the token is a good access token
scopeThe scopes granted, space-separated
client_idThe application the token was issued to
subThe person, or for a service its client ID
issThe project's issuer
exp, iatWhen the token expires, and when it was issued
token_typeBearer

Errors. A token that is expired, malformed or not an access token returns {"active": false} and no other field.

An access token is a signed JWT, so a service can also validate it without a call. See Checking a token.

Revoke a token

Revokes a refresh token, and with it every refresh token descended from the same sign-in.

POST

/oauth/token/revoke

Authentication. The application's client_id, in the body.

Parameters. Sent form-encoded in the body.

ParameterRequiredDescription
tokenYesThe refresh token to revoke

Example request.

curl https://acme.authfi.io/oauth/token/revoke \
  -d token="$REFRESH_TOKEN" \
  -d client_id="$CLIENT_ID"

Example response. 200 with an empty body, whether or not the token existed.

Errors. None specific to this endpoint. See Errors.

This endpoint acts on refresh tokens. An access token stays valid until it expires, which is why access tokens last 15 minutes by default. To end a person's access sooner, end their session or remove their access. See Sign out.

Sign out

Ends the person's session and removes its cookie. This is the end-session endpoint.

GET

/oauth/logout

Authentication. None. The browser makes this request.

Parameters. Sent in the query string.

ParameterRequiredDescription
client_idNoYour application's client ID. Needed to send the person back.
post_logout_redirect_uriNoWhere to send the person afterwards
stateNoA value returned to you unchanged

Example request.

https://acme.authfi.io/oauth/logout?client_id=$CLIENT_ID&post_logout_redirect_uri=https%3A%2F%2Fapp.acme.com%2F&state=abc

Response. The outcome depends on the parameters.

RequestThe person
client_id is given, and post_logout_redirect_uri has the scheme and host of a registered redirect URIReturns to post_logout_redirect_uri
Anything elseSees a page saying they are signed out

Errors. None specific to this endpoint.

To also stop the refresh token your application holds, revoke it. See Sign out.

Keys

Returns the project's public signing keys as a JWK Set (RFC 7517).

GET

/.well-known/jwks.json

Authentication. None.

Parameters. None.

Example request.

curl https://acme.authfi.io/.well-known/jwks.json

Response. The set holds one RS256 key and one EdDSA (Ed25519) key.

KeySigns
RS256ID tokens and access tokens for people and services
EdDSAAccess tokens and transaction tokens for AI agents

Each token names its key in the kid header. Keys rotate, and a rotated key stays published for 24 hours. Fetch the set when you meet a kid you do not know, not on every request, and do not pin one key. See Keys and secrets.

Errors. None specific to this endpoint.

Scopes

A scope is a name your application sends in scope to say what it wants. Send several separated by spaces.

ScopePurposeAdds at userinfo
openidRequired for an ID token—
profileThe person's profile and accessname, picture, locale, groups, roles
emailThe person's emailemail, email_verified

Userinfo returns sub with any scope. profile and email decide what else userinfo returns. The ID token carries email, email_verified, name, groups and roles whatever scopes you request. See ID token.

Errors

Every endpoint on this page reports a failure as OAuth 2.0 defines (RFC 6749): a code in error, and sometimes a sentence in error_description.

{ "error": "invalid_grant", "error_description": "authorization code is invalid or expired" }

The authorization endpoint sends the same two fields, with state, as query parameters on the redirect URI.

errorStatusMeaning
invalid_request400A parameter is missing or malformed
invalid_client401The client is unknown, or its secret is wrong
invalid_grant400The code or refresh token is wrong, expired or already used
unauthorized_client400This application may not use this grant
unsupported_grant_type400The grant type is not one the endpoint offers
invalid_scope400A refresh asked for more than was granted
invalid_target400resource is not something this caller may address
access_denied403The person is not assigned to the application
login_required401The session is too old for prompt=login or max_age
interaction_required401The person must complete another step
unmet_authentication_requirements400The acr_values asked for could not be reached
insufficient_user_authentication401A stronger sign-in is needed before this token can be issued
temporarily_unavailable503Try again shortly
forbidden403The project's plan or account does not allow this
rate_limited429Too many attempts

For what to do about each code, see Errors.