Skip to content
AXUS IDAXUS ID
Docs/API reference
On this page

Reference

The contract behind the code.

This page matches the implementation — the sign-in half of AXUS ID (OAuth2/OIDC endpoints). The other half, every engine function behind the login, lives in the GraphQL API explorer. Worked examples are in the quickstart. Client IDs are AUIDs. Token, userinfo and introspection responses use Cache-Control: no-store.

EndpointMethodPurpose
/authorizeGETStart the Authorization Code flow with PKCE
/oauth/tokenPOSTExchange a code; redeem a refresh token
/oauth/userinfoGET / POSTProfile claims for the access token
/oauth/revokePOSTEnd the whole authorization (RFC 7009)
/oauth/introspectPOSTCheck an opaque access token (RFC 7662)
/oauth/graphqlPOSTProxy AXUS GraphQL using an OAuth access token
/gravatar/<email_hash>GETGravatar-compatible account avatar
/.well-known/openid-configurationGETDiscovery document
/.well-known/jwks.jsonGETPublic keys for ID and JWT access tokens

Issuer

https://axusid-website.vercel.app

Discovery document

https://axusid-website.vercel.app/.well-known/openid-configuration

GET /authorize

Authorization Code flow with mandatory PKCE (S256) — no client secrets exist. Details per parameter:

ParameterRequiredNotes
response_typeyesMust be code
client_idyesYour account’s AUID
redirect_uriyesMust be a registered URI, byte for byte
code_challengeyesS256 output: 43-character unpadded base64url SHA-256 digest
code_challenge_methodyesMust be S256
scopenoMandatory, space-separated; defaults to openid
optional_scopenoAXUS ID extension: scopes the user can toggle; unavailable permissions are omitted
conditional_scopenoAXUS ID extension: AXUS permissions required when held, omitted otherwise
staterecommendedReturned unchanged; generate a fresh value and require it on callback
noncerecommendedEchoed into the ID token when openid is granted; verify against the transaction
promptnologin, select_account, consent, or none; none cannot be combined with other values

Scopes are the four OIDC scopes plus declared AXUS permission keys. Unprefixed keys use system context; use app:<app AUID>:<permission key> for another app’s context. Parameter wildcards require declaration support; bare *requests all permissions the caller holds in one context. Legacy prefix wildcards are rejected. The user reviews engine-provided descriptions on the consent screen. See permission scopes and contexts.

A scope must appear in only one list. Optional scopes can include OIDC scopes; conditional scopes support AXUS permissions only. If scope is omitted, it defaults to openid. Pass optional_scope and conditional_scopeas additional authorization parameters when using an OIDC library.

Three permission modes · add to your PKCE authorization request
{
  "scope": "openid profile app:5:posts.read",
  "optional_scope": "app:5:posts.write",
  "conditional_scope": "app:5:posts.moderate"
}

Replace 5 with the declaration owner’s AUID and use declared keys. Available optional scopes start checked. Conditional permissions are required when held and cannot be toggled off; unavailable optional and conditional permissions are omitted. Availability is checked again at consent submission.

Authorization outcomes

CallbackWhenApp behavior
code + stateAccess approvedExchange once; check the token response’s scope for the complete approved set.
access_denied + stateUser cancels, mandatory access is missing, or the engine denies issuanceDo not exchange or create a session. Offer a new sign-in with a suitable account.
consent_required + stateprompt=none needs approvalStart an interactive authorization to review scopes.
invalid_scope + stateInvalid scope syntax, overlapping lists, or invalid permission declarations/bindingsCorrect the requested lists and start a fresh authorization.
server_error + statePermission checks or token issuance cannot completeTreat as failure, not absent conditional access; retry when the service recovers.

Validate state before handling success or errors. Missing mandatory access issues no code or tokens and leaves the AXUS ID session active. Interactive requests show the missing permissions and let the user switch accounts or return to the app; returning sends the error callback. prompt=none returns the error immediately without UI. Declined optional scopes prompt again when requested later. Use prompt=consent to review previously approved choices. See the flow walkthrough.

POST /oauth/token

Accepts form-encoded or JSON bodies. client_id can travel in the body, as the username half of Basic auth (password ignored for library compatibility), or as auid.

Exchange a code · replace the placeholder values
curl --request POST 'https://axusid-website.vercel.app/oauth/token' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'client_id=YOUR_AUID' \
  --data-urlencode 'redirect_uri=https://app.example/auth/callback' \
  --data-urlencode 'code=AUTHORIZATION_CODE' \
  --data-urlencode 'code_verifier=ORIGINAL_VERIFIER'
FieldGrantNotes
grant_typebothauthorization_code or refresh_token
codecodeSingle use; must match client_id and redirect_uri
redirect_uricodeMust equal the authorize request’s URI
client_idbothMust own the code or the refresh token
code_verifiercodeOriginal verifier; generate 43–128 unreserved characters (32 random base64url bytes works)
refresh_tokenrefreshRotates on every use (see below)

Failure codes, all JSON with error and error_description:

ErrorStatusMeaning
invalid_request400Malformed request or missing PKCE fields
invalid_client401Unknown client_id
invalid_grant400 / 401Bad, expired or reused code; PKCE or URI mismatch; revoked grant. Refresh failures return 401
server_error500Token issuance failed on our side

The token response

Example · openid profile · default opaque access token
{
  "access_token": "axid_at_…",
  "token_type": "Bearer",
  "expires_in": 43200,
  "id_token": "eyJhbG…",
  "axus_access_token": "…"
}
FieldPresentNotes
access_tokenalwaysOpaque (axid_at_…, 12h, revocable instantly) by default; per-client RS256 JWT (15min, offline-verifiable via JWKS)
token_typealwaysBearer
expires_inalwaysSeconds until access_token expiry: 43200 opaque, 900 JWT
scopealwaysComplete approved scope set, including OIDC; excludes declined or unavailable permissions
id_tokenopenid scopeRS256 JWT: sub is the user AUID, aud is your client AUID, carries nonce when sent
refresh_tokenoffline_access scopeOpaque. Rotates on every use; the old one works 30s for retries, then reuse revokes the whole authorization
axus_access_tokenAXUS permission scopesNative token for AXUS GraphQL APIs. No expiry; dies when the user disconnects the app
Check approved scopes: the token response’s scopelists the complete approved OIDC and AXUS scope set. Enable optional or conditional features only when their scopes appear here. Missing mandatory permissions return access_denied without an authorization code.

Refresh tokens are bound to their client. Request offline_access only for continued API access; serialize refreshes and save each replacement atomically.

GET /oauth/userinfo

Authorization: Bearer <access_token>, GET or POST. The token must carry the openid scope or you get invalid_token (401, with WWW-Authenticate). Claims are filtered by granted scope — you never see more than the user approved:

ClaimScopeNotes
subopenidThe user’s AUID
preferred_usernameprofileDefault username, without @
nameprofileDisplay name
given_name / family_nameprofileWhen set on the profile
emailemailSynthetic compatibility address: <auid>@amail.com; not proof of a deliverable or verified contact address

GET /gravatar/<email_hash>

Hash the trimmed, lowercase synthetic email (<auid>@amail.com) using MD5 or SHA-256. This public endpoint returns the current default variation’s avatar as a square JPEG. An optional .jpg suffix is accepted. Responses cache for five minutes and allow cross-origin reads.

ParameterDefaultNotes
s / size80Square edge in pixels, 1–2048; invalid values use 80
d / defaultGravatar default404 returns HTTP 404; built-in styles and custom image URLs redirect to Gravatar’s default-image service
f / forcedefaultoffSet to y to always return the requested default

Accounts become available after signing in through this provider. Missing accounts or avatars use the requested default; service failures return an uncached 503.

Avatar URL
https://axusid-website.vercel.app/gravatar/<email_hash>?s=128&d=404

POST /oauth/revoke

Body: token (required), token_type_hint (access_token or refresh_token, optional — both kinds are tried). Revoking any token ends the whole authorization: the app’s native token is revoked at the engine and its remaining tokens go with it. The user’s own session is untouched. Unknown tokens still return 200.

POST /oauth/introspect

RFC 7662 for opaque access tokens. client_id (body or Basic auth) is required. Missing or unknown clients receive 401; a missing token receives 400. Inactive tokens and tokens owned by another client return{ active: false }. Active tokens return active, scope, client_id, sub, token_type, exp, and iss.

Introspection request
curl --request POST 'https://axusid-website.vercel.app/oauth/introspect' \
  --data-urlencode 'client_id=YOUR_AUID' \
  --data-urlencode 'token=ACCESS_TOKEN'
Example active response
{
  "active": true,
  "scope": "openid profile",
  "client_id": "YOUR_AUID",
  "sub": "USER_AUID",
  "token_type": "Bearer",
  "exp": 2000000000,
  "iss": "https://axusid-website.vercel.app"
}

POST /oauth/graphql

Send a JSON GraphQL request with Authorization: Bearer <access_token>. The proxy resolves the OAuth token to the authorization’s native AXUS token and forwards the request to the engine, limited by granted permissions (discovery: axus_graphql_proxy_endpoint). The separate axus_access_token is a server-side credential for direct native calls — not an identifier, not an ID token.

Refresh an authorization

Request offline_access during authorization to receive a refresh token. Each successful refresh returns a replacement. Tokens are long-lived but can be revoked; do not assume the connection will last indefinitely.

Server-side refresh request
curl --request POST 'https://axusid-website.vercel.app/oauth/token' \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode 'client_id=YOUR_AUID' \
  --data-urlencode 'refresh_token=CURRENT_REFRESH_TOKEN'
Logout and disconnect are different. Local logout deletes your app’s session. Revocation disconnects the whole AXUS authorization. Offline JWT verification alone cannot detect immediate revocation; a previously issued JWT can still verify until its expiry. AXUS endpoints check authorization state when resolving access tokens.