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.
| Endpoint | Method | Purpose |
|---|---|---|
| /authorize | GET | Start the Authorization Code flow with PKCE |
| /oauth/token | POST | Exchange a code; redeem a refresh token |
| /oauth/userinfo | GET / POST | Profile claims for the access token |
| /oauth/revoke | POST | End the whole authorization (RFC 7009) |
| /oauth/introspect | POST | Check an opaque access token (RFC 7662) |
| /oauth/graphql | POST | Proxy AXUS GraphQL using an OAuth access token |
| /gravatar/<email_hash> | GET | Gravatar-compatible account avatar |
| /.well-known/openid-configuration | GET | Discovery document |
| /.well-known/jwks.json | GET | Public keys for ID and JWT access tokens |
Issuer
https://axusid-website.vercel.appDiscovery document
https://axusid-website.vercel.app/.well-known/openid-configurationGET /authorize
Authorization Code flow with mandatory PKCE (S256) — no client secrets exist. Details per parameter:
| Parameter | Required | Notes |
|---|---|---|
| response_type | yes | Must be code |
| client_id | yes | Your account’s AUID |
| redirect_uri | yes | Must be a registered URI, byte for byte |
| code_challenge | yes | S256 output: 43-character unpadded base64url SHA-256 digest |
| code_challenge_method | yes | Must be S256 |
| scope | no | Mandatory, space-separated; defaults to openid |
| optional_scope | no | AXUS ID extension: scopes the user can toggle; unavailable permissions are omitted |
| conditional_scope | no | AXUS ID extension: AXUS permissions required when held, omitted otherwise |
| state | recommended | Returned unchanged; generate a fresh value and require it on callback |
| nonce | recommended | Echoed into the ID token when openid is granted; verify against the transaction |
| prompt | no | login, 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.
{
"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
| Callback | When | App behavior |
|---|---|---|
| code + state | Access approved | Exchange once; check the token response’s scope for the complete approved set. |
| access_denied + state | User cancels, mandatory access is missing, or the engine denies issuance | Do not exchange or create a session. Offer a new sign-in with a suitable account. |
| consent_required + state | prompt=none needs approval | Start an interactive authorization to review scopes. |
| invalid_scope + state | Invalid scope syntax, overlapping lists, or invalid permission declarations/bindings | Correct the requested lists and start a fresh authorization. |
| server_error + state | Permission checks or token issuance cannot complete | Treat 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.
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'| Field | Grant | Notes |
|---|---|---|
| grant_type | both | authorization_code or refresh_token |
| code | code | Single use; must match client_id and redirect_uri |
| redirect_uri | code | Must equal the authorize request’s URI |
| client_id | both | Must own the code or the refresh token |
| code_verifier | code | Original verifier; generate 43–128 unreserved characters (32 random base64url bytes works) |
| refresh_token | refresh | Rotates on every use (see below) |
Failure codes, all JSON with error and error_description:
| Error | Status | Meaning |
|---|---|---|
| invalid_request | 400 | Malformed request or missing PKCE fields |
| invalid_client | 401 | Unknown client_id |
| invalid_grant | 400 / 401 | Bad, expired or reused code; PKCE or URI mismatch; revoked grant. Refresh failures return 401 |
| server_error | 500 | Token issuance failed on our side |
The token response
{
"access_token": "axid_at_…",
"token_type": "Bearer",
"expires_in": 43200,
"id_token": "eyJhbG…",
"axus_access_token": "…"
}| Field | Present | Notes |
|---|---|---|
| access_token | always | Opaque (axid_at_…, 12h, revocable instantly) by default; per-client RS256 JWT (15min, offline-verifiable via JWKS) |
| token_type | always | Bearer |
| expires_in | always | Seconds until access_token expiry: 43200 opaque, 900 JWT |
| scope | always | Complete approved scope set, including OIDC; excludes declined or unavailable permissions |
| id_token | openid scope | RS256 JWT: sub is the user AUID, aud is your client AUID, carries nonce when sent |
| refresh_token | offline_access scope | Opaque. Rotates on every use; the old one works 30s for retries, then reuse revokes the whole authorization |
| axus_access_token | AXUS permission scopes | Native token for AXUS GraphQL APIs. No expiry; dies when the user disconnects the app |
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:
| Claim | Scope | Notes |
|---|---|---|
| sub | openid | The user’s AUID |
| preferred_username | profile | Default username, without @ |
| name | profile | Display name |
| given_name / family_name | profile | When set on the profile |
| Synthetic 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.
| Parameter | Default | Notes |
|---|---|---|
| s / size | 80 | Square edge in pixels, 1–2048; invalid values use 80 |
| d / default | Gravatar default | 404 returns HTTP 404; built-in styles and custom image URLs redirect to Gravatar’s default-image service |
| f / forcedefault | off | Set 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.
https://axusid-website.vercel.app/gravatar/<email_hash>?s=128&d=404POST /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.
curl --request POST 'https://axusid-website.vercel.app/oauth/introspect' \
--data-urlencode 'client_id=YOUR_AUID' \
--data-urlencode 'token=ACCESS_TOKEN'{
"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.
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'