Documentación de la API

Valida constancias de situación fiscal (CSF), consulta el Listado 69-B del SAT y obtén datos fiscales ya normalizados y listos para mapear a los nodos de un CFDI 4.0 (RFC, razón social, código postal, régimen fiscal). Integración típica: menos de 15 minutos.

https://TU-DOMINIO.com

Autenticación

Todos los endpoints de producto (los que consumen crédito) requieren tu API key en el header Authorization, esquema Bearer. El catálogo público (/v1/catalogo/*) no requiere autenticación.

Authorization: Bearer vf_live_TU_API_KEY_AQUI
Tu API key es un secreto: trátala como una contraseña. Úsala solo desde tu backend, nunca la incluyas en código de frontend/cliente ni en apps móviles.

Créditos y saldo

Cada cliente tiene un solo saldo (pool) de créditos. Cada llamada exitosa a un endpoint de producto consume créditos de ese pool, distinto según el trabajo real que hace cada uno (10 para /validaciones-fiscales, 5 para /revalidaciones-fiscales, 1 para /lista-69b), lo verás reflejado en el campo creditos_consumidos de la respuesta. Si no tienes saldo, el endpoint responde 402 Payment Required sin intentar procesar nada.

Probar en vivo

Cada endpoint de esta página también se puede probar de verdad, con peticiones HTTP reales contra tu servidor: subir el PDF, mandar el RFC, ver la respuesta JSON tal cual la regresa la API, sin escribir ningún código ni salir del navegador. Pega tu API key una sola vez con el botón Authorize y ya queda lista para cualquier endpoint que pruebes después.

POST /v1/mx/validaciones-fiscales

Sube el PDF de una Constancia de Situación Fiscal (CSF) del SAT. La API extrae y valida en vivo contra el SAT: RFC, razón social, domicilio fiscal y régimen fiscal -- este último ya mapeado a la clave de 3 dígitos c_RegimenFiscal que un CFDI exige, no solo el texto libre que trae el PDF.

Request

CampoTipoDescripción
archivomultipart/form-dataEl PDF de la CSF. Máximo 8 MB.

Response 200

CampoTipoDescripción
identificacion_fiscalstringRFC extraído y validado contra el SAT.
razon_socialstring | null
codigo_postalstring | nullCódigo postal del domicilio fiscal.
estado_textostring | null
municipio_textostring | null
coloniastring | null
callestring | null
numero_exteriorstring | null
numero_interiorstring | null
codigo_regimen_textostring | nullTexto libre del régimen tal cual lo trae la CSF (ej. "Régimen Simplificado de Confianza"). Se conserva para auditoría.
regimen_fiscal_codigostring | nullLa clave de 3 dígitos que necesitas para un CFDI (ej. "626"). null si no se pudo mapear con confianza: en ese caso usa codigo_regimen_texto para resolverlo a mano.
regimen_fiscal_descripcionstring | nullDescripción oficial del catálogo SAT correspondiente a regimen_fiscal_codigo.
regimen_fiscal_coincidenciabooleanfalse si codigo_regimen_texto vino con datos pero no hubo match en el catálogo (el SAT cambió la redacción, o el régimen ya no está vigente).
token_validacionstring | nullÚsalo en /revalidaciones-fiscales para volver a consultar sin re-subir el PDF.
creditos_consumidosstring[]Claves de los créditos cobrados por esta llamada.
curl -X POST https://TU-DOMINIO.com/v1/mx/validaciones-fiscales \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -F "archivo=@/ruta/a/constancia.pdf"
import requests

with open("constancia.pdf", "rb") as archivo:
    respuesta = requests.post(
        "https://TU-DOMINIO.com/v1/mx/validaciones-fiscales",
        headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
        files={"archivo": archivo},
    )

datos = respuesta.json()
print(datos["regimen_fiscal_codigo"])  # ej. "626"
const form = new FormData();
form.append("archivo", archivoPdf); // File o Blob

const respuesta = await fetch("https://TU-DOMINIO.com/v1/mx/validaciones-fiscales", {
  method: "POST",
  headers: { Authorization: "Bearer vf_live_TU_API_KEY_AQUI" },
  body: form,
});

const datos = await respuesta.json();
console.log(datos.regimen_fiscal_codigo); // ej. "626"

Ejemplo de respuesta 200

{
  "identificacion_fiscal": "AAA080808HL8",
  "razon_social": "COMERCIALIZADORA EJEMPLO SA DE CV",
  "codigo_postal": "06600",
  "estado_texto": "CIUDAD DE MEXICO",
  "municipio_texto": "CUAUHTEMOC",
  "colonia": "JUAREZ",
  "calle": "AV REFORMA",
  "numero_exterior": "123",
  "numero_interior": null,
  "codigo_regimen_texto": "Régimen Simplificado de Confianza",
  "regimen_fiscal_codigo": "626",
  "regimen_fiscal_descripcion": "Régimen Simplificado de Confianza",
  "regimen_fiscal_coincidencia": true,
  "token_validacion": "abc123tokendelsat",
  "creditos_consumidos": ["VALIDACION_CSF_MX"]
}

POST /v1/mx/revalidaciones-fiscales

Repite la consulta al SAT usando el token_validacion de una validación anterior; no necesitas volver a subir el PDF. Ideal para revalidar periódicamente (ej. cada 15-30 días) sin pedirle al cliente su CSF de nuevo. Misma forma de respuesta que /validaciones-fiscales.

Request

CampoTipoDescripción
token_validacionstringToken regresado por una llamada previa a /validaciones-fiscales.
curl -X POST https://TU-DOMINIO.com/v1/mx/revalidaciones-fiscales \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -H "Content-Type: application/json" \
  -d '{"token_validacion": "abc123tokendelsat"}'
import requests

respuesta = requests.post(
    "https://TU-DOMINIO.com/v1/mx/revalidaciones-fiscales",
    headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
    json={"token_validacion": "abc123tokendelsat"},
)
print(respuesta.json())
const respuesta = await fetch("https://TU-DOMINIO.com/v1/mx/revalidaciones-fiscales", {
  method: "POST",
  headers: {
    Authorization: "Bearer vf_live_TU_API_KEY_AQUI",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ token_validacion: "abc123tokendelsat" }),
});
console.log(await respuesta.json());
La respuesta tiene exactamente los mismos campos que /validaciones-fiscales, incluidos regimen_fiscal_codigo y regimen_fiscal_descripcion.

POST /v1/mx/lista-69b

Consulta si un RFC aparece en el Listado 69-B del SAT (Empresas que Facturan Operaciones Simuladas, EFOS), en cualquiera de sus 4 etapas. Consulta contra una copia local que se actualiza periódicamente, no en vivo contra el SAT (el SAT no publica el CSV en un horario fijo).

Request

CampoTipoDescripción
rfcstringRFC a consultar (12-13 caracteres). Se normaliza a mayúsculas.

Response 200

CampoTipoDescripción
rfcstring
aparece_en_listadobooleanDeliberadamente NO es un solo semáforo de riesgo: un mismo RFC puede tener historial en más de una situación.
situaciones_encontradasstring[]Alguno de: presunto, desvirtuado, definitivo, sentencia_favorable.
detalleobject[]Un registro por cada coincidencia: situacion, nombre_contribuyente, datos_originales (fila cruda del SAT).
listado_actualizado_enstring | nullFecha ISO 8601 de la última actualización exitosa de la copia local.
creditos_consumidosstring[]
curl -X POST https://TU-DOMINIO.com/v1/mx/lista-69b \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -H "Content-Type: application/json" \
  -d '{"rfc": "AAA080808HL8"}'
import requests

respuesta = requests.post(
    "https://TU-DOMINIO.com/v1/mx/lista-69b",
    headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
    json={"rfc": "AAA080808HL8"},
)
datos = respuesta.json()
print(datos["aparece_en_listado"])
const respuesta = await fetch("https://TU-DOMINIO.com/v1/mx/lista-69b", {
  method: "POST",
  headers: {
    Authorization: "Bearer vf_live_TU_API_KEY_AQUI",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ rfc: "AAA080808HL8" }),
});
const datos = await respuesta.json();
console.log(datos.aparece_en_listado);

Ejemplo de respuesta 200

{
  "rfc": "AAA080808HL8",
  "aparece_en_listado": true,
  "situaciones_encontradas": ["presunto", "desvirtuado"],
  "detalle": [
    {
      "situacion": "desvirtuado",
      "nombre_contribuyente": "EJEMPLO SA DE CV",
      "datos_originales": { "No": "12345", "Oficio Global de Presunción": "500-05-2018-27105 de fecha 27 de septiembre de 2018" }
    }
  ],
  "listado_actualizado_en": "2026-08-27T06:52:57.421759+00:00",
  "creditos_consumidos": ["VAL_LISTA_NEGRA_69B"]
}

GET /v1/catalogo/regimenes-fiscales

Catálogo oficial de regímenes fiscales del SAT (c_RegimenFiscal). No requiere autenticación. Útil cuando regimen_fiscal_coincidencia te regresó false y necesitas dejar que un humano elija el régimen correcto de una lista.

curl https://TU-DOMINIO.com/v1/catalogo/regimenes-fiscales
import requests

respuesta = requests.get("https://TU-DOMINIO.com/v1/catalogo/regimenes-fiscales")
print(respuesta.json())
const respuesta = await fetch("https://TU-DOMINIO.com/v1/catalogo/regimenes-fiscales");
console.log(await respuesta.json());

Ejemplo de respuesta 200

[
  { "codigo": "601", "descripcion": "General de Ley Personas Morales" },
  { "codigo": "612", "descripcion": "Personas Físicas con Actividades Empresariales y Profesionales" },
  { "codigo": "626", "descripcion": "Régimen Simplificado de Confianza" }
]

POST /v1/mx/extracciones/curp

Sube la Constancia CURP (el PDF que se descarga en gob.mx/curp) o una foto de la CURP, y la API regresa los datos ya separados en campos individuales: la CURP, el nombre completo dividido en nombre(s) y apellidos, la fecha y entidad de nacimiento, y el sexo. Sirve para prellenar un formulario sin que la persona tenga que volver a teclear cada dato a mano.

Request

CampoTipoDescripción
archivomultipart/form-dataPDF de la Constancia CURP, o foto/imagen (JPG, PNG) de la CURP. Máximo 8 MB.
incluir_recorteboolean (query param, default false)Actívalo solo si necesitas la imagen recortada del recuadro principal (por ejemplo, para armar un expediente de evidencia de una solicitud de visa). Cuesta lo mismo en créditos que dejarlo apagado.

Response 200

CampoTipoDescripción
curpstring | null
estructura_validabooleanLa CURP tiene la forma correcta (18 caracteres, patrón esperado).
digito_verificador_coincideboolean | nullInformativo, nunca es motivo de rechazo por sí solo. null si no se pudo calcular.
nombresstring | null
apellido_paternostring | null
apellido_maternostring | null
anio_nacimientointeger | null
mes_nacimientointeger | null
dia_nacimientointeger | null
sexostring | null
entidad_nacimientostring | null
texto_qr_originalstring | nullLo que traiga el código QR del documento, tal cual, sin interpretar.
token_validacionstring | nullPresente cuando el QR resultó ser una URL de verificación.
imagen_recuadro_base64string | nullSolo presente si mandaste incluir_recorte=true y se pudo aislar el recuadro con confianza. PNG codificado en base64.
creditos_consumidosstring[]
curl -X POST "https://TU-DOMINIO.com/v1/mx/extracciones/curp?incluir_recorte=true" \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -F "archivo=@/ruta/a/constancia_curp.pdf"
import requests

with open("constancia_curp.pdf", "rb") as archivo:
    respuesta = requests.post(
        "https://TU-DOMINIO.com/v1/mx/extracciones/curp",
        headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
        params={"incluir_recorte": "true"},
        files={"archivo": archivo},
    )

datos = respuesta.json()
print(datos["curp"])
const form = new FormData();
form.append("archivo", archivoPdf); // File o Blob

const respuesta = await fetch(
  "https://TU-DOMINIO.com/v1/mx/extracciones/curp?incluir_recorte=true",
  {
    method: "POST",
    headers: { Authorization: "Bearer vf_live_TU_API_KEY_AQUI" },
    body: form,
  }
);

const datos = await respuesta.json();
console.log(datos.curp);

Ejemplo de respuesta 200

{
  "curp": "XEXX010101HNEXTX04",
  "estructura_valida": true,
  "digito_verificador_coincide": true,
  "nombres": "ANA",
  "apellido_paterno": "PEREZ",
  "apellido_materno": "GOMEZ",
  "anio_nacimiento": 2001,
  "mes_nacimiento": 1,
  "dia_nacimiento": 1,
  "sexo": "M",
  "entidad_nacimiento": "NACIDO EN EL EXTRANJERO",
  "texto_qr_original": "XEXX010101HNEXTX04|PEREZ|GOMEZ|ANA",
  "token_validacion": null,
  "imagen_recuadro_base64": "iVBORw0KGgoAAAANSUhEUgAA...(truncado)",
  "creditos_consumidos": ["EXTRACCION_CURP"]
}

POST /v1/mx/extracciones/domicilio

Sube un comprobante de domicilio en PDF (por ejemplo, un recibo de CFE, agua o teléfono descargado del portal del proveedor) y la API separa calle, colonia, código postal, ciudad o municipio y estado en campos individuales, listos para usarse en un formulario.

Por ahora solo funciona con PDFs que traigan texto seleccionable (un recibo descargado directo del portal del proveedor). Una foto o un escaneo sin capa de texto regresa confianza_extraccion: "baja" con los campos estructurados en null.

Request

CampoTipoDescripción
archivomultipart/form-dataPDF del comprobante de domicilio, con texto seleccionable. Máximo 8 MB.

Response 200

CampoTipoDescripción
callestring | null
numero_exteriorstring | null
numero_interiorstring | null
coloniastring | null
codigo_postalstring | null
ciudad_o_municipiostring | null
estadostring | null
confianza_extraccionstring"alta" si se reconocieron al menos 2 de las etiquetas esperadas (CP, calle, colonia, estado); "baja" si no, y entonces conviene revisar texto_completo a mano.
texto_completostring | nullPrimeros 3000 caracteres del texto del PDF, por si necesitas revisarlo cuando la confianza salió baja.
creditos_consumidosstring[]
curl -X POST https://TU-DOMINIO.com/v1/mx/extracciones/domicilio \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -F "archivo=@/ruta/a/recibo_cfe.pdf"
import requests

with open("recibo_cfe.pdf", "rb") as archivo:
    respuesta = requests.post(
        "https://TU-DOMINIO.com/v1/mx/extracciones/domicilio",
        headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
        files={"archivo": archivo},
    )

datos = respuesta.json()
print(datos["codigo_postal"])
const form = new FormData();
form.append("archivo", archivoPdf); // File o Blob

const respuesta = await fetch("https://TU-DOMINIO.com/v1/mx/extracciones/domicilio", {
  method: "POST",
  headers: { Authorization: "Bearer vf_live_TU_API_KEY_AQUI" },
  body: form,
});

const datos = await respuesta.json();
console.log(datos.codigo_postal);

Ejemplo de respuesta 200

{
  "calle": "AV REFORMA",
  "numero_exterior": "123",
  "numero_interior": null,
  "colonia": "JUAREZ",
  "codigo_postal": "06600",
  "ciudad_o_municipio": "CUAUHTEMOC",
  "estado": "CIUDAD DE MEXICO",
  "confianza_extraccion": "alta",
  "texto_completo": "CFE RECIBO... AV REFORMA 123... COL JUAREZ... C.P. 06600...",
  "creditos_consumidos": ["EXTRACCION_DOMICILIO"]
}

POST /v1/mx/extracciones/pasaporte

Sube un pasaporte en PDF y la API lee la zona MRZ (las dos líneas de texto al pie de la página de datos) para extraer nombre, apellidos, fechas de nacimiento y vencimiento, y confirmar que los dígitos de control del documento cierran correctamente. Útil para detectar un pasaporte alterado o mal capturado antes de usarlo en un trámite.

Por ahora solo funciona con PDFs que traigan la MRZ como texto seleccionable (por ejemplo, un PDF ya pasado por OCR de otro sistema). Si ya tienes el texto de la MRZ por otro medio (tu propio OCR, o capturado a mano), usa POST /v1/mx/extracciones/pasaporte/mrz-texto en su lugar: manda {"mrz_texto": "..."} y no necesita subir ningún archivo.

Request

CampoTipoDescripción
archivomultipart/form-dataPDF del pasaporte, con la MRZ como texto seleccionable. Máximo 8 MB.

Response 200

CampoTipoDescripción
pais_emisorstring | nullCódigo de 3 letras del país que emitió el documento.
nacionalidadstring | null
apellido_paternostring | nullLa MRZ trae los apellidos juntos; se separan con la misma heurística que usa la CURP.
apellido_maternostring | null
nombresstring | null
numero_documentostring | null
sexostring | null
anio_nacimientointeger | null
mes_nacimientointeger | null
dia_nacimientointeger | null
anio_expiracioninteger | null
mes_expiracioninteger | null
dia_expiracioninteger | null
numero_personalstring | nullCampo opcional de la MRZ, no todos los países lo usan.
digitos_verificadores_validosobjectUn booleano por cada dígito de control de la MRZ: numero_documento, fecha_nacimiento, fecha_expiracion, compuesto.
todos_los_digitos_validosbooleantrue solo si los 4 dígitos de control cerraron correctamente.
creditos_consumidosstring[]
curl -X POST https://TU-DOMINIO.com/v1/mx/extracciones/pasaporte \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -F "archivo=@/ruta/a/pasaporte.pdf"
import requests

with open("pasaporte.pdf", "rb") as archivo:
    respuesta = requests.post(
        "https://TU-DOMINIO.com/v1/mx/extracciones/pasaporte",
        headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
        files={"archivo": archivo},
    )

datos = respuesta.json()
print(datos["todos_los_digitos_validos"])
const form = new FormData();
form.append("archivo", archivoPdf); // File o Blob

const respuesta = await fetch("https://TU-DOMINIO.com/v1/mx/extracciones/pasaporte", {
  method: "POST",
  headers: { Authorization: "Bearer vf_live_TU_API_KEY_AQUI" },
  body: form,
});

const datos = await respuesta.json();
console.log(datos.todos_los_digitos_validos);

Ejemplo de respuesta 200

{
  "pais_emisor": "UTO",
  "nacionalidad": "UTO",
  "apellido_paterno": "ERIKSSON",
  "apellido_materno": null,
  "nombres": "ANNA MARIA",
  "numero_documento": "L898902C3",
  "sexo": "M",
  "anio_nacimiento": 1974,
  "mes_nacimiento": 8,
  "dia_nacimiento": 12,
  "anio_expiracion": 2012,
  "mes_expiracion": 4,
  "dia_expiracion": 15,
  "numero_personal": "ZE184226B",
  "digitos_verificadores_validos": {
    "numero_documento": true,
    "fecha_nacimiento": true,
    "fecha_expiracion": true,
    "compuesto": true
  },
  "todos_los_digitos_validos": true,
  "creditos_consumidos": ["EXTRACCION_PASAPORTE"]
}

POST /v1/mx/validaciones-cruzadas

Compara los datos que ya extrajiste de 2 a 8 documentos de la misma persona (CURP, domicilio, pasaporte, CSF, o los tuyos propios) y te dice si son consistentes entre sí. No procesa ningún archivo nuevo, solo compara los campos que ya tienes, así que sirve como una última revisión antes de usar esos datos en un formulario.

Request

CampoTipoDescripción
documentosobject[] (2 a 8 elementos)Cada elemento representa los datos ya extraídos de un documento. Ver campos abajo.
Campo de documentos[i]TipoDescripción
tipostringIdentificador libre del documento de origen, por ejemplo "csf", "curp", "pasaporte", "domicilio".
nombre_completostring | nullUn solo string con el nombre completo (no lo partas en nombres/apellidos): la comparación es por traslape de palabras, así que el orden entre documentos no importa.
anio_nacimientointeger | null
mes_nacimientointeger | null
dia_nacimientointeger | null
curpstring | null
rfcstring | null

Response 200

CampoTipoDescripción
consistentebooleantrue si no se encontró ninguna discrepancia.
discrepanciasobject[]Un elemento por cada campo (curp, rfc, fecha_nacimiento o nombre_completo) donde los documentos no coinciden: campo (string) y valores (objeto, tipo de documento apuntando al valor visto ahí).
creditos_consumidosstring[]
curl -X POST https://TU-DOMINIO.com/v1/mx/validaciones-cruzadas \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -H "Content-Type: application/json" \
  -d '{
    "documentos": [
      { "tipo": "csf", "nombre_completo": "JUAN PEREZ LOPEZ", "rfc": "PELJ800101ABC", "anio_nacimiento": 1980, "mes_nacimiento": 1, "dia_nacimiento": 1 },
      { "tipo": "curp", "nombre_completo": "JUAN PEREZ LOPEZ", "curp": "PELJ800101HDFRPN01", "anio_nacimiento": 1980, "mes_nacimiento": 1, "dia_nacimiento": 2 }
    ]
  }'
import requests

respuesta = requests.post(
    "https://TU-DOMINIO.com/v1/mx/validaciones-cruzadas",
    headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
    json={
        "documentos": [
            {"tipo": "csf", "nombre_completo": "JUAN PEREZ LOPEZ", "rfc": "PELJ800101ABC", "anio_nacimiento": 1980, "mes_nacimiento": 1, "dia_nacimiento": 1},
            {"tipo": "curp", "nombre_completo": "JUAN PEREZ LOPEZ", "curp": "PELJ800101HDFRPN01", "anio_nacimiento": 1980, "mes_nacimiento": 1, "dia_nacimiento": 2},
        ]
    },
)
datos = respuesta.json()
print(datos["consistente"])
const respuesta = await fetch("https://TU-DOMINIO.com/v1/mx/validaciones-cruzadas", {
  method: "POST",
  headers: {
    Authorization: "Bearer vf_live_TU_API_KEY_AQUI",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    documentos: [
      { tipo: "csf", nombre_completo: "JUAN PEREZ LOPEZ", rfc: "PELJ800101ABC", anio_nacimiento: 1980, mes_nacimiento: 1, dia_nacimiento: 1 },
      { tipo: "curp", nombre_completo: "JUAN PEREZ LOPEZ", curp: "PELJ800101HDFRPN01", anio_nacimiento: 1980, mes_nacimiento: 1, dia_nacimiento: 2 },
    ],
  }),
});
const datos = await respuesta.json();
console.log(datos.consistente);

Ejemplo de respuesta 200

{
  "consistente": false,
  "discrepancias": [
    {
      "campo": "fecha_nacimiento",
      "valores": { "csf": "1980-01-01", "curp": "1980-01-02" }
    }
  ],
  "creditos_consumidos": ["VALIDACION_CRUZADA"]
}

POST /v1/mx/extracciones/combinada

Procesa hasta 4 documentos de la misma persona (CSF, CURP, domicilio y pasaporte) en una sola llamada, con descuento sobre llamar cada endpoint por separado, y guarda el resultado en el historial auditable de un expediente. Si alguno de los documentos no se puede procesar, los demás que sí funcionaron se regresan igual: un solo documento fallido no cancela toda la llamada.

Request

CampoTipoDescripción
csfmultipart/form-data (opcional)Constancia de Situación Fiscal (PDF con QR del SAT).
curpmultipart/form-data (opcional)Constancia CURP (PDF) o foto/imagen de la CURP.
domiciliomultipart/form-data (opcional)Comprobante de domicilio (PDF con texto seleccionable).
pasaportemultipart/form-data (opcional)Pasaporte (PDF con la MRZ como texto seleccionable).
id_expedientestring, form field (opcional)Expediente existente al que agregar estos documentos. Si no se manda, se crea uno nuevo.
referencia_expedientestring, form field (opcional)Solo aplica si no se manda id_expediente: referencia libre para el expediente nuevo.
incluir_recorte_curpboolean, form field (default false)Igual que incluir_recorte en /extracciones/curp, pero para el campo curp de esta respuesta.
Manda al menos uno de los 4 documentos. Cada uno se procesa y se cobra por separado, así que si el saldo se agota a la mitad, los documentos ya procesados no se pierden: solo los que faltan quedan marcados como error de saldo en errores.

Response 200

CampoTipoDescripción
id_expedientestringEl expediente usado (el que mandaste, o uno nuevo si no mandaste id_expediente).
fiscalobject | nullMismo objeto que regresa /v1/mx/validaciones-fiscales. null si no mandaste csf.
curpobject | nullMismo objeto que regresa /v1/mx/extracciones/curp. null si no mandaste curp.
domicilioobject | nullMismo objeto que regresa /v1/mx/extracciones/domicilio. null si no mandaste domicilio.
pasaporteobject | nullMismo objeto que regresa /v1/mx/extracciones/pasaporte. null si no mandaste pasaporte.
erroresobjectSolo incluye los documentos que sí mandaste pero no se pudieron procesar: tipo de documento apuntando al motivo.
creditos_consumidosstring[]Una clave por cada documento procesado con éxito.
curl -X POST https://TU-DOMINIO.com/v1/mx/extracciones/combinada \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -F "referencia_expediente=Juan Perez - visa EB-2" \
  -F "csf=@/ruta/a/constancia.pdf" \
  -F "curp=@/ruta/a/constancia_curp.pdf" \
  -F "pasaporte=@/ruta/a/pasaporte.pdf"
import requests

with open("constancia.pdf", "rb") as csf, open("constancia_curp.pdf", "rb") as curp, open("pasaporte.pdf", "rb") as pasaporte:
    respuesta = requests.post(
        "https://TU-DOMINIO.com/v1/mx/extracciones/combinada",
        headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
        data={"referencia_expediente": "Juan Perez - visa EB-2"},
        files={"csf": csf, "curp": curp, "pasaporte": pasaporte},
    )

datos = respuesta.json()
print(datos["id_expediente"])
const form = new FormData();
form.append("referencia_expediente", "Juan Perez - visa EB-2");
form.append("csf", archivoCsf);
form.append("curp", archivoCurp);
form.append("pasaporte", archivoPasaporte);

const respuesta = await fetch("https://TU-DOMINIO.com/v1/mx/extracciones/combinada", {
  method: "POST",
  headers: { Authorization: "Bearer vf_live_TU_API_KEY_AQUI" },
  body: form,
});

const datos = await respuesta.json();
console.log(datos.id_expediente);

Ejemplo de respuesta 200

{
  "id_expediente": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "fiscal": { "identificacion_fiscal": "AAA080808HL8", "razon_social": "COMERCIALIZADORA EJEMPLO SA DE CV", "...": "..." },
  "curp": { "curp": "XEXX010101HNEXTX04", "nombres": "ANA", "...": "..." },
  "domicilio": null,
  "pasaporte": { "pais_emisor": "UTO", "nombres": "ANNA MARIA", "...": "..." },
  "errores": {},
  "creditos_consumidos": ["FISCAL_EN_COMBINADA", "CURP_EN_COMBINADA", "PASAPORTE_EN_COMBINADA"]
}

POST /v1/mx/expedientes

Crea un expediente vacío para agrupar las extracciones de una persona o trámite bajo un mismo identificador (por ejemplo, todos los documentos de una solicitud de visa). Después vas agregando documentos con /v1/mx/extracciones/combinada usando el id_expediente que te regresa aquí.

Request

CampoTipoDescripción
referenciastring | null (opcional)Texto libre para reconocer el expediente en tu propio sistema, por ejemplo "Juan Pérez - visa EB-2". Nunca se interpreta ni se valida.

Response 200

CampoTipoDescripción
id_expedientestringUUID del expediente. Guárdalo para usarlo en /extracciones/combinada y en /expedientes/{id_expediente}.
referenciastring | null
creado_enstring (fecha ISO 8601)
curl -X POST https://TU-DOMINIO.com/v1/mx/expedientes \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -H "Content-Type: application/json" \
  -d '{"referencia": "Juan Perez - visa EB-2"}'
import requests

respuesta = requests.post(
    "https://TU-DOMINIO.com/v1/mx/expedientes",
    headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
    json={"referencia": "Juan Perez - visa EB-2"},
)
datos = respuesta.json()
print(datos["id_expediente"])
const respuesta = await fetch("https://TU-DOMINIO.com/v1/mx/expedientes", {
  method: "POST",
  headers: {
    Authorization: "Bearer vf_live_TU_API_KEY_AQUI",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ referencia: "Juan Perez - visa EB-2" }),
});
const datos = await respuesta.json();
console.log(datos.id_expediente);

Ejemplo de respuesta 200

{
  "id_expediente": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "referencia": "Juan Perez - visa EB-2",
  "creado_en": "2026-09-04T10:15:00.000000+00:00"
}

GET /v1/mx/expedientes/{id_expediente}

Consulta el historial completo de un expediente: cada documento que se procesó, el endpoint que se usó, si fue exitoso y el resultado completo tal como se le devolvió al cliente en ese momento. Es el registro auditable para poder mostrar de dónde salió cada dato más adelante, por ejemplo ante un revisor de la solicitud.

Request

CampoTipoDescripción
id_expedientestring (parámetro de ruta)UUID del expediente, regresado por POST /expedientes o por POST /extracciones/combinada.

Response 200

CampoTipoDescripción
id_expedientestring
referenciastring | null
creado_enstring (fecha ISO 8601)
documentosobject[]Uno por cada documento procesado, en orden cronológico. Cada elemento trae tipo_documento (string), endpoint (string), exitoso (boolean), resultado (object, el JSON completo que se le devolvió al cliente para ese documento) y creado_en (fecha ISO 8601).
Regresa 404 si el expediente no existe, o si existe pero pertenece a otra cuenta.
curl https://TU-DOMINIO.com/v1/mx/expedientes/3fa85f64-5717-4562-b3fc-2c963f66afa6 \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI"
import requests

respuesta = requests.get(
    "https://TU-DOMINIO.com/v1/mx/expedientes/3fa85f64-5717-4562-b3fc-2c963f66afa6",
    headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
)
datos = respuesta.json()
print(len(datos["documentos"]))
const respuesta = await fetch(
  "https://TU-DOMINIO.com/v1/mx/expedientes/3fa85f64-5717-4562-b3fc-2c963f66afa6",
  { headers: { Authorization: "Bearer vf_live_TU_API_KEY_AQUI" } }
);
const datos = await respuesta.json();
console.log(datos.documentos.length);

Ejemplo de respuesta 200

{
  "id_expediente": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "referencia": "Juan Perez - visa EB-2",
  "creado_en": "2026-09-04T10:15:00.000000+00:00",
  "documentos": [
    {
      "tipo_documento": "curp",
      "endpoint": "/v1/mx/extracciones/combinada",
      "exitoso": true,
      "resultado": { "curp": "XEXX010101HNEXTX04", "nombres": "ANA", "...": "..." },
      "creado_en": "2026-09-04T10:16:12.000000+00:00"
    },
    {
      "tipo_documento": "domicilio",
      "endpoint": "/v1/mx/extracciones/combinada",
      "exitoso": false,
      "resultado": { "error": "No se pudo leer texto del PDF (¿es un escaneo sin capa de texto?)." },
      "creado_en": "2026-09-04T10:16:12.000000+00:00"
    }
  ]
}

POST /v1/mx/consultas/cedula-profesional

Busca en el Registro Nacional de Profesionistas de la SEP por número de cédula o por nombre, y regresa el título, la institución y el tipo de registro encontrado. Sirve para confirmar que un profesionista (abogado, contador, médico, y demás) de verdad tiene la cédula que dice tener.

Request

CampoTipoDescripción
numero_cedulastring | nullManda este campo o nombre (al menos uno de los dos).
nombrestring | nullBúsqueda por nombre si no se tiene el número de cédula.

Response 200

CampoTipoDescripción
total_encontradosinteger
resultadosobject[]Uno por cada coincidencia: numero_cedula, nombre_completo, titulo, institucion, tipo, anio_registro y datos_originales (objeto, el registro crudo tal como lo regresa la SEP).
creditos_consumidosstring[]
curl -X POST https://TU-DOMINIO.com/v1/mx/consultas/cedula-profesional \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -H "Content-Type: application/json" \
  -d '{"numero_cedula": "1234567"}'
import requests

respuesta = requests.post(
    "https://TU-DOMINIO.com/v1/mx/consultas/cedula-profesional",
    headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
    json={"numero_cedula": "1234567"},
)
datos = respuesta.json()
print(datos["total_encontrados"])
const respuesta = await fetch("https://TU-DOMINIO.com/v1/mx/consultas/cedula-profesional", {
  method: "POST",
  headers: {
    Authorization: "Bearer vf_live_TU_API_KEY_AQUI",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ numero_cedula: "1234567" }),
});
const datos = await respuesta.json();
console.log(datos.total_encontrados);

Ejemplo de respuesta 200

{
  "total_encontrados": 1,
  "resultados": [
    {
      "numero_cedula": "1234567",
      "nombre_completo": "JUAN PEREZ LOPEZ",
      "titulo": "LICENCIADO EN DERECHO",
      "institucion": "UNIVERSIDAD NACIONAL AUTONOMA DE MEXICO",
      "tipo": "TITULO",
      "anio_registro": "2005",
      "datos_originales": {}
    }
  ],
  "creditos_consumidos": ["CONSULTA_CEDULA_PROFESIONAL"]
}

POST /v1/mx/validaciones/clabe

Valida la estructura y el dígito verificador de una CLABE interbancaria de 18 dígitos, e identifica el banco a partir de los primeros 3 dígitos. Sirve para detectar un error de captura antes de programar una transferencia o un pago.

Request

CampoTipoDescripción
clabestring (exactamente 18 caracteres)La CLABE a validar.

Response 200

CampoTipoDescripción
clabestring
estructura_validabooleanLa CLABE tiene 18 dígitos con la forma esperada.
digito_verificador_coincideboolean | nullnull si no se pudo calcular (por ejemplo, si estructura_valida es false).
codigo_bancostring | nullLos primeros 3 dígitos de la CLABE.
nombre_bancostring | nullnull si el código de banco no se reconoce en el catálogo.
codigo_plazastring | null
creditos_consumidosstring[]
curl -X POST https://TU-DOMINIO.com/v1/mx/validaciones/clabe \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI" \
  -H "Content-Type: application/json" \
  -d '{"clabe": "002010077777777770"}'
import requests

respuesta = requests.post(
    "https://TU-DOMINIO.com/v1/mx/validaciones/clabe",
    headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
    json={"clabe": "002010077777777770"},
)
datos = respuesta.json()
print(datos["nombre_banco"])
const respuesta = await fetch("https://TU-DOMINIO.com/v1/mx/validaciones/clabe", {
  method: "POST",
  headers: {
    Authorization: "Bearer vf_live_TU_API_KEY_AQUI",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ clabe: "002010077777777770" }),
});
const datos = await respuesta.json();
console.log(datos.nombre_banco);

Ejemplo de respuesta 200

{
  "clabe": "002010077777777770",
  "estructura_valida": true,
  "digito_verificador_coincide": true,
  "codigo_banco": "002",
  "nombre_banco": "BANAMEX",
  "codigo_plaza": "010",
  "creditos_consumidos": ["VALIDACION_CLABE"]
}

GET /v1/datos-economicos/...

Consulta series de datos económicos y financieros (tipo de cambio, inflación, PIB, tasas de interés, población, entre otros) de 7 fuentes oficiales distintas: Banxico, INEGI, FRED, la OCDE, el FMI, la CEPAL y el Banco Mundial. Las 7 comparten la misma forma de respuesta, así que tu integración no cambia según la fuente que consultes.

A diferencia del catálogo de regímenes fiscales, estos endpoints sí requieren tu API key de ValidatosAPI (mismo header Authorization: Bearer que el resto del catálogo). Además, Banxico, INEGI y FRED requieren que el servidor de ValidatosAPI tenga configurado un token gratuito propio de esa fuente (ver la columna "Token propio" en la tabla más abajo); OCDE, FMI, CEPALSTAT y Banco Mundial no necesitan ningún token adicional.

Ejemplo completo: Banxico

GET /v1/datos-economicos/banxico/{id_serie}: por ejemplo, SF43718 es el tipo de cambio pesos por dólar (FIX) que publica Banxico.

CampoTipoDescripción
id_seriestring (parámetro de ruta)Identificador de la serie en el catálogo del SIE de Banxico.
solo_ultimoboolean (query param, default true)true = solo el dato más reciente (1 crédito). false = serie completa (2 créditos).
fecha_iniciostring | null (query param)Formato YYYY-MM-DD. Solo aplica si solo_ultimo=false.
fecha_finstring | null (query param)Formato YYYY-MM-DD. Solo aplica si solo_ultimo=false.

Response 200 (misma forma para las 7 fuentes)

CampoTipoDescripción
fuentestringPor ejemplo "banxico", "inegi", "fred".
identificadorstringEl identificador consultado, tal cual lo mandaste en la ruta.
titulostring | nullNombre descriptivo de la serie, si la fuente lo incluye en su respuesta.
datosobject[]Un elemento por observación: fecha (string, formato según la fuente) y valor (string, para no perder precisión ni formato de fuentes que traen texto, ej. "preliminar").
creditos_consumidosstring[]
curl "https://TU-DOMINIO.com/v1/datos-economicos/banxico/SF43718?solo_ultimo=true" \
  -H "Authorization: Bearer vf_live_TU_API_KEY_AQUI"
import requests

respuesta = requests.get(
    "https://TU-DOMINIO.com/v1/datos-economicos/banxico/SF43718",
    headers={"Authorization": "Bearer vf_live_TU_API_KEY_AQUI"},
    params={"solo_ultimo": "true"},
)
datos = respuesta.json()
print(datos["datos"][0]["valor"])
const respuesta = await fetch(
  "https://TU-DOMINIO.com/v1/datos-economicos/banxico/SF43718?solo_ultimo=true",
  { headers: { Authorization: "Bearer vf_live_TU_API_KEY_AQUI" } }
);
const datos = await respuesta.json();
console.log(datos.datos[0].valor);

Ejemplo de respuesta 200

{
  "fuente": "banxico",
  "identificador": "SF43718",
  "titulo": "Tipo de cambio pesos por dólar E.U.A. FIX",
  "datos": [
    { "fecha": "03/09/2026", "valor": "18.3541" }
  ],
  "creditos_consumidos": ["DATO_ECONOMICO"]
}

Las 7 fuentes

FuenteEndpointToken propioEjemplos de identificador
Banxico GET /v1/datos-economicos/banxico/{id_serie} Sí, gratis en banxico.org.mx/SieAPIRest/service/v1/token SF43718 (tipo de cambio FIX)
INEGI GET /v1/datos-economicos/inegi/{id_indicador} (query area_geografica, default "0700" nacional) Sí, gratis en inegi.org.mx/app/api/indicadores 628194 (INPC)
FRED GET /v1/datos-economicos/fred/{id_serie} (mismos query solo_ultimo/fecha_inicio/fecha_fin que Banxico) Sí, gratis en fred.stlouisfed.org/docs/api/api_key.html GDP (PIB de EUA), UNRATE (desempleo de EUA)
OCDE GET /v1/datos-economicos/ocde/{dataflow} (requiere query filtro_dimensiones) No, pública dataflow OECD.SDD.TPS,DSD_PDB@DF_PDB_LV,1.0 con filtro_dimensiones=MEX.A.GDPHRS..USD_PPP_H.Q...
FMI GET /v1/datos-economicos/fmi/{base_datos}/{frecuencia}/{paises}/{indicador} No, pública IFS / M / MX / ENDA_XDC_USD_RATE (tipo de cambio)
CEPALSTAT GET /v1/datos-economicos/cepalstat/{id_indicador} (query pais, anio, opcionales) No, pública Ver catálogo en statistics.cepal.org/portal/databank
Banco Mundial GET /v1/datos-economicos/banco-mundial/{codigo_pais}/{id_indicador} No, pública MX / NY.GDP.MKTP.CD (PIB en dólares)

Manejo de errores

Todos los errores regresan un cuerpo JSON con un solo campo detail describiendo qué pasó.

CódigoCuándo ocurre
401Falta el header Authorization, o la API key es inválida/revocada/expirada.
402Saldo de créditos insuficiente. No se cobra ni se procesa nada.
403La cuenta del cliente no está activa.
413El PDF excede el máximo permitido (8 MB).
415El archivo enviado no es un PDF válido.
422El documento no pudo procesarse o validarse (ver detail para la causa específica), o el body de la petición no cumple la validación esperada.
500Error interno. Si persiste, contáctanos con el id de la respuesta si está disponible.
{
  "detail": "Falta la API key (header 'Authorization: Bearer ')."
}