Skip to Content

Core APIs (server-to-server verification)

Raw data vs proofs. Without a Valyd user token these are Non-account (Fresh) checks: you did the capture, nothing is retained, and the response contains the raw extracted data (document fields, dob, portrait, OCR). Pass a valyd_access_token (or valyd_id) and the same endpoints run in Account (Managed by Valyd) mode — answering from the user’s stored identity and returning proofs only (id_verified, match + score, license badges, age bands), never raw KYC. Raw account attributes come only from the consent Core API. See Account (Managed by Valyd) .

Agent Quick-Start

  • Source URL: https://docs.valyd.work/verify#standalone 
  • Credentials / env vars needed: VALYD_API_KEY (App API key — keep server-side, never ship to the browser)
  • Files an integrator edits: server route handler / backend service, .env (for VALYD_API_KEY)
  • Estimated steps: 3 (install SDK or use cURL, set VALYD_API_KEY, call the endpoint)
  • Can complete without human input: NO — you must obtain an App API key from the Valyd Developer Portal (https://dev.valyd.work ) before any call will authenticate.
  • Prerequisites:
    • A Valyd App API key. Pass it as the HTTP header X-API-Key: <App API key> on every request. Get this from the Developer Portal → your project → Credentials: https://dev.valyd.work 
    • A server/backend to make the call from (these are server-to-server APIs; never call them from the browser, because the API key would be exposed).
    • (SDK path only) Node.js with the @valyd/sdk npm package installed.

Overview

Direct, synchronous, server-to-server checks. You build your own UI and call these endpoints from your backend. Every request uses the header X-API-Key: <App API key> — keep this server-side, never ship it to the browser.

Base URL for every endpoint below: https://idp.valyd.work

Every response uses the standard envelope and includes a check object:

{ "success": true, "data": { "session_id": "ses_…", "status": "passed", // passed | failed | review "check": { "type": "id_verification" | "liveness" | "face_match" | "age" | "credential", "status": "passed" | "failed" | "review", "score": 0.97, "data": { /* per-check details */ }, "error": null } }, "error": null }

The JSON blocks shown under each endpoint below are the contents of check.data (the per-check details), unless the block is labeled otherwise.

SDK quick start

The official Node SDK is published on npm as @valyd/sdk (https://www.npmjs.com/package/@valyd/sdk ). Image fields accept a file path via readImage("./x.jpg"), a Buffer, or a base64 / data-URL string. Over plain HTTP, send images as a base64 string in the JSON field (or as a multipart file under the same field name).

Install:

npm i @valyd/sdk

Create a client (do this once and reuse it):

import { VerifyClient, readImage } from "@valyd/sdk"; const verify = new VerifyClient({ apiKey: process.env.VALYD_API_KEY! }); // keep VALYD_API_KEY on the server — never in browser code // get the API key from the Developer Portal → your project → Credentials: https://dev.valyd.work

Set the API key in your environment before running:

export VALYD_API_KEY="<your App API key from https://dev.valyd.work>"

POST /api/v2/id-verification — ID Verification

OCR + authenticity from a government ID.

Method: POST Full URL: https://idp.valyd.work/api/v2/id-verification Auth header: X-API-Key: <App API key>

Fields:

  • front_image (image) required — Front of the ID. File, Buffer, or base64/data-URL.
  • back_image (image) — Back of the ID (when applicable).

Request (cURL):

curl -X POST https://idp.valyd.work/api/v2/id-verification \ -H "X-API-Key: $VALYD_API_KEY" \ -F "front_image=@./id_front.jpg" \ -F "back_image=@./id_back.jpg"

Request (SDK, Node):

import { VerifyClient, readImage } from "@valyd/sdk"; const verify = new VerifyClient({ apiKey: process.env.VALYD_API_KEY! }); const { check } = await verify.standalone.idVerification({ frontImage: readImage("./id_front.jpg"), backImage: readImage("./id_back.jpg"), // optional }); console.log(check.data.fields.full_name, check.data.fields.document_number);

Expected output: HTTP 200 with the standard envelope (success: true); check.type is "id_verification" and check.data is:

{ "fields": { "full_name": "Jane Doe", "fathers_name": "John Doe", "document_number": "X1234567", "date_of_birth": "1990-01-15", "date_of_issue": "2020-03-10", "date_of_expiry": "2030-03-10", "sex": "F", "issuing_state": "CA", "country": "US", "document_type": "driver_license" }, "portrait": "<base64>", "dob": "1990-01-15", "authenticity": { "score": 0.96 } }

POST /api/v2/liveness — Liveness

Passive liveness check. Passes when live_score === 1.

Method: POST Full URL: https://idp.valyd.work/api/v2/liveness Auth header: X-API-Key: <App API key>

Fields:

  • image (image) required — A selfie. File, Buffer, or base64/data-URL.

Request (cURL):

curl -X POST https://idp.valyd.work/api/v2/liveness \ -H "X-API-Key: $VALYD_API_KEY" \ -F "image=@./selfie.jpg"

Request (SDK, Node):

const { check } = await verify.standalone.liveness({ image: readImage("./selfie.jpg"), }); // check.status === "passed" when check.data.live_score === 1

Expected output: HTTP 200 with the standard envelope (success: true); check.type is "liveness". check.status === "passed" when live_score === 1. check.data is:

{ "live_score": 1, "result": "live" }

live_score values: 1 = live, 0 = spoof, < 0 = no face detected.


POST /api/v2/face-match — Face Match

Compare two images. Passes when similarity ≥ threshold (default ~0.95).

Method: POST Full URL: https://idp.valyd.work/api/v2/face-match Auth header: X-API-Key: <App API key>

Fields:

  • image1 (image) required — Reference image (typically the ID portrait).
  • image2 (image) required — Selfie to compare against the reference.

Request (cURL):

curl -X POST https://idp.valyd.work/api/v2/face-match \ -H "X-API-Key: $VALYD_API_KEY" \ -F "image1=@./id_portrait.jpg" \ -F "image2=@./selfie.jpg"

Request (SDK, Node):

const { check } = await verify.standalone.faceMatch({ idImage: readImage("./id_portrait.jpg"), selfie: readImage("./selfie.jpg"), }); // check.data.similarity, check.data.threshold

Expected output: HTTP 200 with the standard envelope (success: true); check.type is "face_match". check.data is:

{ "similarity": 0.973, "threshold": 0.95 }

POST /api/v2/age-verification — Age Verification

JSON body. Computes age from DOB and verifies the requested age bands (no ZKP).

Method: POST Full URL: https://idp.valyd.work/api/v2/age-verification Auth header: X-API-Key: <App API key> Content-Type: application/json

Fields:

  • dob (string, YYYY-MM-DD) required — Date of birth.
  • bands (string[]) required — e.g. ["is_18_plus","is_21_plus"].

Request (cURL):

curl -X POST https://idp.valyd.work/api/v2/age-verification \ -H "X-API-Key: $VALYD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "dob": "1995-06-01", "bands": ["is_18_plus","is_21_plus"] }'

Request (SDK, Node):

const { check } = await verify.standalone.ageVerification({ dob: "1995-06-01", bands: ["is_18_plus", "is_21_plus"], });

Expected output: HTTP 200 with the standard envelope (success: true); check.type is "age". check.data is:

{ "age": 30, "dob": "1995-06-01", "bands": { "is_18_plus": { "verified": true, "min_age": 18 }, "is_21_plus": { "verified": true, "min_age": 21 } } }

POST /api/v2/credential-verification — Credential Verification

Look up a professional license in the provider registry. Registry lookups can take 10–60s — use a generous timeout.

Method: POST Full URL: https://idp.valyd.work/api/v2/credential-verification Auth header: X-API-Key: <App API key> Content-Type: application/json

Fields:

  • first_name (string) required — Required even when required_fields omits it — the registry always needs a name.
  • last_name (string) required — Or supply full_name instead of first/last.
  • license_type (string) required — Provider code, e.g. 'MD'. Alias: provider_code.
  • license_state (string) required — 2-letter state code. Alias: state.
  • license_number (string) required — Alias: license_no.
  • npi (string) — Optional NPI when applicable.

Request (cURL):

curl -X POST https://idp.valyd.work/api/v2/credential-verification \ -H "X-API-Key: $VALYD_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "first_name": "Jane", "last_name": "Doe", "license_type": "MD", "license_state": "CA", "license_number": "A12345", "npi": "1234567890" }'

Request (SDK, Node):

const { check } = await verify.standalone.credentialVerification({ firstName: "Jane", lastName: "Doe", providerCode: "MD", licenseState: "CA", licenseNumber: "A12345", npi: "1234567890", // optional }); // check.data.match, check.data.license

Expected output: HTTP 200 with the standard envelope (success: true); check.type is "credential". check.data is:

{ "match": true, "license": { "license_number": "A12345", "status": "active", "issued_at": "2015-01-01", "expires_at": "2027-01-01", "specialty": "Internal Medicine" } }

POST /api/v2/kyc-credential — KYC + Credential

Combined ID verification + liveness + face match + license lookup, in one call. The license is matched against the name OCR’d from the ID — never a client-supplied name — so the holder cannot impersonate someone else’s license.

Method: POST Full URL: https://idp.valyd.work/api/v2/kyc-credential Auth header: X-API-Key: <App API key>

Fields:

  • front_image (image) required — Front of the government ID.
  • selfie (image) required — Live selfie for liveness + face match.
  • back_image (image) — Back of the ID (when applicable).
  • license_type (string) required — Provider code. Alias: provider_code.
  • license_state (string) required — State code. Alias: state.
  • license_number (string) required — Alias: license_no.
  • npi (string) — Optional NPI.

Request (cURL):

curl -X POST https://idp.valyd.work/api/v2/kyc-credential \ -H "X-API-Key: $VALYD_API_KEY" \ -F "front_image=@./id_front.jpg" \ -F "selfie=@./selfie.jpg" \ -F "license_type=MD" \ -F "license_state=CA" \ -F "license_number=A12345"

Request (SDK, Node):

const result = await verify.standalone.kycCredential({ frontImage: readImage("./id_front.jpg"), selfie: readImage("./selfie.jpg"), providerCode: "MD", licenseState: "CA", licenseNumber: "A12345", }); // result.status === "passed" only when every check passes // result.checks: [id_verification, liveness, face_match, credential] // result.identity: { name, dob } ← name used for the license match

Expected output: HTTP 200 with the standard envelope (success: true). The data object is:

{ "session_id": "ses_…", "status": "passed", "identity": { "name": "Jane Doe", "dob": "1990-01-15" }, "checks": [ { "type": "id_verification", "status": "passed", "data": { /* … */ } }, { "type": "liveness", "status": "passed", "data": { "live_score": 1 } }, { "type": "face_match", "status": "passed", "data": { "similarity": 0.97 } }, { "type": "credential", "status": "passed", "data": { "match": true, "license": { /* … */ } } } ] }

status is "passed" only when every check passes.


Credential discovery

Use these endpoints to build state and license-type pickers in your UI before calling credential-verification or kyc-credential. A provider’s required_fields tells you which license inputs to collect — but always collect first / last name even when it isn’t listed, because the registry lookup needs it.

GET /api/v2/credential/states — list states

Method: GET Full URL: https://idp.valyd.work/api/v2/credential/states Auth header: X-API-Key: <App API key>

Request (cURL):

curl https://idp.valyd.work/api/v2/credential/states \ -H "X-API-Key: $VALYD_API_KEY"

Request (SDK, Node):

const { states } = await verify.credentials.states(); // states: [{ state_name: "California", state_code: "CA" }, …]

Expected output: HTTP 200 with the standard envelope (success: true); data is:

{ "states": [ { "state_name": "California", "state_code": "CA" } ] }

GET /api/v2/credential/states/{state}/providers — list providers for a state

{state} is a 2-letter state code (e.g. CA).

Method: GET Full URL: https://idp.valyd.work/api/v2/credential/states/{state}/providers Auth header: X-API-Key: <App API key>

Request (cURL):

curl https://idp.valyd.work/api/v2/credential/states/CA/providers \ -H "X-API-Key: $VALYD_API_KEY"

Request (SDK, Node):

const { providers } = await verify.credentials.providers("CA"); // providers: [{ provider_code, provider_display_name, credential_name, required_fields, … }]

Expected output: HTTP 200 with the standard envelope (success: true); data is:

{ "providers": [ { "provider_code": "MD", "provider_display_name": "Medical Board of California", "credential_name": "Physician & Surgeon", "required_fields": ["license_number"] } ] }

Errors

Over HTTP, failures return the envelope { success: false, error: { code, message } } with the matching HTTP status:

  • 401 — invalid or missing API key.
  • 400 — validation error (missing field, bad image, unknown provider).
  • 404 — unknown state or provider.
  • 429 — rate limited.
  • 5xx — upstream registry or internal error.

In the SDK, the same failures throw ValydVerifyError with { code, status, message }. Credential registry lookups can take 10–60 seconds — configure a generous client timeout.

import { VerifyClient, ValydVerifyError, readImage } from "@valyd/sdk"; const verify = new VerifyClient({ apiKey: process.env.VALYD_API_KEY!, timeoutMs: 90_000, // registry lookups can be slow }); try { const { check } = await verify.standalone.credentialVerification({ firstName: "Jane", lastName: "Doe", providerCode: "MD", licenseState: "CA", licenseNumber: "A12345", }); } catch (err) { if (err instanceof ValydVerifyError) { console.error(err.status, err.code, err.message); } else { throw err; } }

Common errors

  1. HTTP 401 — invalid or missing API key.

    • Cause: The X-API-Key header is absent or holds a wrong/revoked key.
    • Fix: Set X-API-Key: <App API key> on the request (or apiKey in the SDK client). Obtain a valid key from the Developer Portal → your project → Credentials: https://dev.valyd.work 
  2. Client timeout on credential / kyc-credential calls.

    • Cause: Registry lookups can take 10–60 seconds; the default client timeout aborts first.
    • Fix: Configure a generous timeout (e.g. timeoutMs: 90_000 in the SDK client, or --max-time 90 for cURL).
  3. HTTP 400 / 404 — validation error or unknown state/provider.

    • Cause: A required field is missing, an image is unreadable, or the supplied license_state / license_type (provider_code) is not in the registry.
    • Fix: Call GET /api/v2/credential/states and GET /api/v2/credential/states/{state}/providers first to get valid codes and each provider’s required_fields; always include first/last name even if required_fields omits it.