API Documentation
Introduction
The Extract Passport Data API lets you extract data from passports of any country programmatically. It uses artificial intelligence to process the images, extract every field on the data page, and validate the MRZ with the ICAO 9303 check digits.
Base URL: https://extractpassportdata.com/api/v1
For AI agents: /llms.txt is the service index, and /llms-full.txt carries the endpoints, fields, prices and FAQ in a single plain-text file.
Get your API key
Create your account right here, without leaving this page, and generate your API key in under a minute.
20 free tokens when you create your account: enough to test the API with your own documents before buying a first package.
Already have an account? Go to your API keys.
Authentication
Every API request must be authenticated with an API key. Include your API key in the X-API-Key header of each request.
curl https://extractpassportdata.com/api/v1/balance \ -H "X-API-Key: pass_your_api_key"
You can create and manage your API keys from the dashboard.
Upload Methods
The API supports several ways to send images. The method is detected automatically from the Content-Type header.
| Method | Content-Type | Recommended for |
|---|---|---|
| Recommended Multipart | multipart/form-data | curl, Postman, HTML forms |
| Base64 JSON | application/json | JavaScript, mobile apps |
| URL Fetch | application/json | Cloud-hosted images |
| Binario | image/jpeg, image/png, image/webp | CLI tools, streaming |
Endpoints
/api/v1/extractExtracts data from an image of the passport data page.
RecommendedMultipart Form-Data
Send files directly. Ideal for curl and Postman.
| Field | Type | Description |
|---|---|---|
image_front | file | Passport data page (required) |
image_back | file | Additional page (optional) |
curl -X POST https://extractpassportdata.com/api/v1/extract \ -H "X-API-Key: pass_your_api_key" \ -F "image_front=@./passport.jpg" \ -F "image_back=@./passport_extra.jpg"
Base64 JSON
Send base64-encoded images. The data-URL prefix is optional.
| Field | Type | Description |
|---|---|---|
image_front | string | Base64 of the front image (required) |
image_back | string | Base64 of the back image (optional) |
IMAGE_FRONT=$(base64 < ./passport.jpg | tr -d '\n')
printf '{"image_front": "%s"}' "$IMAGE_FRONT" | curl -X POST https://extractpassportdata.com/api/v1/extract \
-H "X-API-Key: pass_your_api_key" \
-H "Content-Type: application/json" \
--data-binary @-URL Fetch
Send URLs of hosted images. Only HTTPS URLs are allowed.
| Field | Type | Description |
|---|---|---|
image_front_url | string | HTTPS URL of the front image (required) |
image_back_url | string | HTTPS URL of the back image (optional) |
curl -X POST https://extractpassportdata.com/api/v1/extract \
-H "X-API-Key: pass_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"image_front_url": "https://storage.example.com/passport.jpg",
"image_back_url": "https://storage.example.com/passport_extra.jpg"
}'Binary Upload
Send the raw image as the request body. Supports one image per request.
curl -X POST https://extractpassportdata.com/api/v1/extract \ -H "X-API-Key: pass_your_api_key" \ -H "Content-Type: image/jpeg" \ --data-binary @./passport.jpg
Successful response
{
"success": true,
"extraction_id": "clx1234567890",
"data": {
"passportNumber": "G12345678",
"surname": "GOMEZ VELAZQUEZ",
"givenNames": "MARGARITA",
"nationality": "MEXICANA",
"dateOfBirth": "05/07/1980",
"sex": "F",
"placeOfBirth": "COAHUILA",
"dateOfIssue": "05/07/2023",
"dateOfExpiry": "05/07/2033",
"issuingCountry": "MEX",
"issuingAuthority": "SRE",
"personalNumber": "GOVM800705MCLRLR01",
"mrzLine1": "P<MEXGOMEZ<VELAZQUEZ<<MARGARITA<<<<<<<<<<<<<",
"mrzLine2": "G123456786MEX8007050F3307054<<<<<<<<<<<<<<08"
},
"tokens_remaining": 19,
"upload_method": "multipart"
}Send to a destination (optional)
Add an X-Destination-Id header to have the data delivered to one of your destinations before you are answered. It is optional and per request: without it the response is exactly what it has always been, even if you have a default destination configured.
curl -X POST https://extractpassportdata.com/api/v1/extract \ -H "X-API-Key: pass_your_api_key" \ -H "X-Destination-Id: <your destination id>" \ -F "image_front=@./passport.jpg"
The response reports how the hand-off went. It is attempted exactly once, with no queue and no retry: if it failed, the data is still in your response and what to do next is yours to decide.
{
"success": true,
"extraction_id": "...",
"data": { ... },
"delivery": {
"destination_id": "...",
"succeeded": true,
"http_status": 200,
"latency_ms": 312,
"error_code": null
}
}If the destination does not exist, is not yours, or is paused, the request is refused with 400 DESTINATION_NOT_FOUND before a token is spent.
/api/v1/balanceReturns your account’s current token balance.
Example
curl https://extractpassportdata.com/api/v1/balance \ -H "X-API-Key: pass_your_api_key"
Response
{
"success": true,
"balance": 20
}Capture links and webhooks
Send your customer a single-use link: they photograph their document on their phone and the data lands in your destination (webhook, email or Telegram), without the image passing through your system. Available to every account (beta). Destinations are set up in the dashboard, under Destinations.
/api/v1/capture-linkscurl -X POST https://extractpassportdata.com/api/v1/capture-links \
-H "X-API-Key: pass_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"destinationId": "<id de tu destino>",
"documentType": "passport",
"reference": "reserva-8841",
"requireBack": true
}'destinationIdanddocumentType(ine|passport) are required.reference(up to 80 characters) travels with the delivery.requireBackalso asks for the back.requesterName(up to 60) is the business name your customer sees.- Single use: 15 minutes to open it and, once opened, 15 minutes to capture.
- The url is only returned in this response; treat it as a credential and keep it out of logs.
- Creating the link is free. The capture spends 1 token, refunded if the extraction or the delivery fails.
- Up to 20 live links at once (400 TOO_MANY_LIVE_LINKS). A missing or paused destination: 400 DESTINATION_NOT_FOUND.
Response (201)
{
"success": true,
"id": "clx_enlace_456",
"url": "https://extractpassportdata.com/c/…",
"reference": "reserva-8841",
"documentType": "passport",
"requireBack": true,
"askGuestPersona": false,
"expiresAt": "2026-09-30T18:15:00.000Z"
}Webhooks: what your server receives
Every delivery to a webhook destination, whether from a capture link, from /api/v1/extract with X-Destination-Id or from the dashboard, is a signed POST. It is attempted once, follows no redirects and waits 10 seconds; answer 2xx.
POST /tu-webhook
Content-Type: application/json
X-Signature: sha256=5f2b…
X-Signature-Timestamp: 1790000000
X-Idempotency-Key: clx1234567890
{
"version": "1",
"event": "extraction.completed",
"idempotencyKey": "clx1234567890",
"documentType": "passport",
"source": "capture-link",
"captureLinkId": "clx_enlace_456",
"reference": "reserva-8841",
"extractedAt": "2026-09-30T18:05:02.000Z",
"data": { "curp": "…", "nombre": "…" }
}source is api, web or capture-link. image (front/back in base64) is only present if the destination has image delivery on. Use X-Idempotency-Key to drop duplicates.
Signature: X-Signature is sha256= plus the hex HMAC-SHA256, keyed with the destination secret (whsec_…), of X-Signature-Timestamp + "." + the raw body. Verify over the bytes received (not re-serialized JSON), compare in constant time and reject timestamps older than 5 minutes. The official SDKs do this for you.
import { createHmac, timingSafeEqual } from 'node:crypto'
function verify(secret, rawBody, signature, timestamp) {
if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false
const expected = 'sha256=' + createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex')
const a = Buffer.from(expected)
const b = Buffer.from(signature ?? '')
return a.length === b.length && timingSafeEqual(a, b)
}Error codes
| Code | HTTP | Description |
|---|---|---|
MISSING_API_KEY | 401 | No API key provided |
INVALID_API_KEY | 401 | Invalid or revoked API key |
MISSING_IMAGE | 400 | No front image provided |
INVALID_IMAGE_FORMAT | 400 | Unsupported format (use JPEG, PNG, or WebP) |
IMAGE_TOO_LARGE | 400 | Image exceeds 10 MB |
INVALID_BASE64 | 400 | Failed to decode base64 |
INVALID_MULTIPART | 400 | Failed to parse the multipart form |
URL_FETCH_FAILED | 400 | Could not fetch the image from the URL |
URL_INVALID | 400 | Invalid URL (must be HTTPS) |
URL_BLOCKED | 400 | URL blocked for security reasons |
URL_TIMEOUT | 400 | Timed out fetching the image (10s) |
UNSUPPORTED_CONTENT_TYPE | 415 | Unsupported Content-Type |
INSUFFICIENT_TOKENS | 402 | Insufficient token balance |
RATE_LIMITED | 429 | Too many failed or in-flight requests per minute; successful extractions do not count (see the Retry-After header) |
TOO_MANY_FAILED_EXTRACTIONS | 429 | Too many consecutive failed extractions; check your image quality |
EXTRACTION_FAILED | 500 | Extraction error (token refunded) |
INTERNAL_ERROR | 500 | Internal server error |
Extracted fields
The API extracts the following fields from the passport data page:
passportNumberPassport number
surnameSurname(s)
givenNamesGiven names
nationalityNationality
dateOfBirthDate of birth
sexSex
placeOfBirthPlace of birth
dateOfIssueDate of issue
dateOfExpiryDate of expiry
issuingCountryIssuing country (ICAO code)
issuingAuthorityIssuing authority
personalNumberPersonal number (if present)
mrzLine1MRZ line 1
mrzLine2MRZ line 2
Limits and quotas
- Each extraction consumes 1 token
- Maximum image size: 10MB
- Supported formats: JPEG, PNG, WebP
- URL-fetch timeout: 10 seconds
- If an extraction fails because of a system error, the token is refunded automatically
- Tokens never expire