Identity Verification (IDV) API
The Identity Verification API provides a single endpoint for submitting KYC (Know Your Customer) identity verification data. The API runs sanctions screening first, then verifies users through document verification and selfie matching.
Integration Options
There are two ways to integrate identity verification:
- Call the API directly — submit verification data to
POST /v2/idv/verifyyourself using your merchant credentials, as described on this page. - Generate a Web SDK Link — create a hosted, shareable verification link from the portal (or via API) and send it to your end user. No client-side integration required. See Web SDK Link.
Both options require a flow, which you create in the portal and which determines which fields, documents, and verification methods are required. A group containing that flow is optional when calling /v2/idv/verify directly (groupingId), but required when generating a Web SDK Link (groupId). See Flows & Groups before you start.
Base URL
https://api2.cycurid.com
Authentication
/v2/idv/verify accepts either of the following:
- Basic Authentication — your merchant credentials in the Authorization header:
- Username:
{{merchantApiKey}} - Password:
{{merchantSecret}}
- Username:
- Bearer Token — a short-lived SDK access token issued by the Web SDK Link token exchange (
POST /v2/sdk/session/exchange). Used when verification is performed client-side after a user opens a Web SDK Link.
Processing Flow
- Validation — field formats, empty values, required fields (configured per flow)
- Sanctions Screening — checks applicant against the sanctions database
- If unreachable → request blocked (fail closed)
- If no matches → proceed to KYC
- If matches found → match is recorded and passed along with the KYC result (see Response Format)
- KYC Verification — document verification, face match, MRZ extraction, OCR comparison
- Logging — request, response, and uploaded files stored in database
Endpoints
POST /v2/idv/verify
Submit identity verification data with user information and identity documents.
Content-Type
multipart/form-data
Any field present in the request must have a non-empty value. Sending a field with an empty string will return a 400 error.
Request Parameters
| Field | Type | Required | Description |
|---|---|---|---|
firstName | string | Yes | User's first name (English characters only) |
lastName | string | Yes | User's last name (English characters only) |
externalUserId | string | Yes | Unique identifier for the user in your system |
flowId | string | Yes | ID of the flow to run this verification against (max 36 characters). Created in the portal — see Flows & Groups. |
groupingId | string | No | ID of a group this verification belongs to (max 255 characters). If provided, must be a group that flowId belongs to, or the request is rejected. If omitted, the verification isn't associated with any group. |
sessionId | string | No | Links this call to a session created via the Web SDK Link flow (max 255 characters). |
middleName | string | No | User's middle name (English characters only) |
dob | string | No | Date of birth in MM/DD/YYYY format |
phone | string | No | Phone number in international format (e.g., +16045551234) |
email | string | No | Email address |
street | string | No | Street address line 1 (English characters only) |
street2 | string | No | Street address line 2 (English characters only) |
city | string | No | City (English characters only) |
state | string | No | State or province code (e.g., BC, ON) (English characters only) |
postcode | string | No | Postal or ZIP code |
country | string | No | Country code (ISO 3166-1 alpha-3, e.g., CAN, USA) |
nationality | string | No | Nationality code (ISO 3166-1 alpha-3) |
documentTypes | string | No | Comma-separated list: PASSPORT, DRIVERS, ID_CARD, RESIDENCY_CARD. ID_CARD is a broad category covering national ID cards, military IDs, and more — see Supported ID Card Types. RESIDENCY_CARD (residency permit) is currently only supported for documents issued by an EU member state or the United Arab Emirates — see Residency Permit. |
documentCountry | string | Conditional | Country code for issued documents (ISO 3166-1 alpha-3). Required when documentTypes is provided. |
ssn | string | Conditional | Social Security Number. Required when the flow's configuration marks SSN as required (globally or for a specific country override). |
Which optional fields are required, which document types are accepted, and which biometric method is used are all configured on the flow (flowId) in the portal's Configuration page, including per-country overrides. See Flows & Groups. When a required field is missing, the API returns a specific error listing which fields are missing.
File Upload Fields
All files: max 10 MB, formats JPEG, PNG, WEBP, BMP, PDF.
| Field | Type | Required When | Description |
|---|---|---|---|
passportFront | file | documentTypes includes PASSPORT | Front side of passport |
driversFront | file | documentTypes includes DRIVERS | Front side of driver's license |
driversBack | file | documentTypes includes DRIVERS | Back side of driver's license |
idCardFront | file | documentTypes includes ID_CARD | Front side of ID card |
idCardBack | file | documentTypes includes ID_CARD | Back side of ID card |
residencyFront | file | documentTypes includes RESIDENCY_CARD | Front side of residency permit |
residencyBack | file | documentTypes includes RESIDENCY_CARD | Back side of residency permit |
selfie | file | Flow's biometric type is selfie file upload | Static user selfie for face matching |
- Passport: Front side only — passports never require a back side
- Driver's License / ID Card / Residency Permit: Whether the back side is required depends on the flow's configuration and on the document's issuing country and type — some countries issue a single-sided variant of a document that doesn't need a back image at all. If the flow doesn't explicitly configure a side, the API falls back to a per-country/per-type lookup — see Checking Whether a Back Image Is Required. When in doubt, call that endpoint before rendering your upload UI rather than assuming every document is double-sided.
- Which side is "front": The
*Frontfield must contain the image with the applicant's face photo visible, regardless of which physical side of the card that photo is printed on. Most documents print the photo on the side you'd naturally think of as the front, but some (e.g. Chinese resident ID cards) print it on the reverse — for those, upload the photo-bearing side asidCardFront/residencyFront/driversFrontand the other side (if required) as the corresponding*Backfield, even though that's the physical front of the card. This matters most fordriversFront— unlikeID_CARD/RESIDENCY_CARD, face matching forDRIVERSonly ever looks at the*Frontimage. - Biometric field:
selfieis required whenever any document is provided, and cannot be submitted alone - Empty files: File fields must contain actual file data — empty file fields are rejected
ID_CARD Covers More Than One DocumentNew merchants sometimes assume ID_CARD means a single, uniform "national ID card" format. It doesn't — it's a catch-all category for whatever non-passport, non-driver's-license government-issued photo ID applies in the applicant's country (national ID cards, voter cards, military IDs, and more). Residence/residency permits are not part of ID_CARD — they're their own document type, RESIDENCY_CARD, see Residency Permit below. See Supported ID Card Types for the full breakdown of what's accepted under ID_CARD, and configure which countries/types your flow accepts on the flow's Configuration page in the portal.
Residency Permit
RESIDENCY_CARD (label: "Residency Permit") is submitted via residencyFront / residencyBack, the same way as ID_CARD or DRIVERS. It's currently supported only for permits issued by an EU member state or the United Arab Emirates — documentCountry must be one of:
ARE, AUT, BEL, BGR, CYP, CZE, DEU, DNK, ESP, EST, FIN, FRA, GRC, HRV, HUN, IRL, ITA, LTU, LUX, LVA, MLT, NLD, POL, PRT, ROU, SVK, SVN, SWE
For every one of these countries, both front and back are currently required.
A residency permit issued by one of the countries above is treated as authoritative proof that the applicant currently resides somewhere non-restricted — even if their self-declared country or nationality is itself a restricted jurisdiction (e.g. a Russian national submitting nationality=RUS alongside a German-issued RESIDENCY_CARD, documentCountry=DEU, can still pass). This exemption applies to country and nationality only — it does not apply if documentCountry itself is a restricted jurisdiction, and it does not apply to any other document type.
GET /v2/idv/document-requirements
Look up whether a given country's variant of a document type needs a back-side image, before rendering your upload UI or building the request. Useful for DRIVERS, ID_CARD, and RESIDENCY_CARD, since the back side is optional for some country/document-type combinations — many countries issue single-sided variants (e.g. some national ID cards), and requiring an unnecessary back-image upload adds friction for no benefit.
Content-Type
None — this is a query-parameter-only GET request, no request body.
Uses the same Authentication as POST /v2/idv/verify (Basic or Bearer).
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
country | string | Yes | ISO 3166-1 alpha-2/alpha-3 code, or a country name in any supported language/script (e.g. CAN, CA, Canada) |
docType | string | Yes | One of PASSPORT, DRIVERS, ID_CARD, RESIDENCY_CARD |
Response
{
"success": true,
"needsBack": false,
"hasOneSidedVariant": true,
"known": true
}
| Field | Description |
|---|---|
needsBack | Whether a back-side image is expected for this country/document-type pair. PASSPORT always returns false. |
hasOneSidedVariant | true when this country's document-type category mixes single- and double-sided real-world documents (e.g. a country where both a one-sided slip and a two-sided card are both classified as ID_CARD). When true, needsBack reflects the safer default, but don't hard-require the back image client-side — some applicants will legitimately hold the single-sided variant. |
known | false when this country/document-type pair isn't in the lookup dataset yet. Treat an unknown pair as back required (the API does the same fail-safe on submission) — an unknown pair returns needsBack: true by default. |
A flow's own configuration always takes precedence over this lookup — if a flow explicitly marks a *Back field as required or not required (globally or via a country override), POST /v2/idv/verify uses that instead. This endpoint reflects the fallback used when the flow doesn't explicitly configure that field. See Flows & Groups.
Error Responses
{
"success": false,
"message": "country and docType query parameters are required"
}
{
"success": false,
"message": "Unsupported docType 'FOO' — expected one of: ID_CARD, DRIVERS, RESIDENCY_CARD, PASSPORT"
}
{
"success": false,
"message": "'Wakanda' is not a valid ISO country code or country name"
}
Usage Examples
Example 1: Passport with OCR Match
- cURL
- JavaScript
- Python
curl -X POST 'https://api2.cycurid.com/v2/idv/verify' \
-u '{{merchantApiKey}}:{{merchantSecret}}' \
-F 'firstName=Jamie' \
-F 'lastName=Ross' \
-F 'dob=05/14/1990' \
-F 'phone=+15145550142' \
-F 'email=jamie.ross@example.com' \
-F 'street=123 Example Street' \
-F 'city=Montreal' \
-F 'state=QC' \
-F 'postcode=H0H 0H0' \
-F 'country=CAN' \
-F 'nationality=CAN' \
-F 'documentTypes=PASSPORT' \
-F 'documentCountry=CAN' \
-F 'externalUserId=user-67890' \
-F 'flowId=flow_a1b2c3d4' \
-F 'groupingId=group_e5f6g7h8' \
-F 'ocrMatch=true' \
-F 'passportFront=@/path/to/passport.jpg' \
-F 'selfie=@/path/to/selfie.jpg'
import fs from 'fs';
const formData = new FormData();
formData.append('firstName', 'Jamie');
formData.append('lastName', 'Ross');
formData.append('dob', '05/14/1990');
formData.append('phone', '+15145550142');
formData.append('email', 'jamie.ross@example.com');
formData.append('street', '123 Example Street');
formData.append('city', 'Montreal');
formData.append('state', 'QC');
formData.append('postcode', 'H0H 0H0');
formData.append('country', 'CAN');
formData.append('nationality', 'CAN');
formData.append('documentTypes', 'PASSPORT');
formData.append('documentCountry', 'CAN');
formData.append('externalUserId', 'user-67890');
formData.append('flowId', 'flow_a1b2c3d4');
formData.append('groupingId', 'group_e5f6g7h8');
formData.append('ocrMatch', 'true');
formData.append('passportFront', new Blob([fs.readFileSync('/path/to/passport.jpg')]));
formData.append('selfie', new Blob([fs.readFileSync('/path/to/selfie.jpg')]));
const response = await fetch('https://api2.cycurid.com/v2/idv/verify', {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`${merchantApiKey}:${merchantSecret}`).toString('base64'),
},
body: formData,
});
const result = await response.json();
console.log(result);
import requests
response = requests.post(
'https://api2.cycurid.com/v2/idv/verify',
auth=(merchant_api_key, merchant_secret),
data={
'firstName': 'Jamie',
'lastName': 'Ross',
'dob': '05/14/1990',
'phone': '+15145550142',
'email': 'jamie.ross@example.com',
'street': '123 Example Street',
'city': 'Montreal',
'state': 'QC',
'postcode': 'H0H 0H0',
'country': 'CAN',
'nationality': 'CAN',
'documentTypes': 'PASSPORT',
'documentCountry': 'CAN',
'externalUserId': 'user-67890',
'flowId': 'flow_a1b2c3d4',
'groupingId': 'group_e5f6g7h8',
'ocrMatch': 'true',
},
files={
'passportFront': open('/path/to/passport.jpg', 'rb'),
'selfie': open('/path/to/selfie.jpg', 'rb'),
},
)
print(response.json())
Example 2: Driver's License (Canada)
- cURL
- JavaScript
- Python
curl -X POST 'https://api2.cycurid.com/v2/idv/verify' \
-u '{{merchantApiKey}}:{{merchantSecret}}' \
-F 'firstName=Taylor' \
-F 'middleName=Lee' \
-F 'lastName=Kim' \
-F 'dob=09/23/1987' \
-F 'phone=+16045550187' \
-F 'email=taylor.kim@example.com' \
-F 'street=123 Example Street' \
-F 'street2=Suite 100' \
-F 'city=Vancouver' \
-F 'state=BC' \
-F 'postcode=V0V 0V0' \
-F 'country=CAN' \
-F 'nationality=CAN' \
-F 'documentTypes=DRIVERS' \
-F 'documentCountry=CAN' \
-F 'externalUserId=user-12345' \
-F 'flowId=flow_a1b2c3d4' \
-F 'groupingId=group_e5f6g7h8' \
-F 'ocrMatch=true' \
-F 'driversFront=@/path/to/drivers_front.jpg' \
-F 'driversBack=@/path/to/drivers_back.jpg' \
-F 'selfie=@/path/to/selfie.jpg'
import fs from 'fs';
const formData = new FormData();
formData.append('firstName', 'Taylor');
formData.append('middleName', 'Lee');
formData.append('lastName', 'Kim');
formData.append('dob', '09/23/1987');
formData.append('phone', '+16045550187');
formData.append('email', 'taylor.kim@example.com');
formData.append('street', '123 Example Street');
formData.append('street2', 'Suite 100');
formData.append('city', 'Vancouver');
formData.append('state', 'BC');
formData.append('postcode', 'V0V 0V0');
formData.append('country', 'CAN');
formData.append('nationality', 'CAN');
formData.append('documentTypes', 'DRIVERS');
formData.append('documentCountry', 'CAN');
formData.append('externalUserId', 'user-12345');
formData.append('flowId', 'flow_a1b2c3d4');
formData.append('groupingId', 'group_e5f6g7h8');
formData.append('ocrMatch', 'true');
formData.append('driversFront', new Blob([fs.readFileSync('/path/to/drivers_front.jpg')]));
formData.append('driversBack', new Blob([fs.readFileSync('/path/to/drivers_back.jpg')]));
formData.append('selfie', new Blob([fs.readFileSync('/path/to/selfie.jpg')]));
const response = await fetch('https://api2.cycurid.com/v2/idv/verify', {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`${merchantApiKey}:${merchantSecret}`).toString('base64'),
},
body: formData,
});
const result = await response.json();
console.log(result);
import requests
response = requests.post(
'https://api2.cycurid.com/v2/idv/verify',
auth=(merchant_api_key, merchant_secret),
data={
'firstName': 'Taylor',
'middleName': 'Lee',
'lastName': 'Kim',
'dob': '09/23/1987',
'phone': '+16045550187',
'email': 'taylor.kim@example.com',
'street': '123 Example Street',
'street2': 'Suite 100',
'city': 'Vancouver',
'state': 'BC',
'postcode': 'V0V 0V0',
'country': 'CAN',
'nationality': 'CAN',
'documentTypes': 'DRIVERS',
'documentCountry': 'CAN',
'externalUserId': 'user-12345',
'flowId': 'flow_a1b2c3d4',
'groupingId': 'group_e5f6g7h8',
'ocrMatch': 'true',
},
files={
'driversFront': open('/path/to/drivers_front.jpg', 'rb'),
'driversBack': open('/path/to/drivers_back.jpg', 'rb'),
'selfie': open('/path/to/selfie.jpg', 'rb'),
},
)
print(response.json())
Example 3: Residency Permit (Germany)
- cURL
- JavaScript
- Python
curl -X POST 'https://api2.cycurid.com/v2/idv/verify' \
-u '{{merchantApiKey}}:{{merchantSecret}}' \
-F 'firstName=Anna' \
-F 'lastName=Novak' \
-F 'dob=02/17/1991' \
-F 'phone=+491745550123' \
-F 'email=anna.novak@example.com' \
-F 'street=Musterstrasse 12' \
-F 'city=Berlin' \
-F 'postcode=10115' \
-F 'country=DEU' \
-F 'nationality=DEU' \
-F 'documentTypes=RESIDENCY_CARD' \
-F 'documentCountry=DEU' \
-F 'externalUserId=user-33333' \
-F 'flowId=flow_a1b2c3d4' \
-F 'groupingId=group_e5f6g7h8' \
-F 'ocrMatch=true' \
-F 'residencyFront=@/path/to/residency_front.jpg' \
-F 'residencyBack=@/path/to/residency_back.jpg' \
-F 'selfie=@/path/to/selfie.jpg'
import fs from 'fs';
const formData = new FormData();
formData.append('firstName', 'Anna');
formData.append('lastName', 'Novak');
formData.append('dob', '02/17/1991');
formData.append('phone', '+491745550123');
formData.append('email', 'anna.novak@example.com');
formData.append('street', 'Musterstrasse 12');
formData.append('city', 'Berlin');
formData.append('postcode', '10115');
formData.append('country', 'DEU');
formData.append('nationality', 'DEU');
formData.append('documentTypes', 'RESIDENCY_CARD');
formData.append('documentCountry', 'DEU');
formData.append('externalUserId', 'user-33333');
formData.append('flowId', 'flow_a1b2c3d4');
formData.append('groupingId', 'group_e5f6g7h8');
formData.append('ocrMatch', 'true');
formData.append('residencyFront', new Blob([fs.readFileSync('/path/to/residency_front.jpg')]));
formData.append('residencyBack', new Blob([fs.readFileSync('/path/to/residency_back.jpg')]));
formData.append('selfie', new Blob([fs.readFileSync('/path/to/selfie.jpg')]));
const response = await fetch('https://api2.cycurid.com/v2/idv/verify', {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`${merchantApiKey}:${merchantSecret}`).toString('base64'),
},
body: formData,
});
const result = await response.json();
console.log(result);
import requests
response = requests.post(
'https://api2.cycurid.com/v2/idv/verify',
auth=(merchant_api_key, merchant_secret),
data={
'firstName': 'Anna',
'lastName': 'Novak',
'dob': '02/17/1991',
'phone': '+491745550123',
'email': 'anna.novak@example.com',
'street': 'Musterstrasse 12',
'city': 'Berlin',
'postcode': '10115',
'country': 'DEU',
'nationality': 'DEU',
'documentTypes': 'RESIDENCY_CARD',
'documentCountry': 'DEU',
'externalUserId': 'user-33333',
'flowId': 'flow_a1b2c3d4',
'groupingId': 'group_e5f6g7h8',
'ocrMatch': 'true',
},
files={
'residencyFront': open('/path/to/residency_front.jpg', 'rb'),
'residencyBack': open('/path/to/residency_back.jpg', 'rb'),
'selfie': open('/path/to/selfie.jpg', 'rb'),
},
)
print(response.json())
documentCountry must be the country that issued the residency permit — see Residency Permit for the full list of currently supported issuing countries and the front/back requirement.
Example 4: Minimal Required Fields
- cURL
- JavaScript
- Python
curl -X POST 'https://api2.cycurid.com/v2/idv/verify' \
-u '{{merchantApiKey}}:{{merchantSecret}}' \
-F 'firstName=Casey' \
-F 'lastName=Nguyen' \
-F 'externalUserId=user-22222' \
-F 'flowId=flow_a1b2c3d4'
const formData = new FormData();
formData.append('firstName', 'Casey');
formData.append('lastName', 'Nguyen');
formData.append('externalUserId', 'user-22222');
formData.append('flowId', 'flow_a1b2c3d4');
const response = await fetch('https://api2.cycurid.com/v2/idv/verify', {
method: 'POST',
headers: {
Authorization: 'Basic ' + Buffer.from(`${merchantApiKey}:${merchantSecret}`).toString('base64'),
},
body: formData,
});
const result = await response.json();
console.log(result);
import requests
response = requests.post(
'https://api2.cycurid.com/v2/idv/verify',
auth=(merchant_api_key, merchant_secret),
data={
'firstName': 'Casey',
'lastName': 'Nguyen',
'externalUserId': 'user-22222',
'flowId': 'flow_a1b2c3d4',
},
)
print(response.json())
Response Format
Once a request passes validation and reaches the verification stage, it returns HTTP 200 — the success field in the response body indicates the outcome. If the sanctions or KYC verification service itself is unreachable, a non-200 error is returned instead (see Error Responses).
Success Response — Verification Passed
{
"success": true,
"metadata": {
"externalUserId": "user-67890",
"info": {
"firstName": "Jamie",
"lastName": "Ross",
"dob": "1990-05-14",
"country": "CAN"
},
"ocrMatch": true,
"documents": [
{ "idDocType": "PASSPORT", "country": "CAN" }
]
},
"documents_processed": 1,
"facemap_id": "69dd249e...",
"document_results": [
{
"document_type": "PASSPORT",
"face_match": true,
"similarity_score": 0.682,
"message": "Faces match"
}
],
"mrz_data": {
"mrz_data": {
"status": "SUCCESS",
"given_name": "JAMIE",
"surname": "ROSS",
"document_number": "AB1234567",
"issuer_code": "CAN",
"birth_date": "1990-05-14",
"expiry_date": "2034-11-23",
"sex": "F",
"mrz_type": "TD3"
},
"success": true
},
"ocr_comparison": {
"match": true,
"match_score": 100,
"field_results": {},
"mismatches": [],
"warnings": []
},
"kycResults": {
"status": 200,
"results": [],
"total": { "value": 0, "relation": "eq" },
"queryParams": { "name": "Jamie Ross", "country": "CAN" }
}
}
kycResults.queryParams reflects the flattened data actually used to query the sanctions database for this request (name, DOB, country, etc.) — useful for auditing what was screened.
Success Response — Sanctions Match Found
{
"success": false,
"message": "Sanctions screening failed — potential match found",
"kycResults": {
"status": 200,
"results": [
{
"id": "entity-id",
"caption": "Entity Name",
"schema": "Person",
"score": 0.85,
"match": true,
"datasets": ["us_ofac_sdn"]
}
],
"total": { "value": 1, "relation": "eq" }
}
}
Success Response — KYC Verification Failed
Once sanctions screening and the verification service have both run, the response is broken into a result object per check. The root success field is false if any check failed; each result object carries its own success flag and, on failure, an error message describing why.
{
"success": false,
"idvResult": {
"success": false,
"facematch": false,
"similarityScore": 0.42,
"error": "The selfie does not match the photo in the document. Please ensure the selfie is of the same person shown in the document."
},
"documentResult": {
"success": true
},
"ocrResult": {
"success": true,
"match": true
},
"kycResults": {
"success": true,
"results": [],
"total": { "value": 0, "relation": "eq" }
}
}
More than one check can fail on the same request. For example, a flow with document, OCR, and minimum-age checks enabled might return:
{
"success": false,
"idvResult": {
"success": true,
"facematch": true,
"similarityScore": 0.6550370454788208,
"sdkStatus": "declined",
"sessionId": "add505db-d95c-4e4c-ac36-b3479f299e49"
},
"documentResult": {
"success": false,
"error": "Failed to verify the barcode data on the driver's license",
"code": 2507,
"source": "PDF417"
},
"minimumAgeResult": {
"success": false,
"error": "Unable to compare age: date of birth could not be extracted from the document"
},
"kycResults": {
"success": true,
"results": [],
"total": { "value": 0, "relation": "eq" },
"queryParams": { "name": "Jamie Ross", "firstName": "Jamie", "lastName": "Ross" }
}
}
In this example the applicant's face matched (idvResult.success: true), but the driver's license barcode could not be verified (documentResult) and the date of birth could not be extracted for the age check — so the overall success is false and idvResult.sdkStatus is "declined".
Result Objects
| Object | Present When | Description |
|---|---|---|
idvResult | Always | Selfie and face-match outcome. Includes facematch, similarityScore, and, on failure, an error message. May include sdkStatus and sessionId. |
documentResult | A document is submitted | Document verification outcome — expiry, issuing-country match, front/back consistency, and MRZ/barcode extraction. Runs regardless of the flow's ocrMatch setting. On failure, includes error, a numeric code, and a source (e.g. MRZ, PDF417) identifying which check failed. |
ocrResult | Flow has ocrMatch enabled | Outcome of comparing OCR/MRZ-extracted document data (name, DOB, etc.) against the submitted applicant information. |
minimumAgeResult | Flow has a minimum or maximum age configured | Outcome of the age requirement check. |
kycResults | Always | Sanctions screening result — see Sanctions Match Found above. |
Common Failure Reasons
| Result Object | Example error Message | Code |
|---|---|---|
idvResult | No face detected in the provided document image | 2001 |
idvResult | Unable to process the document image | 2002 |
idvResult | The provided document image is invalid or corrupted | 2003 |
idvResult | Required document file is missing from the request | 2403 |
idvResult | The provided document type is not supported | 2404 |
idvResult | Selfie image is required | 2405 |
idvResult | No face detected in the selfie image | 2101 |
idvResult | Unable to process the selfie image | 2102 |
idvResult | The provided selfie image is invalid or corrupted | 2103 |
idvResult | The selfie does not appear to be a live person | 2104 |
idvResult | Unable to complete liveness check | 2105 |
idvResult | The selfie does not match the photo in the document | 2201 |
idvResult | Face similarity score is below acceptable threshold | 2202 |
idvResult | Multiple faces detected in the image | 2203 |
idvResult | The selfie does not match the photo on the submitted identity document | 2204 |
idvResult | User ID already exists for this merchant | 2306 |
idvResult | User already registered with this merchant | 2307 |
documentResult | Failed to extract Machine Readable Zone from passport | 2501 |
documentResult | No Machine Readable Zone was detected on the passport | 2511 |
documentResult | Failed to verify the barcode data on the driver's license | 2507 |
documentResult | No barcode was detected on the driver's license | 2512 |
documentResult | Driver's license front and back images do not match | 2505 |
documentResult | ID card front and back images do not match | 2506 |
documentResult | The issuing country detected on the document does not match the declared document country | 2508 |
documentResult | The provided document has expired | 2509 |
ocrResult | MRZ data does not match the submitted user information | 2502 |
ocrResult | OCR extracted data does not match the submitted metadata | 2503 |
ocrResult | One or more fields from OCR do not match metadata | 2504 |
minimumAgeResult | Applicant does not meet the age requirements | 2701 |
minimumAgeResult | Date of birth could not be extracted from the document | 2703 |
kycResults | Applicant has been flagged by a sanctions screening check | 2702 |
Error Responses
These errors return non-200 status codes — either the request was rejected before processing (validation, auth, jurisdiction), or a downstream service was unreachable during processing.
400 — Empty Field
{
"success": false,
"message": "Validation failed",
"errors": ["state cannot be empty"]
}
400 — Validation Error
{
"success": false,
"message": "Validation failed",
"errors": ["firstName - First name is required"]
}
400 — flowId Missing
{
"success": false,
"message": "Validation failed",
"errors": ["flowId - flowId is required"]
}
404 — Flow Not Found
Returned when flowId doesn't exist or doesn't belong to your merchant account.
{
"success": false,
"message": "Flow not found"
}
404 — Group Not Found
Returned when groupingId doesn't exist or doesn't belong to your merchant account.
{
"success": false,
"message": "Group not found"
}
400 — Flow Not In Group
Returned when flowId is not a member of the group specified by groupingId.
{
"success": false,
"message": "Flow does not belong to the specified group"
}
400 — Missing Required Fields (Flow Config)
Returned when the submitted flowId is configured to require specific fields, document types, or files and the request is missing one or more of them. The errors list reflects whichever of the flow's requirements weren't met — for example, a flow requiring DOB, either an ID card or passport, and a selfie:
{
"success": false,
"message": "Required fields missing",
"errors": [
"Date of Birth is required",
"Document Type is required: ID Card, Passport",
"ID Card Front is required",
"Passport Front is required",
"Selfie is required"
]
}
Or, for a flow that only requires a document (driver's license or passport), the document's country, and a selfie:
{
"success": false,
"message": "Required fields missing",
"errors": [
"At least one document type is required: Drivers License, Passport",
"Document Country is required",
"Selfie is required"
]
}
400 — File Validation Error
{
"success": false,
"message": "File validation failed",
"errors": ["selfie file is empty"]
}
400 — Missing SSN
Returned when the flow's configuration requires SSN (globally or for the submitted country) and it wasn't provided.
{
"success": false,
"message": "Validation failed",
"errors": ["Social Security Number is required"]
}
401 — Missing or Invalid Authentication
Returned when the Authorization header is missing or malformed (neither valid Basic nor Bearer credentials).
{
"error": "Unauthorized: Missing or invalid authentication header"
}
401 — Invalid Credentials
Returned when the merchant key or secret doesn't match an account.
{
"message": "Invalid Merchant or Secret Key"
}
403 — Restricted Jurisdiction
{
"success": false,
"message": "Restricted jurisdiction",
"errors": ["Country of residence 'RUS' is a restricted jurisdiction"]
}
The following countries are always restricted and will return a 403 error:
Belarus (BLR), Burundi (BDI), Central African Republic (CAF), Cuba (CUB), Iran (IRN), Libya (LBY), Myanmar (MMR), North Korea (PRK), Russia (RUS), Somalia (SOM), South Sudan (SSD), Sudan (SDN), Syria (SYR), Ukraine (UKR), Venezuela (VEN), Zimbabwe (ZWE)
A flow can additionally mark specific countries as restricted via country overrides in the portal — in that case the error message ends with "...is a restricted jurisdiction for this flow" instead. See Flows & Groups.
country and nationality are exempted from this check when the applicant submits a RESIDENCY_CARD issued by a non-restricted country — see Residency Permit.
501 — Unsupported Document Type
Returned when a requested document type isn't supported yet.
{
"success": false,
"message": "ID_CARD verification is coming soon for this document/country combination",
"errors": ["..."]
}
429 — Rate Limit Exceeded
{
"success": false,
"message": "You have exceeded your requests per minute limit."
}
Environment Variables
When using the Postman collection, configure these variables:
| Variable | Description | Example Value |
|---|---|---|
baseUrl | API base URL | https://api2.cycurid.com |
merchantApiKey | Your merchant API key | Provided in portal |
merchantSecret | Your merchant secret | Provided in portal |
Document Type Reference
| Document Type | documentTypes value | Front Field | Back Field | Back Required |
|---|---|---|---|---|
| Passport | PASSPORT | passportFront | N/A | Never |
| Driver's License | DRIVERS | driversFront | driversBack | As configured on the flow, else by country/type — see GET /v2/idv/document-requirements |
| ID Card (see below) | ID_CARD | idCardFront | idCardBack | As configured on the flow, else by country/type — see GET /v2/idv/document-requirements |
| Residency Permit (see above) | RESIDENCY_CARD | residencyFront | residencyBack | Yes, for every currently supported issuing country |
Supported ID Card Types
ID_CARD covers more than a standard national ID — it's an umbrella for any non-passport, non-license government-issued photo ID, submitted via the same idCardFront / idCardBack fields. Accepted types vary by country and are configured per flow in the portal (see Flows & Groups).
| Category | What it is | Examples |
|---|---|---|
| National ID Card | The primary civil identity card issued to residents/citizens for everyday use | Cédula de Identidad (Latin America), Carte Nationale d'Identité (France, Francophone Africa), Personalausweis (Germany/Austria), Aadhaar (India), RG (Brazil) |
| Refugee / Asylum ID | Identity document issued to refugees or asylum seekers | Refugee ID, Asylum Seeker Permit, Doklad o azylu (Czechia), Flüchtlingsausweis (Austria) |
| Military / Police / Defense ID | ID issued to active military, police, or defense personnel | Army/Military ID, Truppenausweis (Germany), Military Police Card, Gendarmerie ID |
| Diplomatic ID | ID issued to diplomatic personnel | Diplomatenausweis (Austria), Carte d'identité diplomatique (Belgium), Diplomatic ID Card |
| Voter ID / Electoral Card | Card issued for voter registration or identification | Elector Identification Card, Cartão de Eleitor (Brazil), Voter Card |
| Consular ID | ID issued by a country's consulate to its citizens living abroad | Matrícula Consular (Mexico/Latin America), Identificación Consular |
| Learner's / Provisional / Temporary ID | Interim or restricted-validity identity documents | Temporary NID (Bangladesh), Provisional ID, Learner's Permit (issued as photo ID in some countries) |
| Social Security / Health / Disability ID | Government-issued welfare, health, or disability identification | Social Security Card, National Health Insurance ID, Persons with Disabilities ID |
| Work / Employment Permit | Card proving authorization to work, used as photo ID | Employment Pass (Singapore), Work Permit |
| Firearms License | Firearms ownership/carry license, accepted as government photo ID | Firearms License (Australia, Canada, Austria) |
| Professional / Occupational ID | ID issued by a professional licensing or regulatory body | Bar Association Card, Federal Council of Medicine Card (Brazil), Professional ID |
| Tax ID | Government tax identification card | Tax ID Card |
| Senior Citizen ID | ID issued to elderly citizens, sometimes tied to benefits eligibility | Senior Citizen ID (Philippines), Seniors ID Card (Bermuda) |
When designing your flow, don't assume ID_CARD maps to one document per country — many countries have multiple valid ID_CARD-category documents in circulation at once (e.g. a national ID card and a voter card may both be acceptable for the same country). Use the flow's country overrides in the portal to control exactly which document types you'll accept per country, rather than relying on a single global default. Note that residence/residency permits are handled separately from ID_CARD — see Residency Permit.