Verify Endpoint

Complete API reference for the document verification endpoint.

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

HeaderValueRequired
AuthorizationBearer {api_key}Yes
Content-Typeapplication/jsonYes
X-Request-IDCustom request IDNo

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

ParameterTypeRequiredDescription
document_typestringYesType of document: "passport", "drivers_license", "national_id"
document_datastringYesBase64-encoded image data (JPEG, PNG, or PDF)
verification_typestringNo"age_only" (default) or "full_verification"
metadataobjectNoCustom key-value pairs for tracking
webhook_urlstringNoHTTPS URL for asynchronous results
test_modebooleanNoUse 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

FieldTypeDescription
request_idstringUnique request identifier
statusstring"verified", "rejected", or "error"
verified_agenumberAge extracted from document (if verified)
confidence_scorenumberAI confidence (0.0 to 1.0)
document_typestringDetected document type
processing_time_msnumberProcessing duration
metadataobjectYour custom metadata
extraction_dataobjectFull extracted data (full_verification only)

Asynchronous Response

FieldTypeDescription
request_idstringUnique request identifier
statusstringAlways "processing"
estimated_time_secondsnumberExpected completion time
webhook_urlstringYour 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

TypeDescriptionCountries
passportInternational passportAll countries
drivers_licenseDriver's licenseUS, EU, Canada, Australia
national_idNational identity cardEU, Asia, Latin America
residence_permitResidence permitEU countries
visaVisa documentAll 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_age
  • confidence_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 TypeAge OnlyFull 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?