API Reference · v1
REST API for workflow-based verification management. Every request authenticates with a server-side API key sent in the X-Authorization header, next to the ID the key belongs to.
On This Page
- Base URL & Authentication
- Conventions
- Action Mapping
- Verifications
- Create Verification —
POST /verifications - Retrieve Verification —
GET /verifications/{id} - List Verifications —
GET /verifications - Update Verification Status —
PATCH /verifications/{id}/status - Update Verification Data —
PATCH /verifications/{id}/data - Delete Verification —
DELETE /verifications/{id} - Create Verification PDF —
POST /verifications/{id}/pdf
- Create Verification —
- Bans
- List Bans —
GET /bans - Create Ban —
POST /bans - Delete Ban —
DELETE /bans/{id}
- List Bans —
- Workflows
- List Workflows —
GET /workflows - Create Workflow —
POST /workflows - Retrieve Workflow —
GET /workflows/{id} - Update Workflow —
PATCH /workflows/{id} - Deleting Workflows
- List Workflows —
- Billing
- Get Balance —
GET /billing/balance - Balance Top-Up —
POST /billing/top-up
- Get Balance —
- Webhooks
- Security & Operations
Base URL & Authentication
Send every request over HTTPS to the base URL, with two headers: an API key and the ID it belongs to. Verification endpoints take a workflow’s key; ban and workflow endpoints take your organization’s key.
Base URL
https://api.osvoy.com/api/v1
Headers
| Endpoints | Send These Headers |
|---|---|
| Verifications All seven endpoints | X-Authorization: <workflow API key>X-Workflow-ID: <workflow ID> |
| Bans, Workflows | X-Authorization: <organization API key>X-Organization-ID: <organization ID> |
| Requests with a body | Content-Type: application/json |
Example Request
curl -X POST https://api.osvoy.com/api/v1/verifications \
-H "X-Authorization: $OSVOY_WORKFLOW_KEY" \
-H "X-Workflow-ID: <workflow-id>" \
-H "Content-Type: application/json" \
-d '{"vendor_data": "cust_20931847"}'
Note: Keys are shown once — when they’re generated or rotated. Keep them on your server; never ship one in a browser or a mobile app.
Conventions
Shared rules across every endpoint — base URL, auth, IDs, pagination, rate limits and error format.
| Convention | Value |
|---|---|
| Base URL | https://api.osvoy.com/api/v1 |
| Authentication | X-Authorization plus X-Workflow-ID or X-Organization-ID — see Base URL & Authentication |
| Content Type | application/json |
| ID Format | UUID v4 |
| Timestamps | ISO 8601, UTC — 2026-04-23T14:30:00Z |
| Pagination | ?page=<number> |
| Filtering | ?status=passed&workflow_id=… |
| Idempotency | The last request sent is the one applied. |
| Rate Limit | 100 requests per minute per organization (burst 200). X-RateLimit-* headers are returned. |
| Errors | RFC 7807 Problem Details with code and message. |
HTTP Status Codes
| Code | Meaning |
|---|---|
200 OK | Successful read / update / resource created |
202 Accepted | PDF generation queued |
204 No Content | Successful delete |
400 Bad Request | Validation error |
401 Unauthorized | Missing or invalid API key |
402 Payment Required | Insufficient credit balance |
403 Forbidden | Key lacks scope for action |
404 Not Found | Resource does not exist |
409 Conflict | Invalid state transition or duplicate |
422 Unprocessable Entity | Semantic validation failed |
429 Too Many Requests | Rate limit exceeded |
5xx | Server error — retry with backoff |
Resource Model
Verification ── produced by ──> Workflow
│
├── status transitions
│ (created → in_progress → manual_review → passed/failed/expired/cancelled)
├── verification data (document data, liveness results, checks, etc.)
└── can be exported as PDF report
Ban ── scoped by organization
Workflow ── reusable, customizable template for hosted KYC flows
Balance ── prepaid credits consumed per verification
Action Mapping
All 17 actions mapped to REST endpoints across four groups: Verifications, Bans, Workflows and Billing.
| # | Action | Method | Path |
|---|---|---|---|
| 1 | create-verification | POST | /verifications |
| 2 | retrieve-verification | GET | /verifications/{id} |
| 3 | list-verification | GET | /verifications |
| 4 | update-verification-status | PATCH | /verifications/{id}/status |
| 5 | update-verification-data | PATCH | /verifications/{id}/data |
| 6 | delete-verification | DELETE | /verifications/{id} |
| 7 | create-verification-pdf | POST | /verifications/{id}/pdf |
| 8 | list-bans | GET | /bans |
| 9 | create-ban | POST | /bans |
| 10 | delete-ban | DELETE | /bans/{id} |
| 11 | list-workflows | GET | /workflows |
| 12 | create-workflow | POST | /workflows |
| 13 | retrieve-workflow | GET | /workflows/{id} |
| 14 | update-workflow | PATCH | /workflows/{id} |
| 15 | delete-workflow | — | — |
| 16 | get-balance | GET | /billing/balance |
| 17 | balance-top-up | POST | /billing/top-up |
Verifications
A verification is an instance of a workflow applied to one end user. Creating one returns a hosted verification_url you redirect the user to.
Create Verification
POST /verifications
Action: create-verification · Workflow Key
Creates a new verification and returns a short-lived hosted verification_url — it lives for 1 hour.
Request Body
{
"workflow_id": "8b2f3c4d-0a1e-4b2f-9c3d-1e2f3a4b5c6d",
"vendor_data": "user-42"
}
Response · 200 OK
{
"id": "bbd23629-1a24-4d26-bb12-a905b9ae85b8",
"workflow_id": "8b2f3c4d-0a1e-4b2f-9c3d-1e2f3a4b5c6d",
"vendor_data": "user-42",
"status": "created",
"verification_url": "https://verify.yourdomain.com/bbd23629-1a24-4d26-bb12-a905b9ae85b8",
"created_at": "2026-04-23T13:11:13.625Z",
"expires_at": "2026-04-23T14:11:13.625Z"
}
Errors:
400invalid workflow ·402insufficient balance.
Retrieve Verification
GET /verifications/{id}
Action: retrieve-verification · Workflow Key
Returns the full verification record, including extracted data once the verification is complete.
Response · 200 OK
{
"id": "84f56ba9-fcef-47b0-87f5-1a07ae70396e",
"workflow_id": "8b2f3c4d-0a1e-4b2f-9c3d-1e2f3a4b5c6d",
"vendor_data": null,
"status": "passed",
"created_by_api": false,
"warnings": [],
"files": [],
"verification_data": {
"document": {
"document_type": "id_card",
"document_number": null,
"personal_number": null,
"issuing_state": "TUR",
"first_name": null,
"last_name": null,
"date_of_birth": null,
"expiration_date": null,
"gender": null,
"is_document_verified": true
},
"liveness": {
"active_score": null,
"passive_score": null,
"is_live": true
},
"face_match": {
"face_similarity": null,
"is_face_verified": true
},
"ip_analysis": {
"time_zone": null,
"isp": null,
"organization": null,
"is_datacenter": false,
"is_vpn_tor": false
}
},
"created_at": "2026-04-20T12:41:35.787Z",
"finished_at": "2026-04-21T10:18:00.167Z",
"webhook_sent_successfully": false
}
List Verifications
GET /verifications
Action: list-verification · Workflow Key
Paginated, filterable list of verifications.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | integer | Optional | Page number |
status | enum | Optional | created, in_progress, manual_review, passed, failed, expired, cancelled |
workflow_id | uuid | Optional | Filter by workflow |
vendor_data | string | Optional | Filter by your user identifier |
issuing_state | string | Optional | 3-letter country code (e.g. TUR for Türkiye) |
created_after | datetime | Optional | ISO 8601 |
created_before | datetime | Optional | ISO 8601 |
Response · 200 OK
{
"count": 22,
"prevPage": 1,
"nextPage": 3,
"results": [
{
"id": "84f56ba9-fcef-47b0-87f5-1a07ae70396e",
"status": "passed",
"workflow_id": "8b2f3c4d-0a1e-4b2f-9c3d-1e2f3a4b5c6d",
"vendor_data": "user-42",
"created_at": "2026-04-20T12:41:35.787Z"
}
]
}
Note:
prevPageisnullon the first page;nextPageisnullon the last.
Update Verification Status
PATCH /verifications/{id}/status
Action: update-verification-status · Workflow Key
Manual review override. Only these transitions are allowed: manual_review → passed | failed, passed → failed, failed → passed. Every transition is audit logged.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
status | enum | Required | passed or failed |
comment | string | Optional | Why you decided. Kept as the session’s note, up to 100 characters. |
Request Body
{
"status": "passed",
"comment": "Verified against secondary documentation by agent."
}
Response · 200 OK
{
"id": "84f56ba9-fcef-47b0-87f5-1a07ae70396e",
"status": "passed",
"updated_at": "2026-04-23T15:00:00Z"
}
Errors:
409on an invalid transition (e.g.created → passed).
Update Verification Data
PATCH /verifications/{id}/data
Action: update-verification-data · Workflow Key
Updates writable fields on a verification — typically vendor_data and manual corrections to extracted fields. Core biometric and document results are immutable.
Request Body
{
"vendor_data": "user-42-migrated",
"document_data": {
"document_type": "id_card",
"document_number": "X12345678",
"personal_number": "12345678901",
"issuing_state": "TUR",
"first_name": "Mehmet Ali",
"last_name": "Yılmaz",
"date_of_birth": "1990-06-15",
"expiration_date": "2030-06-14",
"gender": "M"
}
}
Response · 200 OK
{
"id": "84f56ba9-fcef-47b0-87f5-1a07ae70396e",
"vendor_data": "user-42-migrated",
"updated_at": "2026-04-23T15:05:00Z"
}
Errors:
422if a field is immutable or has an invalid value.
Delete Verification
DELETE /verifications/{id}
Action: delete-verification · Workflow Key
GDPR-style hard delete with no recovery. Removes the record and its artefacts (images, videos, PDFs).
Errors:
404not found ·409if the verification is currentlyin_progress.
Create Verification PDF
POST /verifications/{id}/pdf
Action: create-verification-pdf · Workflow Key
Generates a signed PDF report of the verification. The PDF is deleted 3 hours after creation.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
language | enum | Required | en or tr |
Request Body
{
"language": "en"
}
Response · 200 OK
{
"pdf_url": "https://cdn.osvoy.com/pdf/pdf_01HX....pdf",
"expires_at": "2026-04-24T14:30:00Z"
}
Note:
languageacceptsenortr.
Bans
A ban blocks a subject — by vendor_data, document_number, personal_number or ip — from being approved in future verifications. Any matching session auto-flips to failed, with an audit log entry.
List Bans
GET /bans
Action: list-bans · Organization Key
Lists ban entries, optionally filtered by data type and an exact-match lookup.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | integer | Optional | Pagination |
data_type | enum | Optional | vendor_data, personal_number, document_number, ip |
data | string | Optional | Exact-match lookup |
Response · 200 OK
{
"count": 22,
"prevPage": 1,
"nextPage": 3,
"results": [
{
"id": 1,
"data_type": "document_number",
"data": "X12345678",
"reason": "Confirmed fraudulent document",
"created_at": "2026-04-20T09:10:00Z"
}
]
}
Create Ban
POST /bans
Action: create-ban · Organization Key
Blocks a subject by vendor_data, document_number, personal_number or ip. Any matching session auto-flips to failed, with an audit log entry.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
data_type | enum | Required | vendor_data, personal_number, document_number, ip |
data | string | Required | The value to block, up to 500 characters. |
reason | string | Optional | Why it’s blocked, up to 500 characters. |
Request Body
{
"data_type": "document_number",
"data": "X12345678",
"reason": "Confirmed fraudulent document"
}
Response · 200 OK
{
"id": 1,
"data_type": "document_number",
"data": "X12345678",
"reason": "Confirmed fraudulent document",
"created_at": "2026-04-20T09:10:00Z"
}
Errors:
409if the same subject is already banned.
Delete Ban
DELETE /bans/{id}
Action: delete-ban · Organization Key
Removes a ban. Verifications it already declined stay declined.
Workflows
A workflow is a reusable template that defines which verification modules run, in what order, and with what decision rules.
List Workflows
GET /workflows
Action: list-workflows · Organization Key
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
page | integer | Optional | Page number |
limit | integer | Optional | Results per page |
Response · 200 OK
{
"results": [
{
"id": "8b2f3c4d-0a1e-4b2f-9c3d-1e2f3a4b5c6d",
"name": "Standard KYC",
"created_at": "2026-01-10T10:00:00Z",
"updated_at": "2026-04-01T12:00:00Z"
}
]
}
Create Workflow
POST /workflows
Action: create-workflow · Organization Key
Workflows are reusable templates defining which verification modules run, in what order, and with what decision rules. id_verification.maximum_age is a year of birth: anyone born earlier is rejected.
Request Body
{
"name": "Standard KYC",
"primary_color": "#2563EB",
"font": "inter",
"redirection_url": "https://example.com",
"redirection_enabled": true,
"id_verification": {
"minimum_age": 18,
"maximum_age": 1961
},
"liveness": {
"method": "3d_action",
"liveness_3d_action_order": ["left", "right", "up"],
"active_decline": 30,
"active_review": 60,
"passive_decline": 30,
"passive_review": 60
},
"face_match": {
"decline_max": 30,
"review_max": 60
},
"ip_analysis": {
"ip_mismatch": "review",
"tor_vpn": "decline",
"expected_ip": "review"
}
}
Response · 200 OK
{
"id": "8b2f3c4d-0a1e-4b2f-9c3d-1e2f3a4b5c6d",
"name": "Standard KYC",
"created_at": "2026-04-23T13:11:13.625Z"
}
Retrieve Workflow
GET /workflows/{id}
Action: retrieve-workflow · Organization Key
Returns the full workflow, including modules, configs and decision rules. Same shape as the create response.
Update Workflow
PATCH /workflows/{id}
Action: update-workflow · Organization Key
Partial update — only the supplied fields change, and a settings object you send is merged into the stored one.
Request Body
{
"name": "Standard KYC (v2)",
"id_verification": {
"minimum_age": 21
}
}
Response · 200 OK
{
"id": "8b2f3c4d-0a1e-4b2f-9c3d-1e2f3a4b5c6d",
"name": "Standard KYC (v2)",
"updated_at": "2026-04-23T15:10:00Z"
}
Errors:
409on a conflicting update.
Deleting Workflows
Warning:
delete-workflow— deleting a workflow through the API isn’t allowed, to prevent accidental data loss. Delete one from the Workflows page instead.
Billing
Prepaid credits are consumed per verification. Top up with a saved payment method or a one-time payment token.
Get Balance
GET /billing/balance
Action: get-balance
Each verification module has a credit cost (id_verification = 10, liveness = 5, aml = 3). The cost is deducted when a verification is created and refunded if it expires unused.
Response · 200 OK
{
"balance": 4275,
"currency": "credits",
"last_updated": "2026-04-23T14:25:00Z"
}
Balance Top-Up
POST /billing/top-up
Action: balance-top-up
Purchases credits, charged to a saved payment method on the account or a one-time payment token.
Request Body
{
"amount": 10000
}
Response · 200 OK
{
"transaction_url": "https://api.osvoy.com/txn_01HX..."
}
Errors:
402if the payment method fails ·409on a duplicateidempotency_key.
Webhooks
Register destination URLs out of band (console or a separate endpoint) and receive signed POSTs. Each request is signed with HMAC-SHA256 in X-Signature.
verification.status
Event
POST <your-webhook-url>
Sent whenever a verification transitions to a terminal state.
Payload
{
"session_id": "sess_8f3a2c1d-4b5e-6f7a-8b9c-0d1e2f3a4b5c",
"status": "passed",
"timestamp": "2026-05-15T14:32:18Z",
"vendor_data": "customer_ref_98765",
"webhook_type": "status.updated",
"workflow_id": 3,
"created_at": "2026-05-15T14:28:42Z",
"webhook_url": "https://api.example.com/webhooks/kyc",
"redirect_url": "https://app.example.com/verification/complete",
"face_match": {
"similarity_score": 0.82,
"verified": true,
"selfie_image_url": "https://cdn.example.com/sessions/sess_8f3a2c1d/selfie.jpg",
"warnings": [
{
"label": "face-features-match-reference-image",
"type": "danger"
}
]
},
"document": {
"front_side_image_url": "https://cdn.example.com/sessions/sess_8f3a2c1d/doc_front.jpg",
"front_side_video_url": "https://cdn.example.com/sessions/sess_8f3a2c1d/doc_front.mp4",
"back_side_image_url": "https://cdn.example.com/sessions/sess_8f3a2c1d/doc_back.jpg",
"back_side_video_url": "https://cdn.example.com/sessions/sess_8f3a2c1d/doc_back.mp4",
"face_image_url": "https://cdn.example.com/sessions/sess_8f3a2c1d/doc_face.jpg",
"issuing_state": "TR",
"nationality": "TR",
"document_type": "id_card",
"document_number": "A12B34567",
"personal_number": "12345678901",
"first_name": "Mehmet Ali",
"last_name": "Yılmaz",
"full_name": "Mehmet Ali Yılmaz",
"date_of_birth": "1990-06-15",
"expiration_date": "2030-06-14",
"gender": "M",
"warnings": [
{
"label": "user-meets-minimum-age-requirement",
"type": "danger"
}
],
"verified_by_gov_db": true
},
"ip_analysis": {
"location": "Istanbul, Turkey",
"time_zone": "Europe/Istanbul",
"isp": "Turk Telekom",
"organization": "TTNet A.S.",
"is_vpn_tor": false,
"is_datacenter": false,
"device": "iPhone 14 Pro",
"browser": "Safari 17.4",
"os_name": "iOS",
"platform": "mobile",
"verified": true,
"warnings": []
},
"liveness": {
"verified": true,
"selfie_image_url": "https://cdn.example.com/sessions/sess_8f3a2c1d/liveness_selfie.jpg",
"selfie_video_url": "https://cdn.example.com/sessions/sess_8f3a2c1d/liveness_selfie.mp4",
"warnings": [],
"active_liveness": {
"mode": "active",
"actions_matched": ["left", "right"],
"actions_requested": ["left", "right"],
"liveness_score": 0.78
},
"passive_liveness": {
"verified": true,
"liveness_score": 0.71
}
}
}
Note: Set the destination URL on each workflow, under Customization in the workflow editor. Deliveries go to that workflow’s URL.
Verifying Deliveries
Every delivery carries three headers. Recompute the signature over the raw body with the workflow’s secret key and compare before you trust the payload.
Delivery Headers
| Header | Description |
|---|---|
content-type | application/json; charset=utf-8 |
x-timestamp | Unix time, in seconds, when the delivery was sent. It isn’t part of the signature. |
x-signature | Hex-encoded HMAC-SHA256 of the raw request body, keyed with the workflow’s secret key. No prefix. |
Example · Node.js
import crypto from "node:crypto";
// rawBody: the exact bytes you received — check them before JSON.parse.
// secretKey: the workflow's secret key (sk_…), kept on your server.
export function isGenuineDelivery(rawBody, signatureHeader, secretKey) {
const expected = crypto
.createHmac("sha256", secretKey)
.update(rawBody)
.digest("hex");
const received = String(signatureHeader || "");
return (
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
);
}
Note: A delivery that fails or doesn’t answer 2xx is retried up to 3 times, 60 seconds apart. Answer quickly and do slow work afterwards.
verification.status_update
Event · Opt-In
POST <your-webhook-url>
Sent on every status change — created → in_progress → manual_review → passed — so you can follow a session without polling. It carries what changed, not the results; those still arrive once in verification.status. Turned on per workflow: ask Support.
Payload
{
"session_id": "84f56ba9-fcef-47b0-87f5-1a07ae70396e",
"webhook_type": "verification.status_update",
"status": "manual_review",
"previous_status": "in_progress",
"is_final": false,
"current_step": "liveness",
"fail_reason": null,
"reject_reason": null,
"vendor_data": "customer_ref_98765",
"workflow_id": 3,
"webhook_url": "https://api.example.com/webhooks/kyc",
"redirect_url": "https://app.example.com/verification/complete",
"created_at": "2026-05-15T14:28:42Z",
"finished_at": null,
"changed_at": "2026-05-15T14:31:05Z",
"timestamp": 1778855465
}
Security & Operations
API Keys
API keys are scoped per organization and manage all organization data. For testing or playground access, contact Support.
PII at Rest
Encrypted with per-tenant keys. The DELETE /verifications/{id} action guarantees physical removal at the same time.
Audit Log
Maintained for every POST, PATCH and DELETE — retrievable separately for a compliance review.
Consent & Disclosure
The caller’s responsibility. Show the end user who is verifying them and link to your privacy policy before redirecting to the hosted verification_url.