Cómo Integrar una API de OCR de Pasaporte (Guía Completa con Código)
Integración paso a paso de una API de extracción de datos de pasaporte: los cuatro métodos de envío de imagen, el manejo de cada código de error y ejemplos listos en JavaScript, Python y PHP.
Integrar la lectura de pasaportes en tu aplicación es, en el caso simple, una petición HTTP. Lo que separa una integración que funciona en la demo de una que aguanta producción es el manejo de los casos que no son el camino feliz: la foto borrosa, el token agotado, el reintento.
Esta guía cubre ambas cosas.
La petición mínima
curl -X POST https://extraerdatosdepasaporte.com/api/v1/extract \
-H "X-API-Key: pas_tu_api_key" \
-F "image_front=@./pasaporte.jpg"
Tu API key se genera en el panel al crear una cuenta, que incluye 20 extracciones gratis sin tarjeta.
La respuesta:
{
"success": true,
"extraction_id": "clx7f2k...",
"data": {
"passportNumber": "G12345678",
"surname": "GOMEZ VELAZQUEZ",
"givenNames": "MARGARITA",
"nationality": "MEX",
"issuingCountry": "MEX",
"dateOfBirth": "05/07/1980",
"dateOfIssue": "10/03/2023",
"dateOfExpiry": "05/07/2033",
"sex": "F",
"placeOfBirth": "CIUDAD DE MEXICO",
"issuingAuthority": "SRE",
"personalNumber": "GOVM800705MDFMLR09",
"mrzLine1": "P<MEXGOMEZ<VELAZQUEZ<<MARGARITA<<<<<<<<<<<<<",
"mrzLine2": "G123456786MEX8007050F3307054<<<<<<<<<<<<<<08"
},
"tokens_remaining": 19,
"upload_method": "multipart"
}
Las dos líneas de la MRZ vienen completas y ya validadas contra sus dígitos verificadores. Si quieres revalidarlas por tu cuenta —una práctica sana cuando el dato alimenta un proceso regulado— el algoritmo está en Dígitos verificadores ICAO 9303.
Cuatro maneras de mandar la imagen
El endpoint detecta el método por el Content-Type, así que puedes usar el que mejor encaje en tu arquitectura sin cambiar de ruta.
1. Multipart — la opción natural desde un formulario o desde curl:
const form = new FormData()
form.append('image_front', fileBuffer, 'pasaporte.jpg')
const res = await fetch('https://extraerdatosdepasaporte.com/api/v1/extract', {
method: 'POST',
headers: { 'X-API-Key': process.env.PASSPORT_API_KEY },
body: form,
})
2. Base64 en JSON — cómodo cuando la imagen ya viaja dentro de un payload JSON:
await fetch(url, {
method: 'POST',
headers: {
'X-API-Key': key,
'Content-Type': 'application/json',
},
body: JSON.stringify({
image_front: `data:image/jpeg;base64,${buffer.toString('base64')}`,
}),
})
3. Por URL — si tus imágenes ya están en S3 o en un bucket público, no hace falta descargarlas para reenviarlas:
{ "image_front_url": "https://tu-bucket.s3.amazonaws.com/pasaporte.jpg" }
4. Binario directo — el Content-Type es el de la imagen y el cuerpo son los bytes:
curl -X POST https://extraerdatosdepasaporte.com/api/v1/extract \
-H "X-API-Key: pas_tu_api_key" \
-H "Content-Type: image/jpeg" \
--data-binary @pasaporte.jpg
Formatos admitidos: JPEG, PNG, WebP, HEIC y HEIF, hasta 10 MB. HEIC importa más de lo que parece: es lo que produce un iPhone por defecto, así que una app móvil que suba la foto tal cual funciona sin conversión previa.
Los errores, que es donde está el trabajo real
Cada error trae un campo code estable. Ramifica sobre code, nunca sobre el texto de error, que está en español y puede reescribirse.
| HTTP | code | Qué pasó | Qué hacer |
|---|---|---|---|
| 401 | MISSING_API_KEY | Falta el header | Error de programación |
| 401 | INVALID_API_KEY | Key inválida o revocada | Revisar credenciales |
| 402 | INSUFFICIENT_TOKENS | Saldo agotado | Recargar; ver enroll_url |
| 422 | LOW_IMAGE_QUALITY | Imagen ilegible | Repetir la captura |
| 429 | RATE_LIMITED | Demasiadas peticiones | Esperar Retry-After |
| 429 | TOO_MANY_FAILED_EXTRACTIONS | Racha de fallos | Revisar la fuente de imágenes |
| 500 | EXTRACTION_FAILED | Falló el motor | Reintentar con backoff |
Dos merecen comentario aparte.
LOW_IMAGE_QUALITY (422) no es un fallo tuyo
Es la respuesta cuando la imagen llegó bien pero no permite una lectura que verifique. Trae missing_fields con los campos que no se pudieron leer:
{
"success": false,
"code": "LOW_IMAGE_QUALITY",
"error": "La calidad de la imagen no permite una lectura confiable",
"missing_fields": ["passportNumber", "mrzLine2"],
"extraction_id": "clx7f2k..."
}
Preferimos este 422 a devolver datos dudosos, y el token se reembolsa: una imagen ilegible no te cuesta saldo. En tu interfaz, tradúcelo a una instrucción concreta para el usuario — "no se leyó la franja inferior, vuelve a tomar la foto incluyendo las dos líneas del pie" es infinitamente más útil que "error al procesar".
429 con Retry-After
Ambos 429 traen el header Retry-After en segundos. Respétalo:
async function extractConReintento(imagen, intentos = 3) {
for (let i = 0; i < intentos; i++) {
const res = await enviar(imagen)
if (res.ok) return res.json()
const { code } = await res.json()
// Reintentar no arregla una imagen ilegible ni una key inválida.
if (code === 'LOW_IMAGE_QUALITY' || code?.includes('API_KEY')) {
throw new Error(code)
}
if (res.status === 429) {
const espera = Number(res.headers.get('Retry-After') ?? 2 ** i)
await new Promise((r) => setTimeout(r, espera * 1000))
continue
}
if (res.status >= 500) {
await new Promise((r) => setTimeout(r, 2 ** i * 1000))
continue
}
throw new Error(code)
}
throw new Error('Agotados los reintentos')
}
La regla que evita la mayoría de los problemas: no reintentes lo que un reintento no puede arreglar. Una imagen borrosa seguirá borrosa en el cuarto intento; lo único que consigues es gastar cuota y activar el guardia de rachas de fallos.
Python
import requests
def extraer(ruta, api_key):
with open(ruta, 'rb') as f:
r = requests.post(
'https://extraerdatosdepasaporte.com/api/v1/extract',
headers={'X-API-Key': api_key},
files={'image_front': f},
timeout=30,
)
cuerpo = r.json()
if not cuerpo.get('success'):
raise RuntimeError(f"{cuerpo.get('code')}: {cuerpo.get('error')}")
return cuerpo['data']
Fija siempre un timeout. Sin él, una petición colgada bloquea el worker indefinidamente.
PHP
$ch = curl_init('https://extraerdatosdepasaporte.com/api/v1/extract');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . $apiKey],
CURLOPT_POSTFIELDS => [
'image_front' => new CURLFile($ruta, 'image/jpeg'),
],
CURLOPT_TIMEOUT => 30,
]);
$respuesta = json_decode(curl_exec($ch), true);
curl_close($ch);
if (empty($respuesta['success'])) {
throw new RuntimeException($respuesta['code'] ?? 'ERROR_DESCONOCIDO');
}
Cuatro cosas antes de pasar a producción
-
La API key va en el servidor. Nunca en el bundle del navegador ni en una app móvil: cualquiera puede extraerla y gastar tu saldo. Sube la imagen a tu backend y que él llame a la API.
-
Guarda el
extraction_id. Viene también en las respuestas de error y es la referencia para cualquier consulta de soporte. -
Vigila
tokens_remaining. Llega en cada respuesta exitosa: es la forma barata de alertar antes de quedarte sin saldo a mitad de una operación. -
Prueba con fotos malas a propósito. Con reflejo, torcidas, a media luz. El camino feliz siempre funciona; lo que decide la experiencia real de tus usuarios es cómo se comporta tu código en el 422.
Los patrones de captura que evitan la mayoría de esos 422 están en Errores comunes al escanear pasaportes.
Empieza
La documentación completa tiene la referencia de todos los campos, y la demo gratuita te deja probar con un pasaporte real sin registrarte. Al crear una cuenta recibes 20 extracciones para integrar contra datos de verdad; los precios empiezan después de eso.
¿Necesitas extraer datos de pasaportes automáticamente?
Prueba nuestra API con 20 extracciones gratis. Integración en minutos, resultados en segundos.
Comenzar gratis