Introduction
Veridian is a Compliance-as-a-Service API. KYC identity verification, sanctions screening, and AML compliance in one REST API — built for fintech developers who want to ship fast without stitching together five vendors.
All requests are made over HTTPS. The API returns JSON for all responses, including errors.
Base URL
https://api-production-b0c5.up.railway.appKey features
- KYC identity verification — document + liveness in under 2 seconds
- Sanctions screening — OFAC, UN, EU, and 50+ global watchlists
- AML compliance — adverse media, PEP screening
- KYB business verification — UBOs, corporate structures
- Transparent pricing — no enterprise contracts
- Webhooks for async result delivery
Authentication
All API requests require a Bearer token in the Authorization header. Requests without a valid API key will return a 401 Unauthorized error.
curl https://api-production-b0c5.up.railway.app/v1/verifications \
-H "Authorization: Bearer your_api_key"Key format
API keys are prefixed with vrd_live_ for production and vrd_test_ for sandbox. Sandbox keys make requests against simulated data and will never charge your account.
Quick start
Go from zero to live KYC verifications in under 15 minutes.
Step 1 — Get your API key
Sign up at the Veridian dashboard. Your API key is available immediately — no approval process, no sales call. Your first 50 verifications are free.
Step 2 — Send your first verification
POST a document image and selfie as base64-encoded strings. The API returns a verification ID immediately while processing continues asynchronously.
curl -X POST https://api-production-b0c5.up.railway.app/v1/verifications \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_type": "passport",
"document_front": "<base64_image>",
"selfie": "<base64_image>",
"reference_id": "user_abc123"
}'
# Response:
# {
# "id": "ver_a1b2c3d4",
# "status": "pending",
# "reference_id": "user_abc123",
# "created_at": "2026-04-08T12:00:00Z"
# }Step 3 — Get the result
Poll GET /v1/verifications/:id or configure a webhook URL in your dashboard to receive results as they complete (recommended for production). Most verifications complete in 1–3 seconds.
curl https://api-production-b0c5.up.railway.app/v1/verifications/ver_a1b2c3d4 \
-H "Authorization: Bearer your_api_key"
# Response:
# {
# "id": "ver_a1b2c3d4",
# "status": "approved",
# "risk_score": 12,
# "sanctions_hit": false,
# "name": "Jane Smith",
# "nationality": "US",
# "completed_at": "2026-04-08T12:00:02Z"
# }/v1/verificationsCreates a new KYC identity verification. Submit a document image and selfie; Veridian handles document classification, liveness detection, and sanctions screening automatically. Returns a verification object with status: "pending".
Request body
document_type*string"passport" | "drivers_license" | "national_id" | "residence_permit"document_front*stringBase64-encoded image of the document front. JPEG or PNG, max 10MB.document_backstringBase64-encoded image of the document back. Required for drivers_license.selfie*stringBase64-encoded selfie photo for liveness detection.reference_idstringYour internal user or session ID. Returned in all responses and webhook events.* Required field
Request example
curl -X POST https://api-production-b0c5.up.railway.app/v1/verifications \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_type": "passport",
"document_front": "<base64_image>",
"selfie": "<base64_image>",
"reference_id": "user_abc123"
}'Response
{
"id": "ver_a1b2c3d4",
"status": "pending",
"reference_id": "user_abc123",
"document_type": "passport",
"created_at": "2026-04-08T12:00:00Z"
}Error codes
invalid_document422Document image could not be read. Ensure the image is clear, well-lit, and under 10MB.unsupported_document422Document type not supported for the detected country.insufficient_funds402Account has no remaining verifications. Upgrade your plan./v1/verifications/:idRetrieves a verification by ID. Once status is no longer pending, all identity fields are populated.
Path parameters
id*stringThe verification ID returned from POST /v1/verifications. Prefixed with "ver_".Request example
curl https://api-production-b0c5.up.railway.app/v1/verifications/ver_a1b2c3d4 \
-H "Authorization: Bearer your_api_key"Response
{
"id": "ver_a1b2c3d4",
"status": "approved",
"reference_id": "user_abc123",
"document_type": "passport",
"risk_score": 12,
"sanctions_hit": false,
"name": "Jane Smith",
"date_of_birth": "1990-05-14",
"nationality": "US",
"document_number": "A12345678",
"document_expiry": "2030-01-01",
"created_at": "2026-04-08T12:00:00Z",
"completed_at": "2026-04-08T12:00:02Z"
}Status values
pendingstringVerification is being processed. Poll again or wait for a webhook.approvedstringIdentity verified. No sanctions hits. Safe to onboard.rejectedstringIdentity could not be verified. Document unreadable, liveness failed, or document expired.reviewstringRequires manual review. Veridian will notify you via webhook when complete.expiredstringVerification was not completed within 30 minutes and has expired./v1/sanctions/screenScreens a person against OFAC, UN, EU, and 50+ global watchlists. Returns a match list with fuzzy match scores. All KYC verifications automatically include a sanctions check — this endpoint is for standalone screening without document verification.
Request body
name*stringFull legal name of the person to screen.date_of_birthstringISO 8601 date (YYYY-MM-DD). Improves match accuracy.nationalitystringISO 3166-1 alpha-2 country code (e.g. "US", "GB"). Improves match accuracy.Request example
curl -X POST https://api-production-b0c5.up.railway.app/v1/sanctions/screen \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Smith",
"date_of_birth": "1990-05-14",
"nationality": "US"
}'Response
{
"id": "scr_x1y2z3w4",
"status": "clear",
"matches": [],
"screened_at": "2026-04-08T12:00:00Z"
}
// If a potential match is found:
{
"id": "scr_x1y2z3w4",
"status": "potential_match",
"matches": [
{
"list": "OFAC SDN",
"name": "Jane A. Smith",
"fuzzy_score": 0.91,
"entity_type": "individual",
"additional_info": "Sanctioned entity — see OFAC reference #12345"
}
],
"screened_at": "2026-04-08T12:00:00Z"
}How fuzzy matching works
Veridian uses trigram similarity combined with phonetic matching (Soundex/Metaphone) to catch name variations, transliterations, and common misspellings. A fuzzy_score of 1.0 is an exact match; scores above 0.85 are flagged as potential matches and require review.
status: "clear" result does not constitute legal compliance advice. You remain responsible for your AML/BSA obligations. Consult your compliance counsel for guidance./v1/billing/checkoutCreates a Paddle checkout session for plan upgrades or initial subscription. Returns a short-lived checkout URL to redirect your user to. Typically used in your onboarding flow after the user selects a plan.
Request body
plan*string"starter" | "growth" | "scale"email*stringCustomer email address. Pre-fills the checkout form.success_url*stringURL to redirect the user after a successful payment.cancel_url*stringURL to redirect the user if they cancel checkout.Request example
curl -X POST https://api-production-b0c5.up.railway.app/v1/billing/checkout \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"plan": "growth",
"email": "user@example.com",
"success_url": "https://example.com/dashboard",
"cancel_url": "https://example.com/pricing"
}'Response
{
"checkout_url": "https://checkout.paddle.com/checkout/...",
"expires_at": "2026-04-08T13:00:00Z"
}The checkout URL expires after 1 hour. Redirect the user immediately after receiving it. Do not cache or reuse checkout URLs.
TypeScript SDK
The official TypeScript/Node.js SDK wraps the REST API with full type safety. Compatible with Node.js 18+ and Bun.
Installation
npm install @veridian/sdkInitialization
import VeridianClient from '@veridian/sdk'
const veridian = new VeridianClient(process.env.VERIDIAN_API_KEY!)
// Sandbox mode
const sandbox = new VeridianClient(process.env.VERIDIAN_TEST_KEY!)Create a verification
const verification = await veridian.createVerification({
documentType: 'passport',
documentFront: documentBase64,
selfie: selfieBase64,
referenceId: userId,
})
// Poll for result
const result = await veridian.getVerification(verification.id)
if (result.status === 'approved') {
console.log('Verified:', result.name, result.nationality)
console.log('Risk score:', result.riskScore) // 0–100
console.log('Sanctions hit:', result.sanctionsHit) // boolean
}Sanctions screening
const screen = await veridian.screenSanctions({
name: 'Jane Smith',
dateOfBirth: '1990-05-14',
nationality: 'US',
})
if (screen.status === 'clear') {
console.log('No sanctions matches found')
} else {
console.log('Potential matches:', screen.matches)
}Webhook event types
import { VeridianWebhookEvent } from '@veridian/sdk'
// Type-safe webhook handling
function handleWebhook(event: VeridianWebhookEvent) {
switch (event.type) {
case 'verification.completed':
console.log(event.data.status) // 'approved' | 'rejected' | 'review'
break
case 'verification.flagged':
console.log(event.data.reason)
break
}
}Python SDK
The official Python SDK. Compatible with Python 3.9+. Supports both sync and async usage via asyncio.
Installation
pip install veridian-kycInitialization
from veridian import VeridianClient
import os
client = VeridianClient(os.environ["VERIDIAN_API_KEY"])
# Async client
from veridian.async_client import AsyncVeridianClient
async_client = AsyncVeridianClient(os.environ["VERIDIAN_API_KEY"])Create a verification
verification = client.create_verification(
document_type="passport",
document_front=document_base64,
selfie=selfie_base64,
reference_id=user_id,
)
# Poll for result
result = client.get_verification(verification.id)
if result.status == "approved":
print(f"Verified: {result.name}, {result.nationality}")
print(f"Risk score: {result.risk_score}") # 0-100
print(f"Sanctions hit: {result.sanctions_hit}") # boolAsync usage
import asyncio
from veridian.async_client import AsyncVeridianClient
async def verify_user(document_b64: str, selfie_b64: str) -> dict:
async with AsyncVeridianClient(api_key) as client:
verification = await client.create_verification(
document_type="passport",
document_front=document_b64,
selfie=selfie_b64,
)
return await client.get_verification(verification.id)
result = asyncio.run(verify_user(doc, selfie))Webhooks
Veridian sends webhook events to your server when verification status changes. Configure your webhook URL in the dashboard under Settings → Webhooks.
Webhook events
verification.completedeventVerification finished. status is "approved", "rejected", or "review".verification.flaggedeventPotential sanctions hit found. Requires manual review.verification.expiredeventVerification was not completed within 30 minutes.Payload format
{
"id": "evt_p9q8r7s6",
"type": "verification.completed",
"created_at": "2026-04-08T12:00:02Z",
"data": {
"verification_id": "ver_a1b2c3d4",
"status": "approved",
"reference_id": "user_abc123",
"risk_score": 12,
"sanctions_hit": false
}
}Signature verification
Every webhook request includes an x-veridian-signature header. This is an HMAC-SHA256 signature of the raw request body using your webhook secret. Always verify this signature before processing events.
const crypto = require('crypto')
function verifyWebhook(rawBody, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex')
// Use timingSafeEqual to prevent timing attacks
const expectedBuf = Buffer.from(expected, 'hex')
const sigBuf = Buffer.from(signature, 'hex')
if (expectedBuf.length !== sigBuf.length) return false
return crypto.timingSafeEqual(expectedBuf, sigBuf)
}
// Express example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['x-veridian-signature']
if (!verifyWebhook(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature')
}
const event = JSON.parse(req.body)
// handle event...
res.status(200).send('OK')
})Retry behavior
If your endpoint returns a non-2xx status code or times out (30 second timeout), Veridian will retry delivery up to 5 times with exponential backoff: immediately, 1 min, 5 min, 30 min, 2 hours. After 5 failed attempts the event is marked as failed and you can replay it from the dashboard.
200 OK as quickly as possible — ideally before doing any processing. Queue events for async handling to avoid timeouts.Error handling
Veridian uses conventional HTTP status codes. All errors return a JSON body with a machine-readable code and a human-readable message.
Error response format
{
"error": {
"code": "invalid_document",
"message": "The document image could not be processed. Ensure the image is clear, well-lit, and under 10MB.",
"request_id": "req_abc123xyz"
}
}Always log the request_id — include it when contacting support to help us trace the exact request.
HTTP status codes
200 OKsuccessRequest succeeded.201 CreatedsuccessResource created successfully.400 Bad RequesterrorMalformed request body or missing required fields.401 UnauthorizederrorAPI key missing or invalid.402 Payment RequirederrorAccount has no remaining verifications. Upgrade your plan.404 Not FounderrorThe requested resource does not exist.422 UnprocessableerrorRequest body is valid JSON but contains invalid values.429 Too Many RequestserrorRate limit exceeded. Default: 100 requests/minute.500 Internal ErrorerrorServer error. Our team is automatically notified. Retry with exponential backoff.Error codes
invalid_api_key401API key is missing, malformed, or has been revoked.insufficient_funds402Account has exhausted its monthly verification quota.invalid_document422Document image could not be read or classified.liveness_failed422Selfie liveness check failed — possible spoof attempt.document_expired422The submitted document has passed its expiry date.unsupported_document422Document type is not supported for the detected country.rate_limit_exceeded429Too many requests. Back off and retry after the Retry-After header value.internal_error500Unexpected server error. Retry with exponential backoff.TypeScript error handling
import { VeridianError } from '@veridian/sdk'
try {
const result = await veridian.createVerification({ ... })
} catch (err) {
if (err instanceof VeridianError) {
console.error(err.code) // 'invalid_document'
console.error(err.message) // Human-readable description
console.error(err.requestId) // For support tickets
console.error(err.status) // HTTP status code
if (err.code === 'insufficient_funds') {
// redirect user to upgrade page
}
}
}