Skip to main content

Architecture Overview

The relay platform provides credit screening, identity verification, and payment processing through a unified API layer.

System Components

┌─────────────────────────────────────────────────────────────────┐
│ Client Layer │
│ │
│ ┌──────────────┐ ┌─────────────────────────┐ │
│ │ Embedded SDK │ │ Direct REST API Client │ │
│ │ (Browser) │ │ (Server-to-Server) │ │
│ └──────┬───────┘ └────────────┬────────────┘ │
│ │ publishableKey → JWT │ X-API-KEY │
└──────────┼────────────────────────────────────┼────────────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────────────────┐
│ relay API │
│ │
│ ┌──────────────────┐ ┌───────────────┐ ┌──────────────┐ │
│ │ Token Exchange │ │ Screening │ │ Payment │ │
│ │ POST /auth/token │ │ Endpoints │ │ Endpoints │ │
│ │ /exchange │ │ /screening/* │ │ /payments/* │ │
│ └──────────────────┘ └───────────────┘ └──────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────┘

Integration Paths

There are two ways to integrate with relay:

Best for: applications that want to embed the screening flow directly in their UI.

Your Page → SDK (iframe) → relay API (JWT)
  • The SDK handles authentication, form rendering, and PII collection
  • Your application receives callbacks (onScreeningSuccess, onVerificationSuccess, etc.)
  • You never touch raw PII — it stays within the relay iframe
  • See SDK Quickstart to get started

2. Direct REST API

Best for: server-to-server integrations where you manage the consumer experience yourself.

Your Server → relay API (X-API-KEY)
  • You collect consumer data and submit it via API calls
  • You handle the KBA/OTP verification flow
  • You are responsible for PII security and FCRA compliance
  • See API Getting Started to get started

Request Flow: Screening (Embedded SDK / Platform API)

This is the flow behind the Embedded SDK — the /screening/* and /auth/* endpoints reached via a JWT obtained through publishable-key token exchange. It is distinct from the Direct REST API (/api/experian/renters|agents|organizations/*, X-API-KEY auth) documented in the Consumer/IRO End User/PMC End User API references — the two paths use different auth, different endpoint shapes, and different error code sets (see Error Codes).

Screening session state machine

Every screening session (GET /screening/{id}/status returns this as verificationStatus) moves through:

SUBMITTED → STEP_UP_REQUIRED → OTP_PENDING | KBA_PENDING → VERIFIED | BLOCKED | EXPIRED | ERROR

VERIFIED means the identity was confirmed — it does not by itself mean the report is releasable for CONSUMER users, who may still owe payment. The status endpoint also returns a derived overallStatus that folds in payment state: COMPLETED, PAYMENT_PENDING, PAYMENT_FAILED, or one of the raw statuses above (e.g. BLOCKED).

Step 1: Submit for screening

POST /screening/submit
Authorization: Bearer <jwt from /auth/token/exchange>

Request body (all consumer-provided fields — see the SDK's screening-prefill guide for the subset the SDK can pre-fill):

{
"firstName": "Jane",
"lastName": "Doe",
"dateOfBirth": "01152000",
"ssn": "123456789",
"phoneNumber": "5551234567",
"phoneType": "C",
"currentAddress": "123 Main St",
"currentCity": "Austin",
"currentState": "TX",
"currentZip": "78701",
"jscPayload": "<Experian device fingerprint>",
"termsAcceptedAt": "2026-07-28T00:00:00.000Z",
"userType": "CONSUMER"
}

ssn is required only when userType is CONSUMER. phoneNumber must be exactly 10 digits after stripping formatting, and cannot start with 1. dateOfBirth uses MMDDYYYY and must correspond to an age between 18 and 120.

Response (200):

{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "STEP_UP_REQUIRED",
"stepUp": {
"type": "CHOICE",
"otpEnabled": true,
"kbaEnabled": true
}
}

id is the UUID to use for every subsequent call and for GET /reports/{id}. If EIS can confirm identity without step-up, status comes back VERIFIED directly (no stepUp object).

Step 2: Step-up authentication (OTP or KBA)

When stepUp.type is OTP or CHOICE with otpEnabled, submit the code the consumer received by SMS:

POST /screening/{id}/otp
Body: { "code": "123456" }

To request a new code (rate-limited to one per 30 seconds):

POST /screening/{id}/otp/resend

When stepUp.type is KBA or CHOICE with kbaEnabled, stepUp.kbaQuestions contains the questions/choices to render, and answers are submitted as:

POST /screening/{id}/kba
Body: { "answers": [{ "questionId": 1, "answerId": 3 }, { "questionId": 2, "answerId": 1 }] }

Both endpoints return the same ScreeningResult shape as Step 1. On a wrong answer/code, status stays OTP_PENDING/KBA_PENDING and the response includes attemptsRemaining; after too many attempts the session moves to status: "BLOCKED" with a blockedUntil timestamp (200 response, not an error).

Step 3: Check status / Report retrieval

GET /screening/{id}/status

Returns the full ScreeningResult plus verificationStatus, paymentStatus, and overallStatus. Once overallStatus is COMPLETED, pull the report:

GET /reports/{id}  (session UUID — the `id` returned from submit/step-up)

Response: full credit report + score (JSON or HTML, content-negotiated).

Request Flow: Payment (Embedded SDK / Platform API)

CONSUMER screenings may require payment before the report is releasable. This is a separate Stripe integration from the Direct REST API's Checkout-based flow (/api/stripe/payment/checkout-session, see the Stripe Integration API reference) — the platform payment endpoints use Stripe PaymentIntents directly:

POST /payments/create-intent
Body: { "amount": 3500, "applicationFee": 3500, "screeningId": "<id from /screening/submit>" }

amount/applicationFee are in cents. Response (200):

{
"transaction_id": "txn_...",
"client_secret": "pi_..._secret_...",
"amount": 3500,
"currency": "usd"
}

The client confirms the PaymentIntent with Stripe.js using client_secret, then calls:

POST /payments/{transactionId}/confirm

which returns { "transaction_id", "status", "amount", "currency" } (status is one of PENDING, PROCESSING, REQUIRES_ACTION, SUCCEEDED, FAILED) and synchronously updates the screening session's payment status as a backstop. The authoritative status update still comes from Stripe via server-to-server EventBridge events — there is no client-facing payment webhook endpoint (POST /payments/webhook was retired; do not integrate against it).

Authentication

MethodHeaderDetails
JWT (Bearer token)Authorization: Bearer <token>Used by SDK; 1-hour expiry, origin-bound, auto-refreshed
API KeyX-API-KEYUsed by direct REST API; permanent key, server-side only
Webhook signatureStripe-SignatureHMAC-SHA256; verified using endpoint secret

Data Isolation

All data is isolated per account:

  • API Layer: Authentication tokens contain an account ID; all queries are scoped to the authenticated account
  • Database: Row-level security ensures each account can only access its own data
  • Webhooks: Webhook handlers validate ownership before processing events

Next Steps