API reference

Authenticate every request with an API key issued in the console, sent as x-api-key or a bearer token. Keys are scoped and revocable; usage is metered per key.

POST /api/public/v1/sessions

Opens a verification session and returns the hosted scan URL to send the subject to.

curl -X POST https://your-app.lovable.app/api/public/v1/sessions \
  -H "x-api-key: easy_live_..." \
  -H "content-type: application/json" \
  -d '{"redirect_uri":"https://wallet.example/callback","reference":"withdrawal-8821"}'

{
  "session_id": "8e0f...",
  "status": "pending",
  "verification_url": "https://your-app.lovable.app/verify?session=8e0f...",
  "status_url": "https://your-app.lovable.app/api/public/v1/sessions/8e0f...",
  "expires_at": "2026-09-20T09:21:00.000Z"
}

GET /api/public/v1/sessions/:id

Polls live pillar scores, assurance levels and failure reasons.

{
  "session_id": "8e0f...",
  "status": "verified",
  "assurance": { "ial": "IAL2", "aal": "AAL3" },
  "pillars": { "environment": 1, "cardio": 0.86, "face": 0.93, "cognitive": 0.91 },
  "token_id": "b41c..."
}

POST /api/public/v1/tokens/verify

Validates a cryptographic attestation token: signature, expiry, revocation and pillar claims. The response contains no identity or biometric data — only a salted subject pledge.

curl -X POST https://your-app.lovable.app/api/public/v1/tokens/verify \
  -H "x-api-key: easy_live_..." \
  -H "content-type: application/json" \
  -d '{"token":"eyJ...cat"}'

{
  "valid": true,
  "assurance": { "ial": "IAL2", "aal": "AAL3" },
  "pillars": { "environment": true, "cardio": true, "face": true, "cognitive": true },
  "subject_pledge": "k3Jd...",
  "expires_at": 1758358860,
  "issuer": "easy-peasy.cat.v1"
}

Okta plug-in (webhooks)

Register an org on the Okta page to get three URLs and two one-time secrets. Add the secret in Okta as the hook authentication field Authorization with value Bearer <secret>. The event hook answers Okta's one-time verification challenge automatically.

Event hook        POST /api/public/v1/okta/{integrationId}/events
Token inline hook POST /api/public/v1/okta/{integrationId}/token
Step-up (API key) POST /api/public/v1/okta/{integrationId}/step-up
Standing check    GET  /api/public/v1/okta/{integrationId}/step-up?okta_login=ada@acme.com

# open a step-up scan for an Okta login
curl -X POST https://your-app.lovable.app/api/public/v1/okta/{integrationId}/step-up \
  -H "x-api-key: easy_live_..." -H "content-type: application/json" \
  -d '{"okta_login":"ada@acme.com","okta_user_id":"00u1..."}'

# inline hook response when the subject holds a fresh verification
{
  "commands": [
    { "type": "com.okta.identity.patch", "value": [
        { "op": "add", "path": "/claims/easy_verified", "value": true },
        { "op": "add", "path": "/claims/easy_ial", "value": "IAL2" },
        { "op": "add", "path": "/claims/easy_aal", "value": "AAL3" },
        { "op": "add", "path": "/claims/easy_cat_jti", "value": "b41c..." } ] },
    { "type": "com.okta.access.patch", "value": [ "…same claims…" ] }
  ]
}

# inline hook response when enforcement is on and no fresh scan exists
{ "error": { "errorSummary": "Step-up biometric verification required (321 Easy Peasy).",
             "errorCauses": [ { "reason": "not_verified" } ] } }

Lifecycle events Okta pushes to the event hook — deactivation, suspension, lock, password reset, factor reset, session end — immediately clear the subject's standing verification and revoke its outstanding proof token, so the next token mint is denied until the subject re-scans.

OAuth / Okta bridge

Treat E.A.Sy P.E.A.Sy as a step-up authenticator inside an existing identity provider flow: on a high-risk action, open a session, redirect the subject to the verification URL, then exchange the returned ZNP token for an AAL3 assertion in your Okta or OAuth policy hook using the token verification endpoint. Because the token proves assurance without identifying the subject, it can be relayed through untrusted intermediaries.

Okta wrapper — drop-in embed

Load one script on any page and a four-pillar scan runs in a popup. The page presents only the public client id from the Okta console; launches are restricted to the allow-listed site addresses for that integration, and no proof-token material is ever handed to the browser.

<script src="/easy-peasy.js"></script>
<button data-easy-peasy data-client-id="ezp_…" data-login="ada@example.com">
  Verify identity
</button>

<!-- or imperatively -->
<script>
  EasyPeasy.verify({
    clientId: 'ezp_…',
    login: 'ada@example.com',
    onResult: function (r) { /* r.status, r.sessionId, r.ial, r.aal */ }
  });
</script>

Always confirm the outcome server-side with your API key before granting access — the popup result is a UX signal, not an authorization decision:

GET /api/public/v1/okta/{integrationId}/step-up?okta_login=ada@example.com
x-api-key: ezp_live_…
→ { "verified": true, "assurance": { "ial": "IAL2", "aal": "AAL3" }, "age_seconds": 12 }

Okta wrapper — hosted step-up page

For mid-login step-up, redirect from an Okta custom sign-in page, app or policy action to the hosted page. It resolves the integration policy, runs the scan and returns the subject to the address you supplied (which must be on the integration's return allow-list).

GET /step-up?client_id=ezp_…&login={user.login}&state={state}&return_to={fromURI}

→ returns to:  {return_to}?status=verified&session_id=…&okta_login=…
                &ial=IAL2&aal=AAL3&state={state}
  • POST /api/public/v1/widget/session — origin-checked launch (no secrets)
  • return_to may also be supplied as fromURI
  • Rotating the client id invalidates every existing embed immediately
  • Launches and outcomes are written to the integration hook audit log

SCIM 2.0 user provisioning

Every integration exposes a SCIM 2.0 service provider (RFC 7643 / RFC 7644). Point Okta provisioning at the base URL below and use the provisioning bearer token shown once in the Okta console. Deactivating or deleting an account immediately clears its standing verification and revokes any live proof token.

  • Base URL /api/public/v1/scim/{integrationId}/v2
  • GET /ServiceProviderConfig · /ResourceTypes · /Schemas
  • GET /Users?filter=userName eq "a@b.com"&startIndex=1&count=100
  • POST /Users · GET /Users/{id}
  • PUT /Users/{id} · PATCH /Users/{id} (active toggle)
  • DELETE /Users/{id} → 204, deprovision + token revocation
  • Authorization: Bearer <provisioning token>
  • Errors use urn:ietf:params:scim:api:messages:2.0:Error

Discovery and token introspection

Relying parties discover the assurance API at a single public document and introspect proof tokens with an RFC 7662 style call. Introspection verifies the signature server-side, checks the issued-token ledger for expiry and revocation, and confirms the token was issued to the calling account before returning assurance metadata.

  • GET /api/public/v1/oidc/configuration
  • POST /api/public/v1/oidc/introspect · {"token":"…"}
  • → {"active":true,"acr":"IAL2 AAL3","pillars":{…}}
  • Accepts JSON or application/x-www-form-urlencoded

Errors

  • 401 missing_or_malformed_api_key · invalid_api_key
  • 403 api_key_revoked · insufficient_scope · token_not_issued_to_this_account
  • 400 invalid_request_body
  • 404 session_not_found
  • 200 {"valid":false,"reason":"token_expired|token_revoked|invalid_signature"}