API Reference
Agent Quick-Start
- Source URL: https://docs.valyd.work/verify#api-sessions
- Credentials / env vars needed: VALYD_API_KEY (your App API key, sent as the
X-API-Keyheader) - Files an integrator edits: none — reference only (server code uses these endpoints)
- Estimated steps: 0
- Can complete without human input: YES — this is a reference page; no actions required.
- Prerequisites:
- An App API key from the Valyd Developer Portal (https://dev.valyd.work )
- For workflow-based sessions, a
workflow_idconfigured in the Developer Portal
Base URL for all endpoints: https://idp.valyd.work
Authentication (applies to every call)
Every call uses the header:
X-API-Key: <App API key>A Bearer token is also accepted:
Authorization: Bearer <App API key>Get your App API key from the Valyd Developer Portal (https://dev.valyd.work ). Authenticated endpoints are billed per call against your App.
Sessions
| Method | Path | Description |
|---|---|---|
| POST | /api/v2/session | Create a hosted verification session |
| GET | /api/v2/session | List sessions |
| GET | /api/v2/session/{id} | Retrieve a session |
| PATCH | /api/v2/session/{id}/status | Manual override: `{ status: APPROVED |
POST /api/v2/session — create a hosted verification session
Full URL: https://idp.valyd.work/api/v2/session
curl -X POST https://idp.valyd.work/api/v2/session \
-H "X-API-Key: $VALYD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "workflow_id": "WORKFLOW_ID", "vendor_data": "user-123" }'workflow_id (get it from the Developer Portal: https://dev.valyd.work ) selects the bundle of services to run. vendor_data is your own correlation string, echoed back on webhooks and the decision.
Expected output: HTTP 200/201 with a created session object including its session id and a hosted verification URL to send the user to.
GET /api/v2/session — list sessions
Full URL: https://idp.valyd.work/api/v2/session
curl https://idp.valyd.work/api/v2/session \
-H "X-API-Key: $VALYD_API_KEY"Expected output: HTTP 200 with a list of session objects.
GET /api/v2/session/{id} — retrieve a session
Full URL: https://idp.valyd.work/api/v2/session/{id}
curl https://idp.valyd.work/api/v2/session/SES_ID \
-H "X-API-Key: $VALYD_API_KEY"Expected output: HTTP 200 with the session object, including its current status (see statuses.md for all values).
PATCH /api/v2/session/{id}/status — manual override
Full URL: https://idp.valyd.work/api/v2/session/{id}/status
Manually force a terminal decision. Body must set status to APPROVED or DECLINED.
curl -X PATCH https://idp.valyd.work/api/v2/session/SES_ID/status \
-H "X-API-Key: $VALYD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "status": "APPROVED" }'Expected output: HTTP 200 with the updated session reflecting the overridden status.
Workflows
Workflows are configured in the Developer Portal (https://dev.valyd.work ). A workflow bundles services and exposes a stable workflow_id that you pass when creating a session.
Available services that a workflow can bundle:
id_verificationlivenessface_matchagecredential
There is no REST endpoint to create a workflow from this reference — workflows are defined in the Developer Portal, and you reference the resulting workflow_id in POST /api/v2/session.
Core checks
Run a single check directly without a hosted session.
| Method | Path | Description |
|---|---|---|
| POST | /api/v2/id-verification | OCR + authenticity from a government ID |
| POST | /api/v2/liveness | Passive liveness from a selfie |
| POST | /api/v2/face-match | Selfie vs reference portrait |
| POST | /api/v2/age-verification | Age bands from a DOB |
| POST | /api/v2/credential-verification | Professional license lookup |
Each is called with the X-API-Key header. Example:
curl -X POST https://idp.valyd.work/api/v2/id-verification \
-H "X-API-Key: $VALYD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "...": "service-specific input" }'Expected output: HTTP 200 with the check result, including a per-check status of passed, failed, or review (see statuses.md).
Full URLs:
https://idp.valyd.work/api/v2/id-verificationhttps://idp.valyd.work/api/v2/livenesshttps://idp.valyd.work/api/v2/face-matchhttps://idp.valyd.work/api/v2/age-verificationhttps://idp.valyd.work/api/v2/credential-verification
Decision
| Method | Path | Description |
|---|---|---|
| GET | /api/v2/session/{id}/decision | Full decision and per-check data |
Full URL: https://idp.valyd.work/api/v2/session/{id}/decision
curl https://idp.valyd.work/api/v2/session/SES_ID/decision -H "X-API-Key: $VALYD_API_KEY"Expected output: HTTP 200 with the full decision and per-check data for the session. Call this after a terminal webhook to retrieve all extracted data (the webhook is only a notification).
POST /api/v2/location — the location check
A real GPS fix is always mandatory. This step can never be skipped: a blocked permission or
missing coordinates is a hard failed (never a pass, never a review).
What the status means depends on what you asked for:
| You send | status | data.match | Notes |
|---|---|---|---|
expected_latitude + expected_longitude + radius_m | passed inside the radius, failed outside it | true / false | The status is the verdict. error reads e.g. "You are 119.3 km from the required location (must be within 200 m)." |
expected_latitude + expected_longitude, no radius_m | passed | null | We cannot judge without a threshold — we just report data.distance_m; you decide. |
| No expected point | passed | absent | Capture-only: returns the captured coordinates + accuracy. |
Request:
curl -X POST https://idp.valyd.work/api/v2/location \
-H "X-API-Key: $VALYD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"latitude": 37.3382, "longitude": -121.8863, "accuracy": 12,
"expected_latitude": 37.3390, "expected_longitude": -121.8850,
"radius_m": 200
}'Response (data.check):
{
"type": "location",
"status": "passed",
"score": 137.4,
"data": {
"latitude": 37.3382, "longitude": -121.8863, "accuracy": 12,
"source": "gps", "captured_at": "2026-07-13T18:04:10+00:00",
"expected_latitude": 37.339, "expected_longitude": -121.885,
"distance_m": 137.4, "radius_m": 200, "match": true
}
}Outside the radius the same call returns "status": "failed", "match": false and the human-readable
error. There is no separate location-match endpoint and no evv_presence bundle — location is the
single location check (feature key location), and it does the geofence verdict itself.
IF you need a yes/no "is the user at the place?": → send expected_latitude + expected_longitude + radius_m and read `status`
IF you only want the distance: → send the expected point WITHOUT radius_m and read `data.distance_m`
IF you only want the coordinates: → send just latitude/longitude/accuracy
IF status is failed with reason permission_denied: → the user blocked location; ask them to allow it and retry (it cannot be skipped)Errors & rate limits
Errors use the same response envelope with success: false and an HTTP status that reflects the error class.
{
"success": false,
"error": { "code": "invalid_api_key", "message": "API key is missing or invalid" }
}| HTTP | Code | When |
|---|---|---|
| 400 | validation_error | Malformed body or missing required field |
| 401 | invalid_api_key | Missing/invalid X-API-Key header |
| 404 | not_found | Session or resource does not exist |
| 422 | unprocessable | Could not process the input (e.g. unreadable image) |
| 429 | rate_limited | Public/demo endpoints rate limit exceeded |
| 500 | internal_error | Unexpected server error — safe to retry |
Rate limits and billing:
- Authenticated endpoints are billed per call against your App.
- Public/demo endpoints return HTTP 429 with
code: "rate_limited"when limits are exceeded.
Decision tree — handling an error response
IF HTTP 400 (validation_error): → fix the request body / add the missing required field, then retry.
IF HTTP 401 (invalid_api_key): → set the X-API-Key header to a valid App API key (from https://dev.valyd.work); do not retry until fixed.
IF HTTP 404 (not_found): → verify the session/resource id; do not retry the same id.
IF HTTP 422 (unprocessable): → the input could not be processed (e.g. unreadable image); collect new/clearer input and retry.
IF HTTP 429 (rate_limited): → back off and retry later; you have exceeded public/demo limits.
IF HTTP 500 (internal_error): → safe to retry (with backoff).