POST /v1/verify
Verify a user's age using their government-issued ID document. This is the core endpoint for age verification.
Endpoint
POST https://api.loomapi.com/v1/verify
Authentication
Include your API key in the Authorization header:
Authorization: Bearer your_api_key_here
Request
Headers
| Header | Value | Required |
|---|---|---|
Authorization | Bearer {api_key} | Yes |
Content-Type | application/json | Yes |
X-Request-ID | Custom request ID | No |
Body Parameters
{
"document_type": "passport",
"document_data": "base64_encoded_image_data",
"verification_type": "age_only",
"metadata": {
"user_id": "user_123",
"session_id": "session_456"
},
"webhook_url": "https://your-app.com/webhooks/verification",
"test_mode": false
}
Parameter Details
| Parameter | Type | Required | Description |
|---|---|---|---|
document_type | string | Yes | Type of document: "passport", "drivers_license", "national_id" |
document_data | string | Yes | Base64-encoded image data (JPEG, PNG, or PDF) |
verification_type | string | No | "age_only" (default) or "full_verification" |
metadata | object | No | Custom key-value pairs for tracking |
webhook_url | string | No | HTTPS URL for asynchronous results |
test_mode | boolean | No | Use test data (doesn't consume quota) |
Response
Success Response (200)
{
"request_id": "req_1234567890abcdef",
"status": "verified",
"verified_age": 25,
"confidence_score": 0.98,
"document_type": "passport",
"processing_time_ms": 2340,
"metadata": {
"user_id": "user_123",
"session_id": "session_456"
}
}
Asynchronous Response (202)
When webhook_url is provided, you receive this immediate response:
{
"request_id": "req_1234567890abcdef",
"status": "processing",
"estimated_time_seconds": 30,
"webhook_url": "https://your-app.com/webhooks/verification"
}
Results are sent to your webhook URL when processing completes.
Response Fields
Synchronous Response
| Field | Type | Description |
|---|---|---|
request_id | string | Unique request identifier |
status | string | "verified", "rejected", or "error" |
verified_age | number | Age extracted from document (if verified) |
confidence_score | number | AI confidence (0.0 to 1.0) |
document_type | string | Detected document type |
processing_time_ms | number | Processing duration |
metadata | object | Your custom metadata |
extraction_data | object | Full extracted data (full_verification only) |
Asynchronous Response
| Field | Type | Description |
|---|---|---|
request_id | string | Unique request identifier |
status | string | Always "processing" |
estimated_time_seconds | number | Expected completion time |
webhook_url | string | Your configured webhook URL |
Error Responses
Invalid Document (400)
{
"request_id": "req_1234567890abcdef",
"error": {
"code": "DOCUMENT_INVALID_FORMAT",
"message": "Unsupported document format. Supported: jpeg, png, pdf"
}
}
Authentication Failed (401)
{
"request_id": "req_1234567890abcdef",
"error": {
"code": "AUTH_INVALID_KEY",
"message": "The provided API key is invalid or expired"
}
}
Rate Limit Exceeded (429)
{
"request_id": "req_1234567890abcdef",
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Try again in 30 seconds.",
"details": {
"retry_after_seconds": 30
}
}
}
Examples
Basic Verification
curl -X POST https://api.loomapi.com/v1/verify \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_type": "passport",
"document_data": "iVBORw0KGgoAAAANSUhEUgAA..."
}'
With Webhook
curl -X POST https://api.loomapi.com/v1/verify \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_type": "drivers_license",
"document_data": "iVBORw0KGgoAAAANSUhEUgAA...",
"webhook_url": "https://your-app.com/webhooks/verification",
"metadata": {
"user_id": "user_123",
"session_id": "session_abc"
}
}'
Full Verification
curl -X POST https://api.loomapi.com/v1/verify \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_type": "national_id",
"document_data": "iVBORw0KGgoAAAANSUhEUgAA...",
"verification_type": "full_verification"
}'
Test Mode
curl -X POST https://api.loomapi.com/v1/verify \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"document_type": "passport",
"document_data": "iVBORw0KGgoAAAANSUhEUgAA...",
"test_mode": true
}'
Document Types
Supported Types
| Type | Description | Countries |
|---|---|---|
passport | International passport | All countries |
drivers_license | Driver's license | US, EU, Canada, Australia |
national_id | National identity card | EU, Asia, Latin America |
residence_permit | Residence permit | EU countries |
visa | Visa document | All countries |
Document Requirements
- Format: JPEG, PNG, or PDF
- Size: Maximum 10MB
- Resolution: Minimum 300x300 pixels
- Quality: Clear, well-lit, no glare
- Orientation: Straight, no rotation
- Validity: Not expired
Verification Types
Age Only (age_only)
Extracts and verifies age from the document. Fastest processing, lowest cost.
Response includes:
verified_ageconfidence_score- Basic document validation
Full Verification (full_verification)
Complete document verification including identity validation.
Response includes:
- All age verification data
- Full identity extraction
- Advanced fraud detection
- Document authenticity checks
Rate Limits
- Standard: 100 requests per minute
- Professional: 1,000 requests per minute
- Enterprise: Custom limits
See Rate Limits for complete details.
Processing Times
| Document Type | Age Only | Full Verification |
|---|---|---|
| Passport | < 5 seconds | < 15 seconds |
| Driver's License | < 3 seconds | < 10 seconds |
| National ID | < 4 seconds | < 12 seconds |
Best Practices
Image Quality
// Client-side image validation
function validateImage(file) {
const maxSize = 10 * 1024 * 1024 // 10MB
const minResolution = 300
if (file.size > maxSize) {
throw new Error('File too large')
}
return new Promise((resolve) => {
const img = new Image()
img.onload = () => {
if (img.width < minResolution || img.height < minResolution) {
throw new Error('Image resolution too low')
}
resolve()
}
img.src = URL.createObjectURL(file)
})
}
Error Handling
async function verifyDocument(documentData) {
try {
const response = await fetch('/api/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
document_type: 'passport',
document_data: documentData
})
})
const result = await response.json()
if (!response.ok) {
switch (result.error.code) {
case 'RATE_LIMIT_EXCEEDED':
// Implement backoff
await delay(result.error.details.retry_after_seconds * 1000)
return verifyDocument(documentData)
case 'DOCUMENT_QUALITY_LOW':
throw new Error('Please provide a clearer image')
default:
throw new Error(result.error.message)
}
}
return result
} catch (error) {
console.error('Verification failed:', error)
throw error
}
}
Request IDs
Use custom request IDs for tracking:
const requestId = `verify_${userId}_${Date.now()}`
const response = await fetch('/api/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'X-Request-ID': requestId,
'Content-Type': 'application/json'
},
body: JSON.stringify({
document_type: 'passport',
document_data: documentData
})
})
Testing
Test Documents
Use these test document scenarios:
const testScenarios = {
success: {
document_type: 'passport',
expected_age: 25,
expected_status: 'verified'
},
underage: {
document_type: 'drivers_license',
expected_age: 17,
expected_status: 'rejected'
},
expired: {
document_type: 'national_id',
expected_status: 'rejected',
reason: 'document_expired'
},
poor_quality: {
expected_status: 'error',
code: 'DOCUMENT_QUALITY_LOW'
}
}
Integration Tests
describe('Document Verification', () => {
test('successful verification', async () => {
const result = await verifyDocument(testDocumentData)
expect(result.status).toBe('verified')
expect(result.verified_age).toBeGreaterThanOrEqual(18)
expect(result.confidence_score).toBeGreaterThan(0.8)
})
test('handles rate limits', async () => {
// Make many concurrent requests
const promises = Array(150).fill().map(() =>
verifyDocument(testDocumentData)
)
const results = await Promise.allSettled(promises)
const rateLimited = results.filter(r =>
r.status === 'rejected' &&
r.reason?.error?.code === 'RATE_LIMIT_EXCEEDED'
)
expect(rateLimited.length).toBeGreaterThan(0)
})
})
Support
Need help with the verify endpoint?
- Examples: Check our code examples
- Troubleshooting: See error handling
- Rate Limits: Review rate limit documentation
- Support: Contact support@loomapi.com