Backend

Sessions

Create a verification session for each applicant, hand its URL to your frontend, and read the result.

View as Markdown

A session is one applicant going through one workflow. Create it on your backend with a secret key; everything the frontend needs is in the response.

Create a session

server.mjs
// Your frontend calls this, then passes { clientToken, sessionUrl } to @okyc/web or @okyc/react.
app.post('/api/kyc/session', express.json(), async (req, res) => {
  const userId = req.header('x-user-id') ?? 'demo-user'; // use your own auth here
  try {
    const session = await okyc.sessions.create(
      {
        workflowId: process.env.OKYC_WORKFLOW_ID,
        referenceId: userId,
        redirectUrl: process.env.REDIRECT_URL,
      },
      { idempotencyKey: `kyc-${userId}-${new Date().toISOString().slice(0, 10)}` },
    );
    res.json({ clientToken: session.clientToken, sessionUrl: session.url });
  } catch (e) {
    res.status(e.statusCode ?? 500).json({ error: e.code ?? 'error' });
  }
});
FieldRequiredNotes
workflow_idyesA published workflow. New sessions use its current version.
reference_idYour user's id (max 128 characters). Use it to find sessions and in webhooks.
countryTwo-letter code; defaults to the workflow's country.
prefillphone, email, full_name shown pre-filled to the applicant.
localeFlow language, one of the workflow's languages (en, hi).
redirect_urlWhere redirect mode returns (with ?session_id=&status=). Must be an allowed origin.
metadataString key/value pairs (values up to 500 characters) returned with the session.
expires_inSeconds, 300 to 2,592,000 (30 days). Default: the workflow's TTL (7 days).

The full schema is in the API reference. Send an Idempotency-Key so a retried request doesn't create a second session.

The response:

{
  "id": "ses_…",
  "status": "created",
  "url": "https://verify.okyc.stream/s/ses_…#t=st_…",
  "client_token": "st_…",
  "expires_at": "2026-10-08T09:30:00.000Z"
}
  • Pass url to a mobile SDK, or client_token + url to @okyc/web / @okyc/react.
  • The client token is valid for 30 minutes, sliding while the applicant is active. To resume later, issue a new one with POST /v1/sessions/:id/client_token; the flow continues at the first unfinished step.
  • Don't log the url: it carries the token.

Read the result

curl "https://api.okyc.stream/v1/sessions/ses_…?expand=checks,applicant" \
  -H "Authorization: Bearer $OKYC_SECRET_KEY"

status is the session's state (statuses); decision and decision_source (auto or manual) say how it was decided. expand=checks adds every check with its result; expand=applicant (KYC) or expand=business (KYB) adds the verified profile.

Fetch a session when a webhook arrives rather than polling.

List sessions

GET /v1/sessions?status=review&limit=50 returns a page (data, has_more). Pass the last id as starting_after for the next page, or filter by reference_id. @okyc/node iterates every page for you: for await (const s of okyc.sessions.list({ status: 'review' })).

Other actions

EndpointUse
POST /v1/sessions/:id/decisionDecide a session in review (approved, declined, resubmission_requested + reason_code).
POST /v1/sessions/:id/cancelCancel an active session.
POST /v1/sessions/:id/client_tokenNew client token to resume in the frontend.
GET /v1/sessions/:id/evidence/:evidence_idShort-lived URL for an evidence file (audited).
DELETE /v1/sessions/:id/piiErase personal data (DPDP erasure); the decision skeleton is kept.

Evidence files are served for download: images (JPEG, PNG, WebP) inline, everything else — PDFs, ZIPs — as an attachment with Content-Type: application/octet-stream and a sandboxing Content-Security-Policy, so a malicious document can never run in a browser. Every upload is virus-scanned before it is stored.

Direct checks (no hosted flow)

Run a single check server-to-server, for example to re-verify a PAN you already hold:

curl https://api.okyc.stream/v1/checks/in.pan.verify \
  -H "Authorization: Bearer $OKYC_SECRET_KEY" -H "Content-Type: application/json" \
  -d '{ "pan": "ABCPE1234F" }'

Use a country key (in.pan.verify) or a generic key with country (tax_id.verify + "country": "IN"). ?async=true returns 202 and sends check.completed when done. GET /v1/capabilities?country=IN lists what a country supports.

On this page