CAIRLDocs
Integration

OAuth and OIDC Guide

Map CAIRL hosted verification into OAuth authorization and OIDC-style claim retrieval.

Overview

CAIRL's hosted verification flow uses OAuth 2.0 Authorization Code with PKCE. Your application redirects the user to CAIRL, receives an authorization code, exchanges the code server-to-server, and reads verified claims from the userinfo endpoint.

Use this guide when your application already has an OAuth or OIDC integration layer and you want to plug CAIRL into that layer without collecting identity documents yourself.

If you only want sign-in (no verified claims), use Log in with CAIRL instead: a standard OpenID Connect login with discovery and an ID token. The two purposes never mix in one request.


Endpoints

EndpointPurpose
GET https://cairl.app/verify/startHosted verification entry point; a fresh verification for your site, then consent
GET /oauth/authorize (same host)Reusable-verification path: with verification scopes it honours an existing verification that meets the key's depth and falls inside its time-based freshness window, then goes to consent. Same parameters as /verify/start plus the required response_type=code. This is not the sign-in flow — for that use the OIDC flow (Log in with CAIRL): the openid scope alone, a login-enabled credential, and an ID token.
POST https://cairl.app/api/oauth/tokenToken exchange (grant_type=authorization_code only)
GET https://cairl.app/api/oauth/userinfoVerified claims snapshot
POST https://cairl.app/api/oauth/revokeToken revocation (RFC 7009)
GET https://cairl.app/.well-known/jwks.jsonPublic signing keys for the access-token JWT
GET https://cairl.app/.well-known/openid-configurationOpenID Connect discovery (used by Log in with CAIRL)

The freshness half of that check covers the time-based policies only — daily, 3day, weekly, biweekly, monthly. The event-driven policies, session and transaction, are not enforced at /oauth/authorize today.

What is not published for verification requests:

  • No refresh tokens. Run the authorization flow again for a new snapshot.
  • No ID token on a verification request. Read claims from userinfo (or from the access-token JWT's cairl_claims, verified against the JWKS). An ID token is issued only for a sign-in request, which carries the openid scope alone and requires a login-enabled credential.

Authorization endpoint

Redirect users to:

https://cairl.app/verify/start

Required parameters:

ParameterDescription
client_idYour CAIRL test or live client identifier
redirect_uriExact callback URL registered on /home/f/{slug}/connect
stateRandom CSRF token, at least 16 characters, returned unchanged
scopeSpace-delimited wire scopes such as age:18+ identity:verified
code_challengePKCE S256 challenge
code_challenge_methodMust be S256

PKCE is mandatory; a request without code_challenge is rejected.

The scope values are wire scopes (age:18+), not the claim names that come back in the response (age_18_plus). The full list is in the claims reference.

See the Quickstart for complete Node and Python PKCE examples.


Token endpoint

Exchange the authorization code at:

POST https://cairl.app/api/oauth/token

The token request uses application/x-www-form-urlencoded (JSON is also accepted) and requires all of:

FieldDescription
grant_typeauthorization_code
codeCode from the callback
client_idYour CAIRL test or live client identifier
client_secretServer-side secret from /home/f/{slug}/keys
redirect_uriSame callback URL used in the authorization request
code_verifierOriginal PKCE verifier

The response includes a bearer access_token, token_type, expires_in, and the granted scope. With live credentials, an insufficient balance returns 402 payment_required and no token.


Userinfo and claim retrieval

Read verified claims at:

GET https://cairl.app/api/oauth/userinfo
Authorization: Bearer ACCESS_TOKEN

The response is OIDC-style: it includes a site-scoped sub plus the verified claims the user authorized, and a mode field ("live" or "test"). In live mode sub is stable for that user on your site and is the identifier to match accounts on. In test mode (a cairl_test_… credential) every exchange returns a fresh, random pws_v1_test_… subject and synthetic claims, so never build account matching against a test-mode sub; branch on mode instead. CAIRL does not return raw document images or default raw identity fields through this endpoint.

{
  "sub": "pws_v1_abc123",
  "evaluated_at": "2026-05-03T10:00:00.000Z",
  "claims": {
    "age_18_plus": true,
    "identity_verified": true
  },
  "meta": {
    "claims_requested": ["age_18_plus", "identity_verified"],
    "claims_resolved": ["age_18_plus", "identity_verified"],
    "claims_null": [],
    "claims_ignored": []
  }
}

OIDC compatibility notes

  • Treat sub as a pseudonymous subject identifier scoped to your CAIRL client.
  • Use scopes to request only the claims your product needs.
  • Validate state before exchanging the authorization code.
  • Store and use the client_secret only on your server.
  • Do not expect an ID token on a verification request; use userinfo as the canonical claims response. For sign-in with an ID token, see Log in with CAIRL.
  • Your library may read /.well-known/openid-configuration for the endpoint URLs and JWKS location; verification scopes are listed there beside openid.

On this page