Verify With Vouchly.
Businesses should never need to perform KYC again if the user already has a Vouchly Identity. Three API calls. Zero document uploads. Full verified identity.
POST /identity/request — business requests user data.
User enters Vouchly ID + email, sees all their data.
User reviews data, then approves or rejects.
GET /identity/{request_id} — business receives identity package.
All endpoints
Quickstart
Verify users without ever storing their documents. Three API calls, zero document uploads.
Get your API key
Generate API keys from the Vouchly dashboard. Use sk_test_* for sandbox and sk_live_* for production.
Request identity access
Ask the user for permission to access their verified identity. Send their Vouchly ID and the fields you need.
curl -X POST https://www.vouchly.online/api/v1/identity/request \
-H "Authorization: Bearer sk_test_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"vouchly_id": "VOUCHLY_A3B8X9K2",
"requested_fields": [
"full_name",
"dob",
"selfie",
"verification_status"
]
}'
→ 201 Created
{
"request_id": "req_123",
"status": "pending_consent"
}Show in-app consent screen
Build a consent screen in your own app using the app_name and requested_fields from the response. Show the user what data is being requested and let them Approve or Deny.
// Response from step 2 includes:
{
"request_id": "req_123",
"status": "pending_consent",
"app_name": "Your App Name",
"requested_fields": ["full_name", "dob", ...]
}
// Render a consent UI in your app:
┌─────────────────────────────┐
│ "Your App Name" wants: │
│ ☑ full_name │
│ ☑ dob │
│ ☑ selfie │
│ │
│ [Deny] [Approve & Share]│
└─────────────────────────────┘
// Call from your frontend (no API key needed):
POST /api/v1/identity/approve { "request_id": "req_123" }
POST /api/v1/identity/reject { "request_id": "req_123" }Receive verified identity
Once approved, fetch the user's complete verified identity package. No more KYC ever needed.
curl -X GET https://www.vouchly.online/api/v1/identity/req_123 \
-H "Authorization: Bearer sk_test_your_api_key"
→ 200 OK
{
"verified": true,
"vouchly_id": "VOUCHLY_A3B8X9K2",
"identity": {
"full_name": "Ada Lovelace",
"date_of_birth": "1990-12-10"
},
"verification": {
"provider": "prembly",
"status": "verified",
"reference": "REF_123456"
},
"images": {
"selfie": "https://...",
"document": "https://..."
},
"trust_score": 87,
"verification_level": 4
}Done. That's it — three API calls, no document uploads, full verified identity. Read the full API reference or try the API Playground.
Authentication
All API requests require authentication via a Bearer API key. Two key types are available:
Deterministic results. No real data used. No credit deducted.
Real verifications. Billed per check. Credits deducted.
How to get your API key
Generate API keys from your Vouchly dashboard. Use sk_test_* keys for sandbox testing and sk_live_* keys for production.
Verify With Vouchly
Request access to a user's verified identity. Creates a consent request — the user must approve before you receive data.
POST /api/v1/identity/request
Authorization: Bearer sk_test_your_api_key
Content-Type: application/json
{
"vouchly_id": "VOUCHLY_A3B8X9K2",
"requested_fields": [
"full_name",
"dob",
"selfie",
"verification_status"
],
"app_name": "My Fintech App"
}
→ 201 Created
{
"request_id": "req_123",
"status": "pending_consent",
"app_name": "My Fintech App",
"app_logo": null,
"requested_fields": ["full_name", "dob", "selfie", "verification_status"]
}Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| vouchly_id | string | Required | User's Vouchly ID (e.g. VOUCHLY_A3B8X9K2) |
| requested_fields | string[] | Optional | Fields to request: full_name, dob, selfie, verification_status (default: all) |
| app_name | string | Optional | Your app name (shown on consent screen, defaults to business name) |
| app_logo | string (URL) | Optional | URL to your app logo (shown on consent screen) |
Use app_name and requested_fields to render a consent screen in your own app. Call POST /api/v1/identity/approve from your frontend (no API key needed) when the user approves.
Preview Identity Data
Users can preview their own identity data (including selfie and document images) before approving a consent request. Call this from your frontend — it's public, no API key needed. Show the returned data in your in-app consent screen so the user can review before they approve.
POST /api/v1/identity/preview
Content-Type: application/json
{
"vouchly_id": "VOUCHLY_A3B8X9K2",
"email": "ada@example.com"
}
→ 200 OK
{
"verified": true,
"vouchly_id": "VOUCHLY_A3B8X9K2",
"identity": {
"full_name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+2348012345678",
"date_of_birth": "1990-12-10",
"address": "123 Main St, Lagos",
"nin": "NIN12345678"
},
"document": {
"type": "nin",
"number": "NIN12345678"
},
"verification": {
"provider": "prembly",
"status": "verified",
"trust_score": 87,
"verification_level": 4
},
"images": {
"selfie": "https://cdn.vouchly.online/...",
"document": "https://cdn.vouchly.online/..."
}
}Request parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| vouchly_id | string | Required | User's Vouchly ID |
| string | Required | User's email address (must match the identity) |
The email must match the identity's registered email. This prevents unauthorized access to identity data.
Call this endpoint from your frontend before the user approves. It returns the user's name, DOB, phone, NIN, address, selfie image, and document image. Show these in your in-app consent screen so the user can review what they're sharing.
Get Identity Data
Receive the user's complete verified identity package. Only works after the user has approved the consent request.
GET /api/v1/identity/req_123
Authorization: Bearer sk_test_your_api_key
→ 200 OK
{
"verified": true,
"vouchly_id": "VOUCHLY_A3B8X9K2",
"identity": {
"full_name": "Ada Lovelace",
"email": "ada@example.com",
"phone": "+2348012345678",
"date_of_birth": "1990-12-10",
"address": "123 Main St, Lagos",
"nin": "NIN12345678"
},
"document": {
"type": "nin",
"number": "NIN12345678"
},
"verification": {
"provider": "prembly",
"status": "verified",
"reference": "REF_123456",
"trust_score": 87,
"verification_level": 4
},
"images": {
"selfie": "https://cdn.vouchly.online/...",
"document": "https://cdn.vouchly.online/..."
},
"trust_score": 87,
"verification_level": 4
}Response fields
| Parameter | Type | Required | Description |
|---|---|---|---|
| verified | boolean | Optional | Whether the identity is verified |
| vouchly_id | string | Optional | User's Vouchly ID |
| identity.full_name | string | Optional | User's verified full name |
| identity.email | string | Optional | User's email address |
| identity.phone | string | Optional | User's phone number |
| identity.date_of_birth | string | Optional | Date of birth from verified document |
| identity.address | string | Optional | Residential address (if available) |
| identity.nin | string | Optional | NIN or document ID number (if available) |
| document.type | string | Optional | Document type: nin / bvn / passport / drivers_license |
| document.number | string | Optional | Document number |
| verification.provider | string | Optional | Verification provider (prembly, youverify, vouchly) |
| verification.status | string | Optional | Verification status: verified / pending |
| verification.trust_score | integer | Optional | Trust score (0–100) |
| verification.verification_level | integer | Optional | Verification level (0–5) |
| images.selfie | string (URL) | Optional | Signed selfie image URL (expires in 15 min) |
| images.document | string (URL) | Optional | Signed document image URL (expires in 15 min) |
| trust_score | integer | Optional | Trust score (0–100) |
| verification_level | integer | Optional | Verification level (0–5) |
The user must approve the consent request before you can fetch their data. Pending or rejected requests return an error.
Approve / Reject
Approve or reject a pending identity access request. Call from your frontend (no API key needed) after the user clicks Approve or Deny on your in-app consent screen.
POST /api/v1/identity/approve
Content-Type: application/json
{
"request_id": "req_123"
}
→ 200 OK
{
"status": "approved"
}POST /api/v1/identity/reject
Content-Type: application/json
{
"request_id": "req_123"
}
→ 200 OK
{
"status": "rejected"
}| Parameter | Type | Required | Description |
|---|---|---|---|
| request_id | string | Required | The request ID from POST /identity/request |
These endpoints are public. The consent page verifies the user's Vouchly ID + email match before calling them.
Trust Score
Public endpoint to retrieve a user's trust score and verification level. No authentication required.
GET /api/v1/trust-score/VOUCHLY_A3B8X9K2
→ 200 OK
{
"trust_score": 92,
"tier": "trusted"
}Score ranges
Webhooks
When a KYC status changes, VOUCHLY sends a signed POST to all registered webhooks.
Register a webhook
POST /api/v1/webhooks/register
{
"url": "https://api.yourco.com/webhooks/VOUCHLY",
"events": ["kyc.status_changed"]
}
→ 201 Created
{
"id": "wh_abc123...",
"url": "https://api.yourco.com/webhooks/VOUCHLY",
"secret": "a1b2c3d4e5f6...",
"is_active": true
}Webhook payload
POST /your-webhook-url
X-vouchly-Signature: sha256=...
X-vouchly-Event: kyc.status_changed
{
"submission_id": "8b3f7a2e...",
"status": "APPROVED",
"timestamp": "2026-06-14T10:21:00Z",
"checks": {
"face_match": { "passed": true, "score": 92 },
"liveness": { "passed": true }
}
}Verify signature (Node.js)
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(payload, signature, secret) {
const expected = createHmac("sha256", secret)
.update(JSON.stringify(payload)).digest("hex");
const received = signature.replace("sha256=", "");
return timingSafeEqual(Buffer.from(received), Buffer.from(expected));
}How results are delivered
When a verification completes, the result is delivered to your business in three ways:
1Immediate API response
POST /api/v1/kyc/prembly-submit and POST /api/v1/kyc/submit return the full verification result synchronously, including the identity object and images when Prembly verification data is available.
2Webhook callback
If you register a webhook, VOUCHLY sends a signed POST to your URL when the status changes. The webhook payload includes the submission_id — use GET /api/v1/kyc/{id}/result to fetch the full data.
3Poll via GET /result
Fetch the complete verification result — including ai_result, prembly_response (identity data, photos, signature), and signed document URLs — at any time:
GET /api/v1/kyc/{id}/result
→ 200 OK
{
"id": "8b3f7a2e-...",
"status": "APPROVED",
"full_name": "Ada Lovelace",
"email": "ada@example.com",
"document_type": "nin",
"ai_result": {
"prembly_response": { ... },
"photo": "...",
"firstName": "Ada",
"lastName": "Lovelace",
"gender": "Female",
"nationality": "Nigerian",
"residentialAddress": "42 Marina Street"
},
"signed_urls": {
"document": "https://...",
"selfie": "https://..."
}
}Identity object
gendermarital_statusnationalitystate_of_originlga_of_originlga_of_residenceaddressphone_numberbvn/ninregistration_date
Images object
photo— government database photosignature— signature imageselfie— user selfie (via GET /result)document— ID document (via GET /result)
Verification object
providertrust_scoreverification_level(1-5)
Sandbox environment
Mirrors production with deterministic test data. Sandbox keys (sk_test_*) return predictable results without deducting credits.
Deterministic results
Use the document number to control the result: APPROVED, REJECTED, or FLAGGED.
Isolated data
Sandbox data is completely isolated from production. Create, test, and delete freely.
Error codes
All errors return a consistent JSON response with a flat error message.
→ 400 Bad Request
{
"error": "document: File exceeds 5MB limit"
}
→ 401 Unauthorized
{
"error": "Invalid API key"
}
→ 402 Payment Required
{
"error": "Insufficient verification credits"
}| Status | When |
|---|---|
400 | Invalid request body, missing required fields, or validation error |
401 | Missing or invalid API key |
402 | Insufficient verification credits |
404 | Resource not found (submission, identity, webhook) |
500 | Internal server error — contact support if persistent |
Best practices
Use consent-based identity request
Always use POST /identity/request to ask users for data. Never access identity data without explicit user approval.
Handle webhooks idempotently
Always check the submission_id before processing. Duplicate webhooks may be sent under rare circumstances.
Poll status as fallback
Use GET /status with exponential backoff if you miss a webhook. Max 10 requests/min per submission.
Use signed URLs immediately
Document URLs expire after 15 minutes. Download files immediately.
Always set consent_given: true
Submissions without explicit user consent will be rejected.
Test in sandbox first
Develop against sandbox (sk_test_) before switching to production (sk_live_) API keys.
Integration Guide
Add "Verify With Vouchly" to your app in 5 steps. No redirect. No iframe. Full in-app consent with user data preview (selfie, document images).
Backend: Request identity access
Your backend calls POST /api/v1/identity/request with the user's Vouchly ID. The response includes app_name and requested_fields so your frontend can build the consent UI.
Frontend: Fetch user data preview (public API)
From your frontend, call POST /api/v1/identity/preview — no API key needed. This returns the user's full identity data including their selfie image and document image from their original KYC (Prembly). Show these in your consent UI so the user can review before approving.
Frontend: Show consent UI with data preview
Render a consent screen in your app showing:
- Your app name (from step 1)
- What data is being requested (from step 1)
- User's personal info — name, DOB, email, phone, NIN, address (from step 2)
- User's selfie photo and document photo (from step 2)
- Trust score and verification level (from step 2)
- Approve / Deny buttons
Frontend: Approve or reject
When the user clicks Approve, call POST /api/v1/identity/approve from your frontend. No API key needed. Same for reject.
Backend: Fetch the verified identity
After approval, your backend calls GET /api/v1/identity/{request_id} with your API key to receive the full verified identity package.
Complete code example (React + Node.js)
Backend (Node.js / any server)
// Step 1: Request identity access
const { request_id, app_name, requested_fields } = await fetch(
"https://www.vouchly.online/api/v1/identity/request",
{
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
vouchly_id: req.body.vouchly_id,
requested_fields: ["full_name", "dob",
"email", "phone", "selfie", "trust_score"]
})
}
).then(r => r.json());
// Return to frontend
res.json({ request_id, app_name, requested_fields });
// Step 5: After user approves, fetch identity
const identity = await fetch(
"https://www.vouchly.online/api/v1/identity/" + request_id,
{
headers: { "Authorization": "Bearer YOUR_API_KEY" }
}
).then(r => r.json());
// Use identity data in your appFrontend (React / plain JS)
// Step 2: Fetch user's data preview (NO API KEY)
const preview = await fetch(
"https://www.vouchly.online/api/v1/identity/preview",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
vouchly_id: userVouchlyId,
email: userEmail // must match their identity
})
}
).then(r => r.json());
// Returns: identity fields + selfie URL + document URL
// Step 3: Render consent UI
<ConsentScreen
appName={app_name}
requestedFields={requested_fields}
preview={preview} // has preview.images.selfie etc
// Step 4: Approve
onApprove={() => fetch(
"https://www.vouchly.online/api/v1/identity/approve",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request_id })
}
)}
onReject={() => fetch(
"https://www.vouchly.online/api/v1/identity/reject",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request_id })
}
)}
/>Full HTML example (plain JS)
If you're not using a framework, here's a complete HTML page that implements the full flow:
<!DOCTYPE html>
<html>
<head><title>Verify with Vouchly</title></head>
<body>
<button onclick="verifyWithVouchly()">Verify With Vouchly</button>
<div id="consentModal" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.5); z-index:1000;">
<div style="background:white; max-width:420px; margin:60px auto; padding:24px; border-radius:12px; max-height:90vh; overflow-y:auto;">
<h3 id="consentAppName" style="font-size:18px;"></h3>
<p style="color:#666; font-size:13px;">wants access to your verified identity.</p>
<!-- Data preview section -->
<div id="previewSection" style="display:none; margin:16px 0;">
<h4 style="font-size:14px; margin-bottom:8px;">Your data</h4>
<div id="previewFields" style="font-size:13px; color:#333;"></div>
<div id="previewImages" style="display:flex; gap:8px; margin-top:12px;"></div>
</div>
<div id="idInputSection">
<input id="vouchlyInput" placeholder="VOUCHLY ID" style="width:100%; padding:8px; margin-bottom:8px;">
<input id="emailInput" type="email" placeholder="Your email" style="width:100%; padding:8px; margin-bottom:8px;">
<button onclick="loadPreview()" style="width:100%; padding:10px; background:#2563eb; color:white; border:none; border-radius:8px;">
Preview my data
</button>
</div>
<ul id="consentFields" style="margin:16px 0; padding:0; list-style:none;"></ul>
<div id="actionButtons" style="display:none; display:flex; gap:8px;">
<button onclick="rejectConsent()" style="flex:1; padding:10px; border:1px solid #ccc; border-radius:8px; background:white;">Deny</button>
<button onclick="approveConsent()" style="flex:1; padding:10px; background:#2563eb; color:white; border:none; border-radius:8px;">
Approve & Share
</button>
</div>
</div>
</div>
<script>
let currentRequestId = null;
async function verifyWithVouchly() {
const vouchlyId = prompt("Enter your Vouchly ID:");
if (!vouchlyId) return;
// Step 1: Backend creates identity request (proxy through your server)
const res = await fetch("/your-backend/verify-request", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ vouchly_id: vouchlyId })
});
const data = await res.json();
currentRequestId = data.request_id;
document.getElementById("consentAppName").textContent = data.app_name;
document.getElementById("idInputSection").style.display = "block";
document.getElementById("consentModal").style.display = "block";
}
// Step 2: Load user's data preview (public, no API key)
async function loadPreview() {
const vId = document.getElementById("vouchlyInput").value.trim().toUpperCase();
const email = document.getElementById("emailInput").value.trim().toLowerCase();
if (!vId || !email) return alert("Enter your Vouchly ID and email");
const preview = await fetch("https://www.vouchly.online/api/v1/identity/preview", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ vouchly_id: vId, email })
}).then(r => r.json());
if (preview.error) return alert(preview.error);
// Show user their data
const fields = document.getElementById("previewFields");
fields.innerHTML = '<p>Name: ' + preview.identity.full_name + '</p>' +
'<p>DOB: ' + (preview.identity.date_of_birth || '--') + '</p>' +
'<p>Email: ' + preview.identity.email + '</p>' +
'<p>Phone: ' + (preview.identity.phone || '--') + '</p>' +
'<p>Trust Score: ' + preview.verification.trust_score + '/100</p>';
// Show selfie and document images
const images = document.getElementById("previewImages");
if (preview.images.selfie) {
images.innerHTML += '<div><img src="' + preview.images.selfie +
'" style="width:120px; height:120px; object-fit:cover; border-radius:8px;"></div>';
}
if (preview.images.document) {
images.innerHTML += '<div><img src="' + preview.images.document +
'" style="width:120px; height:120px; object-fit:cover; border-radius:8px;"></div>';
}
document.getElementById("previewSection").style.display = "block";
document.getElementById("idInputSection").style.display = "none";
}
// Step 4: Approve or reject (no API key needed from frontend)
async function approveConsent() {
await fetch("https://www.vouchly.online/api/v1/identity/approve", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request_id: currentRequestId })
});
document.getElementById("consentModal").style.display = "none";
alert("Approved! Your backend can now fetch identity data.");
}
async function rejectConsent() {
await fetch("https://www.vouchly.online/api/v1/identity/reject", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ request_id: currentRequestId })
});
document.getElementById("consentModal").style.display = "none";
alert("Consent denied.");
}
</script>
</body>
</html>Verification Levels
Every verified identity has a verification level from 0 to 5.
| Level | Label | Description |
|---|---|---|
| 0 | Unverified | No identity or not verified |
| 1 | Email Verified | Email address verified |
| 2 | Phone Verified | Phone number verified |
| 3 | Government ID Verified | Passport, NIN, BVN, or license verified |
| 4 | Face Match Verified | Liveness and face match completed |
| 5 | Business Verified | Business-level verification completed |
