Document Verification
curl --request POST \
--url https://api.example.com/kyc/documents/{type}import requests
url = "https://api.example.com/kyc/documents/{type}"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/kyc/documents/{type}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/kyc/documents/{type}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/kyc/documents/{type}"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/kyc/documents/{type}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/kyc/documents/{type}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyCompliance Screening
Document Verification
POST
/
kyc
/
documents
/
{type}
Document Verification
curl --request POST \
--url https://api.example.com/kyc/documents/{type}import requests
url = "https://api.example.com/kyc/documents/{type}"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.example.com/kyc/documents/{type}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/kyc/documents/{type}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/kyc/documents/{type}"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/kyc/documents/{type}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/kyc/documents/{type}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyExtract and verify data from identity documents, proof of address, company charters, tax certificates, and bank ownership documents using AI-powered document processing.
Overview
The document verification endpoints use AI (LLM) to extract structured data from uploaded document images. Supported document types:- ID Documents: Passports, national IDs, driver’s licenses
- Proof of Address: Utility bills, bank statements, government letters
- Company Charters: Corporate registration documents
- Tax Certificates: Tax registrations, RUC/RFC/VAT/TIN certificates, and equivalent documents
- Bank Ownership: Bank account ownership verification documents
Endpoints
| Document Type | Endpoint | Description |
|---|---|---|
| ID Document | POST /kyc/documents/id | Extract data from ID documents |
| Proof of Address | POST /kyc/documents/proof-of-address | Extract data from address proof documents |
| Company Charter | POST /kyc/documents/company-charter | Extract data from company charter documents |
| Tax Certificate | POST /kyc/documents/tax-certificate | Extract tax registration and taxpayer data |
| Bank Ownership | POST /kyc/documents/bank-ownership | Extract data from bank ownership documents |
Authentication
Requireskyc:create permission. Include your Bearer token in the Authorization header.
Request Body Parameters
Supported Image Formats
- JPEG (
image/jpeg) - PNG (
image/png) - PDF (
application/pdf)
Zero Data Retention (ephemeral mode)
You can request ephemeral processing with either theX-Data-Retention: ephemeral header or "data_retention": "ephemeral" in the JSON body. Per-request ephemeral must be enabled for your account first: the endpoint identifier documents.extract (or the wildcard *) has to appear in your tenant’s data_processing_config.allowed_ephemeral_endpoints, otherwise the request is rejected with a 400. Tenants can also opt in account-wide by setting data_processing_config.mode to ephemeral — the tenant-level setting always wins over per-request flags and does not require the allowlist. Contact support to enable either option on your account.
When ephemeral mode is active, the API returns the normal extraction response but never emits the detailed audit event (which otherwise archives the submitted document images and every extracted field). The platform retains only an aggregate billing metric (one counted document verification, with no document content or extracted-field references); tenants with retain_billing_metrics: false suppress that too.
Restrictions in ephemeral mode:
- Images must be submitted inline as base64. S3-referenced documents are rejected with
400 ZDR_UNSUPPORTED_IMAGE_FORMAT, because a document already stored in a durable bucket cannot honor zero retention.
Request Example
ID Document
curl -X POST https://stg.kyc.legaltalent.ai/kyc/documents/id \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"images": [
{
"data": "base64_encoded_image_data...",
"mime_type": "image/jpeg"
}
]
}'
Proof of Address
curl -X POST https://stg.kyc.legaltalent.ai/kyc/documents/proof-of-address \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"images": [
{
"data": "base64_encoded_image_data...",
"mime_type": "application/pdf"
}
]
}'
Tax Certificate
curl -X POST https://stg.kyc.legaltalent.ai/kyc/documents/tax-certificate \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"images": [
{
"data": "base64_encoded_image_data...",
"mime_type": "application/pdf"
}
]
}'
Response Format
Success Response - ID Document
{
"status": "success",
"verification_id": "550e8400-e29b-41d4-a716-446655440000",
"document_type": "id_document",
"extracted_data": {
"full_name": "John Doe",
"document_number": "AB1234567",
"document_type": "Passport",
"nationality": "US",
"date_of_birth": "1985-03-15",
"expiry_date": "2030-03-15",
"issue_date": "2020-03-15",
"issuing_authority": "United States Department of State"
},
"confidence_scores": {
"overall": 0.95,
"full_name": 0.98,
"document_number": 0.99,
"date_of_birth": 0.97
},
"processing_time_ms": 3200,
"timestamp": "2024-11-22T10:30:00Z"
}
Success Response - Proof of Address
{
"status": "success",
"verification_id": "660e8400-e29b-41d4-a716-446655440001",
"document_type": "proof_of_address",
"extracted_data": {
"address": "123 Main Street, Apt 4B",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US",
"document_date": "2024-10-15",
"utility_type": "Electric Bill",
"account_holder_name": "John Doe"
},
"confidence_scores": {
"overall": 0.92,
"address": 0.95,
"document_date": 0.90
},
"processing_time_ms": 2800,
"timestamp": "2024-11-22T10:30:00Z"
}
Success Response - Company Charter
{
"status": "success",
"verification_id": "770e8400-e29b-41d4-a716-446655440002",
"document_type": "company_charter",
"extracted_data": {
"company_name": "Example Corp",
"registration_number": "12345678",
"incorporation_date": "2020-01-15",
"registered_address": "456 Business Ave, Suite 100",
"city": "San Francisco",
"state": "CA",
"country": "US",
"legal_form": "Corporation",
"authorized_capital": "1000000",
"directors": [
{
"name": "John Doe",
"role": "CEO"
}
]
},
"confidence_scores": {
"overall": 0.88,
"company_name": 0.95,
"registration_number": 0.92
},
"processing_time_ms": 4500,
"timestamp": "2024-11-22T10:30:00Z"
}
Success Response - Bank Ownership
{
"status": "success",
"verification_id": "880e8400-e29b-41d4-a716-446655440003",
"document_type": "bank_ownership",
"extracted_data": {
"account_holder_name": "John Doe",
"account_number": "****1234",
"bank_name": "Example Bank",
"account_type": "Checking",
"routing_number": "123456789",
"statement_date": "2024-10-31",
"address": "123 Main Street, New York, NY 10001"
},
"confidence_scores": {
"overall": 0.90,
"account_holder_name": 0.94,
"bank_name": 0.92
},
"processing_time_ms": 3500,
"timestamp": "2024-11-22T10:30:00Z"
}
Success Response - Tax Certificate
The following example represents a Uruguayan DGI certificate. Values vary by country and by the content printed on the document.{
"verification_id": "990e8400-e29b-41d4-a716-446655440004",
"document_type": "tax_certificate",
"status": "extracted",
"extracted_fields": {
"is_valid_document": true,
"detected_document_type": "tax_certificate",
"validation_reason": "Valid DGI tax certificate with RUT and taxpayer information",
"tax_id": "219999990012",
"legal_name": "EMPRESA EJEMPLO S.A.",
"issue_date": "2026-07-01",
"jurisdiction": "Dirección General Impositiva",
"document_country": "UY",
"tax_status": "ACTIVO",
"address": "AV. EJEMPLO 1234, MONTEVIDEO",
"legal_form": "SOCIEDAD ANÓNIMA",
"taxpayer_segment": "NO CEDE",
"economic_activities": [
{
"code": "620100",
"description": "Actividades de programación informática",
"is_primary": true
}
],
"tax_obligations": [
{
"name": "IVA",
"start_date": "2020-01"
}
],
"image_quality": "good"
},
"validations": {
"document_consistency": "consistent"
},
"confidence_scores": {
"overall": 0.9,
"llm_extraction": 0.9
},
"issues": [],
"error_message": null,
"processing_time_ms": 3100,
"created_at": "2026-07-30T14:30:00+00:00"
}
Response Fields
| Field | Type | Description |
|---|---|---|
status | string | Extraction status: extracted or error |
verification_id | string | Unique identifier for this verification |
document_type | string | Type of document processed |
extracted_fields | object | Extracted structured data (varies by document type); fields with null values are omitted |
validations | object | Technical validation results applicable to the document type |
confidence_scores | object | Confidence scores for extracted fields |
issues | array | Extraction warnings or issues; empty when none were reported |
error_message | string or null | Generic extraction error when status is error |
processing_time_ms | integer | Total processing time in milliseconds |
created_at | string | ISO 8601 timestamp of the extraction |
Extracted Data Fields
ID Document Fields
| Field | Type | Description |
|---|---|---|
full_name | string | Full name from document |
document_number | string | Document ID number |
document_type | string | Type of ID (Passport, Driver’s License, etc.) |
nationality | string | Nationality/country code |
date_of_birth | string | Date of birth (YYYY-MM-DD) |
expiry_date | string | Expiry date (YYYY-MM-DD) |
issue_date | string | Issue date (YYYY-MM-DD) |
issuing_authority | string | Authority that issued the document |
issuing_country | string | Country that issued the document (ISO 3166-1 alpha-2) |
address | string | Domicile address, when the document prints one (e.g. the reverse of the Argentine DNI). Empty for documents that do not carry an address. |
ID documents used as proof of address. Some identity documents print the holder’s domicile (notably the reverse of the Argentine DNI), which is accepted in lieu of a separate proof of address. When such a document is captured with its back/reverse side, the
address field is populated and the document’s issuing country is used as the country of residence in the curated extracted block of session webhooks. See residence_country resolution.Proof of Address Fields
| Field | Type | Description |
|---|---|---|
address | string | Full address |
city | string | City name |
state | string | State/province |
postal_code | string | Postal/ZIP code |
country | string | Country code |
document_date | string | Date on document (YYYY-MM-DD) |
utility_type | string | Type of utility (if applicable) |
account_holder_name | string | Name on the account |
Company Charter Fields
| Field | Type | Description |
|---|---|---|
company_name | string | Registered company name |
registration_number | string | Company registration number |
incorporation_date | string | Date of incorporation (YYYY-MM-DD) |
registered_address | string | Registered business address |
city | string | City |
state | string | State/province |
country | string | Country code |
legal_form | string | Legal form (Corporation, LLC, etc.) |
authorized_capital | string | Authorized capital amount |
directors | array | Array of director objects |
Tax Certificate Fields
All extraction fields are optional. Fields for which no value was found are omitted from the response.| Field | Type | Description | Example |
|---|---|---|---|
is_valid_document | boolean | Whether the image was classified as a tax certificate or tax registration document. It does not prove authenticity or confirm that the taxpayer registration is currently valid. | true |
detected_document_type | string | Document type detected in the uploaded image. | tax_certificate, tax_registration |
validation_reason | string | Human-readable reason for the document-type classification. | Valid DGI tax certificate with RUT |
tax_id | string | Tax identifier as extracted, including formatting printed on the document. | 219999990012, 30-71944642-2 |
legal_name | string | Registered legal name shown on the document. | EMPRESA EJEMPLO S.A. |
issue_date | string | Document issue date, normally returned as YYYY-MM-DD. This field does not establish an expiration date. | 2026-07-01 |
jurisdiction | string | Tax authority or jurisdiction shown or identified from the document. | Dirección General Impositiva |
document_country | string | Tax jurisdiction country normalized to ISO 3166-1 alpha-2. | UY |
tax_status | string | Free-text tax status extracted from the document. This field is not a normalized enum. Wording and language may vary. | ACTIVO, INACTIVO, SUSPENDIDO |
address | string | Registered tax address shown on the document. | AV. EJEMPLO 1234, MONTEVIDEO |
legal_form | string | Legal form as stated on the document. null or omitted for individuals or when not stated. | SOCIEDAD ANÓNIMA, SA, SRL, LLC |
taxpayer_segment | string | Taxpayer classification as printed by the tax authority. It is not a normalized enum and must not be confused with tax obligations. | CEDE, NO CEDE, GRANDES CONTRIBUYENTES |
economic_activities | array | Registered business activities. Each item can include code, description, and is_primary. | [{"code":"620100","description":"Programación informática","is_primary":true}] |
tax_obligations | array | Taxes, regimes, or obligations. Each item contains name and may include start_date as YYYY-MM-DD or YYYY-MM. | [{"name":"IVA","start_date":"2020-01"}] |
image_quality | string | Image quality assessment. | excellent, good, acceptable, poor |
is_valid_document: true only means that the uploaded image matches the expected document category. It does not validate the issuer, detect every possible forgery, query the tax authority, or establish that the taxpayer remains active. Use tax_status as extracted evidence and perform an authoritative registry check when current legal or tax status is required.tax_status, legal_form, and taxpayer_segment currently preserve free-text document values rather than returning canonical enums. Integrations should preserve unknown values and avoid rejecting responses solely because a new label appears.
There is no default validity period calculated from issue_date. A workflow can enforce its own age policy with max_days_since_issue.
Bank Ownership Fields
| Field | Type | Description |
|---|---|---|
account_holder_name | string | Name on the account |
account_number | string | Account number (may be masked) |
bank_name | string | Bank name |
account_type | string | Type of account (Checking, Savings, etc.) |
routing_number | string | Bank routing number |
statement_date | string | Statement date (YYYY-MM-DD) |
address | string | Address associated with account |
Error Responses
400 Bad Request - Invalid Image Format
{
"error": "Invalid image format",
"message": "Unsupported MIME type. Supported types: image/jpeg, image/png, application/pdf"
}
400 Bad Request - Missing Images
{
"error": "Missing required parameter: images"
}
400 Bad Request - Invalid Base64
{
"error": "Invalid base64 encoding",
"message": "Failed to decode image data"
}
500 Internal Server Error - Processing Failed
{
"error": "Error processing document",
"message": "Failed to extract data from document"
}
Status Codes
| Code | Description |
|---|---|
| 200 | Success - Document processed and data extracted |
| 400 | Bad Request - Invalid parameters or image format |
| 401 | Unauthorized - Missing or invalid token |
| 403 | Forbidden - Insufficient permissions |
| 500 | Internal Server Error |
Usage Examples
Python Example
import requests
import base64
token = "YOUR_TOKEN"
# Read image file and encode as base64
with open("id_document.jpg", "rb") as f:
image_data = base64.b64encode(f.read()).decode('utf-8')
response = requests.post(
"https://stg.kyc.legaltalent.ai/kyc/documents/id",
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
},
json={
"images": [
{
"data": image_data,
"mime_type": "image/jpeg"
}
]
}
)
if response.status_code == 200:
data = response.json()
print(f"Verification ID: {data['verification_id']}")
print(f"Extracted Name: {data['extracted_data']['full_name']}")
print(f"Confidence: {data['confidence_scores']['overall']}")
else:
print(f"Error: {response.json()}")
JavaScript Example
const token = "YOUR_TOKEN";
const fs = require('fs');
async function extractDocumentData(filePath, documentType) {
// Read file and encode as base64
const fileBuffer = fs.readFileSync(filePath);
const base64Data = fileBuffer.toString('base64');
// Determine MIME type from file extension
const mimeType = filePath.endsWith('.pdf')
? 'application/pdf'
: filePath.endsWith('.png')
? 'image/png'
: 'image/jpeg';
const endpoint = `https://stg.kyc.legaltalent.ai/kyc/documents/${documentType}`;
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
images: [
{
data: base64Data,
mime_type: mimeType
}
]
})
});
const data = await response.json();
if (response.ok) {
console.log(`Verification ID: ${data.verification_id}`);
console.log(`Extracted Data:`, data.extracted_data);
console.log(`Confidence: ${data.confidence_scores.overall}`);
return data;
} else {
console.error("Error:", data.error);
throw new Error(data.error);
}
}
Best Practices
- Image Quality: Use high-quality images (minimum 300 DPI) for best results
- File Size: Keep images under 10MB for optimal performance
- Multiple Pages: For multi-page documents (PDFs), include all pages in the images array
- Confidence Scores: Review confidence scores before using extracted data
- Manual Verification: Always verify extracted data manually for critical operations
- Error Handling: Implement proper error handling for processing failures
Performance
- Typical Response Time: 2-5 seconds per document
- Rate Limits: Subject to API rate limiting (1,000 requests per 5 minutes)
- File Size Limits: Maximum 10MB per image
- Concurrent Processing: Process multiple documents in separate requests
LLM Provider Options
AWS Bedrock (Default)
- Provider:
"bedrock"(default) - Model: Uses default Bedrock model
- Use Case: Production deployments on AWS
OpenAI
- Provider:
"openai" - Model: Specify model (e.g.,
"gpt-4o") - Use Case: High-precision extraction, specific model requirements
Integration Tips
- Pre-processing: Ensure images are properly oriented and well-lit
- Batch Processing: Process multiple documents in separate requests
- Caching: Consider caching verification IDs for reference
- Validation: Validate extracted data against business rules before use