Volver al blog

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.

Extraer Datos de Pasaporte
API pasaporteintegrar OCR pasaporteAPI OCRextraer datos de pasaporteJavaScriptPython

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.

HTTPcodeQué pasóQué hacer
401MISSING_API_KEYFalta el headerError de programación
401INVALID_API_KEYKey inválida o revocadaRevisar credenciales
402INSUFFICIENT_TOKENSSaldo agotadoRecargar; ver enroll_url
422LOW_IMAGE_QUALITYImagen ilegibleRepetir la captura
429RATE_LIMITEDDemasiadas peticionesEsperar Retry-After
429TOO_MANY_FAILED_EXTRACTIONSRacha de fallosRevisar la fuente de imágenes
500EXTRACTION_FAILEDFalló el motorReintentar 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

  1. 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.

  2. Guarda el extraction_id. Viene también en las respuestas de error y es la referencia para cualquier consulta de soporte.

  3. 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.

  4. 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