KYC en México: Cómo Validar INE y CURP Automáticamente con una API

Guía completa de KYC en México con API. Extrae datos de INE, valida CURP, y verifica identidad automáticamente. Ejemplos en Python y Node.js para fintech, neobancos y plataformas gig.

March 17, 2026 · 6 min read

En México, todo producto financiero — desde una cuenta de nómina hasta un crédito personal — requiere verificar la identidad del cliente. La regulación de la CNBV, CONDUSEF y las disposiciones antilavado exigen validar que la persona es quien dice ser. Esta guía explica cómo implementar un flujo de KYC completo con API, sin capturas manuales.

¿Qué documentos se requieren para KYC en México?

Tipo de clienteDocumentos requeridosRegulación aplicable
Persona física (bajo riesgo)INE vigenteCNBV Disposiciones Antilavado Art. 12
Persona física (medio riesgo)INE + CURP + comprobante domicilioCNBV Disposiciones Antilavado Art. 13
Persona moralINE del rep. legal + CSF + acta constitutivaCNBV Disposiciones Antilavado Art. 14
Plataforma gig (conductor, repartidor)INE + comprobante domicilioSTPS / IMSS verificación opcional

Arquitectura del flujo de KYC con Ocilar

# Flujo típico de onboarding KYC en México
# 1. Usuario sube foto de INE (frente y reverso)
# 2. API extrae datos automáticamente
# 3. Se cruzan datos con CURP (opcional)
# 4. Se verifica vigencia del INE
# 5. Se registra el resultado con timestamp para auditoría

Paso 1 — Extraer datos de la INE

Python

from ocilar import OcilarClient
from datetime import datetime

client = OcilarClient(api_key="sk-your_key")

def extraer_ine(frente_path: str, reverso_path: str) -> dict:
    """Extrae todos los datos de una INE."""
    resultado = client.extract_ine(
        front=frente_path,
        back=reverso_path
    )

    # Validar vigencia
    anio_actual = datetime.now().year
    ine_vigente = int(resultado.vigencia) >= anio_actual

    return {
        "nombre": resultado.nombre,
        "curp": resultado.curp,
        "clave_elector": resultado.clave_elector,
        "fecha_nacimiento": resultado.fecha_nacimiento,
        "sexo": resultado.sexo,
        "estado": resultado.estado,
        "municipio": resultado.municipio,
        "domicilio": resultado.domicilio,
        "seccion": resultado.seccion,
        "vigencia": resultado.vigencia,
        "vigente": ine_vigente,
        "anio_registro": resultado.anio_registro,
    }

# Uso
datos_ine = extraer_ine("ine_frente.jpg", "ine_reverso.jpg")
print(datos_ine)

Node.js

import { OcilarClient } from '@ocilar/sdk'
import { createReadStream } from 'fs'

const client = new OcilarClient({ apiKey: 'sk-your_key' })

async function extraerIne(frentePath, reversPath) {
  const result = await client.extractIne({
    front: createReadStream(frentePath),
    back: createReadStream(reversPath)
  })

  const anioActual = new Date().getFullYear()
  const vigente = parseInt(result.vigencia) >= anioActual

  return {
    nombre: result.nombre,
    curp: result.curp,
    claveElector: result.claveElector,
    fechaNacimiento: result.fechaNacimiento,
    sexo: result.sexo,
    estado: result.estado,
    domicilio: result.domicilio,
    vigencia: result.vigencia,
    vigente
  }
}

Paso 2 — Validar la CURP

La CURP extraída de la INE puede validarse cruzándola con el documento de CURP emitido por RENAPO:

from ocilar import OcilarClient

client = OcilarClient(api_key="sk-your_key")

def validar_curp_con_documento(curp_extraida: str, curp_pdf_path: str) -> bool:
    """
    Cruza la CURP de la INE con el documento de CURP oficial de RENAPO.
    Retorna True si coinciden.
    """
    resultado_curp = client.extract_curp(file_path=curp_pdf_path)

    coinciden = curp_extraida.upper() == resultado_curp.curp.upper()
    if not coinciden:
        print(f"Discrepancia: INE dice {curp_extraida}, CURP doc dice {resultado_curp.curp}")

    return coinciden

Paso 3 — Flujo completo de onboarding

Persona física (nivel bajo de riesgo)

from ocilar import OcilarClient
from datetime import datetime
import uuid

client = OcilarClient(api_key="sk-your_key")

def onboarding_persona_fisica(
    ine_frente: str,
    ine_reverso: str,
    curp_pdf: str = None
) -> dict:
    """
    Flujo completo de KYC para persona física.
    Retorna datos verificados listos para guardar en tu base de datos.
    """
    # 1. Extraer INE
    ine = client.extract_ine(front=ine_frente, back=ine_reverso)

    # 2. Validar vigencia
    if int(ine.vigencia) < datetime.now().year:
        return {
            "aprobado": False,
            "razon_rechazo": f"INE vencida en {ine.vigencia}",
            "timestamp": datetime.now().isoformat()
        }

    # 3. Validar CURP si se proporcionó
    curp_validada = True
    if curp_pdf:
        curp_doc = client.extract_curp(file_path=curp_pdf)
        curp_validada = ine.curp.upper() == curp_doc.curp.upper()

    if not curp_validada:
        return {
            "aprobado": False,
            "razon_rechazo": "La CURP del documento no coincide con la INE",
            "timestamp": datetime.now().isoformat()
        }

    # 4. Retornar datos verificados
    return {
        "aprobado": True,
        "id_verificacion": str(uuid.uuid4()),
        "timestamp": datetime.now().isoformat(),
        "datos": {
            "nombre_completo": ine.nombre,
            "curp": ine.curp,
            "clave_elector": ine.clave_elector,
            "fecha_nacimiento": ine.fecha_nacimiento,
            "sexo": ine.sexo,
            "estado_registro": ine.estado,
            "municipio_registro": ine.municipio,
            "domicilio": ine.domicilio,
            "ine_vigencia": ine.vigencia,
        }
    }

# Uso en tu endpoint de onboarding
resultado = onboarding_persona_fisica(
    ine_frente="uploads/ine_frente.jpg",
    ine_reverso="uploads/ine_reverso.jpg",
    curp_pdf="uploads/curp.pdf"  # opcional
)

if resultado["aprobado"]:
    print("Cliente verificado:", resultado["datos"]["nombre_completo"])
else:
    print("Rechazo:", resultado["razon_rechazo"])

Persona moral (con rep. legal)

def onboarding_persona_moral(
    ine_rep_frente: str,
    ine_rep_reverso: str,
    csf_path: str
) -> dict:
    """KYC para persona moral: extrae INE del representante legal + CSF de la empresa."""

    rep_legal = client.extract_ine(front=ine_rep_frente, back=ine_rep_reverso)

    # Validar vigencia del representante
    if int(rep_legal.vigencia) < datetime.now().year:
        return {"aprobado": False, "razon_rechazo": "INE del representante vencida"}

    empresa = client.extract_csf(file_path=csf_path)

    return {
        "aprobado": True,
        "id_verificacion": str(uuid.uuid4()),
        "timestamp": datetime.now().isoformat(),
        "representante_legal": {
            "nombre": rep_legal.nombre,
            "curp": rep_legal.curp,
            "ine_vigencia": rep_legal.vigencia,
        },
        "empresa": {
            "rfc": empresa.rfc,
            "razon_social": empresa.nombre,
            "regimen_fiscal": empresa.regimen_fiscal,
            "domicilio_fiscal": empresa.domicilio_fiscal,
            "codigo_postal": empresa.codigo_postal,
            "actividades": empresa.actividades,
        }
    }

Integración con FastAPI (ejemplo backend)

from fastapi import FastAPI, UploadFile, File
from ocilar import OcilarClient
import tempfile
import os

app = FastAPI()
client = OcilarClient(api_key="sk-your_key")

@app.post("/kyc/individual")
async def kyc_individual(
    ine_frente: UploadFile = File(...),
    ine_reverso: UploadFile = File(...),
):
    # Guardar archivos temporales
    with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as f_frente:
        f_frente.write(await ine_frente.read())
        frente_path = f_frente.name

    with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as f_reverso:
        f_reverso.write(await ine_reverso.read())
        reverso_path = f_reverso.name

    try:
        resultado = client.extract_ine(front=frente_path, back=reverso_path)

        return {
            "nombre": resultado.nombre,
            "curp": resultado.curp,
            "vigente": int(resultado.vigencia) >= 2026,
            "vigencia": resultado.vigencia,
        }
    finally:
        os.unlink(frente_path)
        os.unlink(reverso_path)

Casos de uso por industria

Fintech y neobancos

La regulación CNBV exige identificación de clientes para apertura de cuentas. Con Ocilar, el flujo completo — subida de INE, extracción, validación — tarda menos de 3 segundos, contra 2–5 minutos de revisión manual.

Plataformas de crédito (BNPL, préstamos)

Para scoring crediticio necesitas nombre, fecha de nacimiento, domicilio y CURP. Extrae todos estos campos automáticamente del INE en el primer paso del formulario de solicitud.

Plataformas gig economy

Verificar la identidad de conductores, repartidores y contratistas independientes antes de activarlos. Combina extracción de INE con verificación de semanas cotizadas en IMSS para un perfil más completo.

HR y nómina

Onboarding de empleados sin captura manual. Los datos de INE se alimentan directamente a IMSS, INFONAVIT y el sistema de nómina.

Arrendamiento y bienes raíces

Verificación de inquilinos o compradores automáticamente. El domicilio en la INE sirve como referencia de dirección registrada.

Precios

Extraer datos de una INE (frente + reverso) cuesta $0.05–$0.10 por documento. El plan gratuito incluye 100 documentos al mes.

Preguntas frecuentes

¿La API valida la autenticidad del INE contra el RENAPO?

Ocilar extrae los datos del documento físico por OCR. La validación de autenticidad contra el padrón del RENAPO es un servicio separado. Contáctanos si necesitas esa integración.

¿Los datos se almacenan después del procesamiento?

No. Las imágenes se eliminan dentro de los 60 segundos posteriores al procesamiento. Consulta nuestra política de privacidad.

¿Cumple con la Ley Federal de Protección de Datos Personales?

Ocilar procesa los datos como encargado (procesador). El responsable del tratamiento es tu empresa. Consulta con tu área legal para asegurar que tu aviso de privacidad cubre el procesamiento de imágenes de identificación por terceros.

¿Funciona con fotos tomadas con celular?

Sí. Las fotos móviles funcionan bien cuando la INE ocupa la mayor parte del encuadre, está bien iluminada y no tiene reflejos ni sombras sobre el texto.

Try Ocilar free

1,000 free solves. No credit card required.

Get API Key