Skip to main content

POST /v1/verify/init

Starts a new verification. Returns a verificationId plus three presigned URLs (doc-front, doc-back, selfie) the browser uses to upload images directly to Cloudflare R2.

POST https://api.xxuxe.online/v1/verify/init

Why pre-create the verification ID

Calling /init first (instead of just uploading and submitting) does two things:

  1. Lets the client stitch its own logs together before anything reaches the backend
  2. Makes the later /submit call idempotent — same verificationId in the submit body always means the same DB row

Authentication

Bearer token. Either publishable (qv_pub_*) or secret (qv_sec_*).

Authorization: Bearer qv_pub_FJJWXMA2RN2XPRDK6YJX4KTVD0XSQHW9

Request body

All fields are optional, but country and documentType significantly improve OCR accuracy.

FieldTypeRequiredDescription
userRefstringNoYour own user identifier — echoed back in webhook events. Max 128 chars
countrystringNoISO 3166-1 alpha-2 code (e.g., PY, BR, MX). Hints document type
documentTypestringNoOne of: dni, passport, drivers_license, national_id, other
submittedFullNamestringNoFull name as the user typed it — used for fuzzy matching. Max 255 chars

Example request

curl

curl -X POST https://api.xxuxe.online/v1/verify/init \
-H "Authorization: Bearer qv_pub_FJJWXMA2RN2XPRDK6YJX4KTVD0XSQHW9" \
-H "Content-Type: application/json" \
-d '{
"userRef": "customer-12345",
"country": "PY",
"documentType": "dni",
"submittedFullName": "Juan Carlos Perez"
}'

JavaScript / Node.js

const response = await fetch('https://api.xxuxe.online/v1/verify/init', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VERIDIA_PUBLISHABLE_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
userRef: 'customer-12345',
country: 'PY',
documentType: 'dni',
submittedFullName: 'Juan Carlos Perez',
}),
});

const data = await response.json();
console.log(data.verificationId);

Python

import os
import requests

response = requests.post(
"https://api.xxuxe.online/v1/verify/init",
headers={
"Authorization": f"Bearer {os.environ['VERIDIA_PUBLISHABLE_KEY']}",
"Content-Type": "application/json",
},
json={
"userRef": "customer-12345",
"country": "PY",
"documentType": "dni",
"submittedFullName": "Juan Carlos Perez",
},
)
response.raise_for_status()
data = response.json()
print(data["verificationId"])

PHP

<?php
$payload = json_encode([
"userRef" => "customer-12345",
"country" => "PY",
"documentType" => "dni",
"submittedFullName" => "Juan Carlos Perez",
]);

$ch = curl_init("https://api.xxuxe.online/v1/verify/init");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"Authorization: Bearer " . $_ENV['VERIDIA_PUBLISHABLE_KEY'],
"Content-Type: application/json",
]);

$data = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $data['verificationId'];

Response

200 OK

{
"verificationId": "vf_AG07CDWRRFQV4T05ZXG2",
"uploads": {
"docFront": {
"url": "https://...r2.cloudflarestorage.com/veridia-uploads/verif/tn_xyz/vf_AG07CDWRRFQV4T05ZXG2/doc-front.jpg?...",
"key": "verif/tn_xyz/vf_AG07CDWRRFQV4T05ZXG2/doc-front.jpg",
"method": "PUT",
"headers": {
"Content-Type": "image/jpeg"
}
},
"docBack": {
"url": "https://...r2.cloudflarestorage.com/veridia-uploads/verif/tn_xyz/vf_AG07CDWRRFQV4T05ZXG2/doc-back.jpg?...",
"key": "verif/tn_xyz/vf_AG07CDWRRFQV4T05ZXG2/doc-back.jpg",
"method": "PUT",
"headers": {
"Content-Type": "image/jpeg"
}
},
"selfie": {
"url": "https://...r2.cloudflarestorage.com/veridia-uploads/verif/tn_xyz/vf_AG07CDWRRFQV4T05ZXG2/selfie.jpg?...",
"key": "verif/tn_xyz/vf_AG07CDWRRFQV4T05ZXG2/selfie.jpg",
"method": "PUT",
"headers": {
"Content-Type": "image/jpeg"
}
}
},
"expiresAt": 1714604000
}

Response fields

FieldTypeDescription
verificationIdstringUnique ID for this verification — pass to /submit and /verify/:id
uploads.docFrontobjectPresigned URL for the front of the document (always returned)
uploads.docBackobjectPresigned URL for the back of the document (use only if needed)
uploads.selfieobjectPresigned URL for the selfie (always returned)
uploads.*.urlstringThe full presigned URL — PUT raw image bytes here
uploads.*.keystringOpaque handle — pass back in /submit
uploads.*.methodstringAlways "PUT"
uploads.*.headersobjectHeaders your PUT must include (typically just Content-Type)
expiresAtnumberUnix timestamp when the presigned URLs stop working (15 minutes after init)

Uploading the images

After /init, your client uploads each image directly to R2 using the presigned URL. Do not proxy through your backend — you'd be paying for double bandwidth and adding unnecessary latency.

async function uploadImage(presigned, blob) {
const response = await fetch(presigned.url, {
method: presigned.method, // "PUT"
headers: presigned.headers,
body: blob,
});
if (!response.ok) {
throw new Error(`Upload failed: ${response.status}`);
}
}

await uploadImage(initResponse.uploads.docFront, docFrontBlob);
await uploadImage(initResponse.uploads.selfie, selfieBlob);
// docBack only if your document type needs it

The widget handles all of this for you. You only need this code if you're building a custom mobile or server flow.

Errors

HTTPError codeWhen
400invalid_bodyRequest body failed Zod validation (see detail.fieldErrors)
401unauthorizedMissing Authorization header
401invalid_keyKey revoked, expired, or never existed
403origin_not_allowedPublishable key called from non-whitelisted domain
403insufficient_creditsTenant has 0 credits
429rate_limitedHit the /init rate limit (60 req/min by default)
500internalSomething broke on our side — include the requestId when reporting

Full catalog: Errors.

Notes

  • verificationId format: vf_ prefix + 16-24 alphanumeric chars (regex: ^vf_[A-Za-z0-9]{16,24}$)
  • The presigned URLs are valid for 15 minutes — plenty for a user to complete the widget flow
  • Storage layout: verif/<tenantId>/<verificationId>/<role>.jpg (so retention cleanup is straightforward)
  • The submittedFullName is not stored in the R2 path — it's PII and lives only in the intent record (and your DB)

What's next

After /init, your client uploads the images, then calls:

POST /v1/verify/submit →