SubalansAPI
Documentación de la API

Subalans API

Obtén documentos fiscales (SAT, IMSS, Infonavit), consulta el Buzón Tributario, descarga CFDIs y valida RFCs contra las listas negras del SAT — todo desde una API REST simple, sin manejar navegadores ni portales gubernamentales.

REST · JSON Bearer token 7 servicios stateless
Primeros pasos

Introducción

Cada servicio vive bajo un mismo dominio y expone un único endpoint POST /api/v1 (más un GET /health). Todas las peticiones se autentican con un Bearer token y envían las credenciales del contribuyente en el cuerpo JSON. Las APIs son stateless: hacen login en cada llamada, no guardan credenciales ni archivos, y no registran datos sensibles.

ServicioEndpoint (Base URL)Devuelve
Constancia de Situación Fiscalhttps://subalans.comPDF
Opinión SAT (32D)https://subalans.comPDF
Opinión IMSShttps://subalans.comPDF
Opinión Infonavithttps://subalans.comPDF
Buzón Tributario (notificaciones)https://subalans.comJSON (metadatos)
CFDI (descarga / metadatos)https://subalans.comZIP · XML · PDF
Listas negras EFOS (69-B)https://subalans.comJSON
Convención. Los endpoints de documentos PDF (/constancia, /opinion, /opinion-imss, /infonavit) devuelven en 200 el PDF binario directamente, con Content-Type: application/pdf y Content-Disposition — la respuesta no es JSON. Sólo CFDI y Buzón responden JSON; ahí los archivos vienen codificados en Base64 (campos zip_base64, base64). Los errores siempre son JSON.
Primeros pasos

Autenticación

Manda tu token en el header Authorization con el esquema Bearer. Todas las peticiones usan Content-Type: application/json.

HeaderValor
AuthorizationBearer sk_live_xxxxxxxxxxxxrequerido
Content-Typeapplication/jsonrequerido
cURL
curl -X POST https://subalans.com/api/v1/constancia \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "rfc": "XAXX010101000", "ciec": "TU_CIEC" }'
Nunca expongas tu token ni las credenciales del contribuyente en el navegador o en apps cliente. Las llamadas a esta API deben salir siempre desde tu backend.

Un token ausente devuelve 401 { "error": "Falta el header Authorization: Bearer <token>." }; una llave inválida o revocada devuelve 401 { "error": "API key inválida o revocada." }. Los 401 traen solo error (sin ok).

Permisos y vigencia de las llaves

Desde el dashboard de desarrolladores cada llave puede configurarse con:

  • Permisos por servicio — restringe la llave a ciertos servicios (p. ej. sólo EFOS y CFDI). Llamar a un servicio no permitido devuelve 403 code: "key_scope_denied" con la lista allowed_services en el cuerpo.
  • Vigencia — 7 días, 30 días, 90 días, 1 año o sin expiración. Una llave vencida (expires_at) responde 401 igual que una revocada.
Primeros pasos

Credenciales del contribuyente

Cada documento se obtiene autenticándose ante el portal del gobierno con las credenciales del contribuyente. Hay dos métodos; cada servicio acepta uno o ambos:

Método CIEC

CampoTipoDescripción
rfcstringRFC del contribuyente (12-13 caracteres).
ciecstringContraseña CIEC del SAT.

Método e.firma (FIEL)

CampoTipoDescripción
rfcstringRFC del contribuyente.
cer_b64string (base64)Archivo .cer (certificado) codificado en Base64.
key_b64string (base64)Archivo .key (clave privada) codificado en Base64.
passwordstringContraseña de la clave privada de la e.firma. El campo se llama password; en Infonavit también se acepta el alias efirma_password para distinguirla de la contraseña del portal.
Node.js — preparar e.firma
const fs = require("fs");
const payload = {
  rfc: "ACE180101AA1",
  cer_b64: fs.readFileSync("cert.cer").toString("base64"),
  key_b64: fs.readFileSync("private.key").toString("base64"),
  password: "TU_PASSWORD_EFIRMA",
};
Validación local. La API verifica la contraseña de la e.firma antes de tocar al portal: si el .key no abre con la contraseña, o el .cer no es un certificado válido, responde en milisegundos con 422 y un mensaje claro — sin gastar tiempo en el SAT.
Primeros pasos

Formato de petición y respuesta

Toda petición es un POST con cuerpo JSON al endpoint /api/v1 del servicio. La forma del 200 depende del servicio:

  • Endpoints de PDF (/constancia, /opinion, /opinion-imss, /infonavit): el cuerpo de la respuesta es el PDF binario (Content-Type: application/pdf + Content-Disposition). Guárdalo a disco o súbelo a tu storage tal cual.
  • CFDI y Buzón: JSON con "ok": true y los datos; los archivos van en Base64.
Respuesta 200 de un endpoint de PDF
HTTP/1.1 200 OK
Content-Type: application/pdf
Content-Disposition: attachment; filename="constancia.pdf"

%PDF-1.7 … (bytes del PDF)
Node.js — guardar el PDF
const r = await fetch("https://subalans.com/api/v1/constancia", {
  method: "POST",
  headers: { Authorization: "Bearer " + token, "Content-Type": "application/json" },
  body: JSON.stringify(payload),
});
if (!r.ok) throw new Error((await r.json()).error);  // los errores sí son JSON
const pdf = Buffer.from(await r.arrayBuffer());  // el 200 es binario
fs.writeFileSync("constancia.pdf", pdf);           // o súbelo a tu storage
Primeros pasos

Manejo de errores

Las respuestas de error siempre son JSON con "ok": false, un error legible (en español, listo para mostrar al usuario) y, cuando aplica, un code semántico para tu lógica. El código HTTP te dice de quién es el problema:

HTTPSignificado¿De quién?Qué hacer
200Éxito — documento incluido.Procesa la respuesta.
400Petición inválida (RFC mal formado, faltan campos).Tu integraciónCorrige el cuerpo. No reintentar.
401Token ausente o inválido.Tu integraciónRevisa el header Authorization.
422Credencial incorrecta o sin permiso (CIEC/e.firma mala, sin medios de contacto…).El contribuyentePide al usuario corregir la credencial. No reintentar igual.
429Rebasaste el límite de solicitudes/min de tu plan.Tu integraciónEspera el retry-after (5 s) y reintenta, o sube de plan.
502El portal del gobierno respondió mal o se cayó.SAT/IMSS/InfonavitReintentar con backoff.
504El portal del gobierno tardó demasiado (timeout).SAT/IMSS/InfonavitReintentar más tarde.

Campo code (taxonomía)

codeHTTPSignificado
credencial422 · 400CIEC o e.firma incorrecta / vencida / revocada. Ojo: en los endpoints de PDF llega como 422; en Buzón y CFDI llega como 400 con el mismo code: "credencial".
efirma_invalida422La contraseña no abre el .key, o el .cer no es un certificado válido.
efirma_requerida400El documento exige e.firma y no se enviaron cer_b64/key_b64/password.
sin_medios_contacto422(IMSS) El RFC no tiene correo/celular registrados en el Buzón IMSS.
tecnico502Fallo técnico del portal del gobierno. Reintentable.
timeout504El portal no respondió a tiempo. Reintentable.
plan_gate403El servicio exige un plan superior (p. ej. Infonavit requiere Pro o superior). El cuerpo incluye min_plan y upgrade_url.
key_scope_denied403Tu llave no tiene permiso para este servicio. El cuerpo incluye allowed_services.
rfc_limit_reached403Alcanzaste el límite de RFCs de tu plan (ver Padrón de RFCs).
tecnico503Infraestructura de límites caída (rate limiter). Reintenta tras el retry-after (2 s).
rfc_registry_unavailable503Padrón de RFCs temporalmente no disponible. Reintenta tras el retry-after (5 s).
Llave vencida. Si tu llave tiene vigencia configurada y ya expiró (expires_at), la API responde 401 igual que una llave revocada. Genera una nueva en el panel.
Error de credencial (ejemplo)
{
  "ok": false,
  "code": "credencial",
  "error": "La CIEC es incorrecta: el RFC o la contraseña CIEC no son válidos en el SAT."
}
Mensajes listos para el usuario. El campo error viene siempre en español y describe la causa de forma accionable (p. ej. "Tu e.firma está revocada", "La contraseña del portal Infonavit es incorrecta"). Puedes mostrarlo tal cual.
Primeros pasos

Reintentos y timeouts

Los documentos que requieren navegar portales del gobierno (IMSS, Infonavit, Opinión SAT) tienen latencia variable porque dependen de servicios externos (validación OCSP del SAT, anti-bots, etc.). Diseña tu cliente para tolerarlo:

  • Timeout del cliente ≥ 180 s para IMSS e Infonavit (no cortes a los 90 s — matarías corridas válidas).
  • Reintenta sólo 5xx (502/504, code: "tecnico"/"timeout") con backoff exponencial. No reintentes 4xx — son del lado del usuario.
  • Latencia típica por servicio:
ServicioLatencia típicaNotas
EFOS (listas negras)~0.15 sConsulta en base de datos, instantánea.
Constancia / Opinión SAT7–20 se.firma más rápida que CIEC (sin captcha).
Buzón Tributario10–25 s
CFDI (masivo)~35 sUn ZIP por periodo.
Opinión Infonavit12–90 sDepende del OCSP del SAT.
Opinión IMSS18–100 sAlta variabilidad por el OCSP del SAT.
Primeros pasos

Modo asíncrono y webhooks

Los documentos que dependen de portales del gobierno pueden tardar de segundos a minutos. En vez de mantener la conexión abierta, agrega "async": true al cuerpo de cualquier endpoint de documento (/constancia, /opinion, /opinion-imss, /infonavit, /buzon, /cfdi) y la API responde de inmediato con un job_id:

Petición asíncrona
curl -X POST https://subalans.com/api/v1/opinion \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "rfc": "XAXX010101000", "ciec": "TU_CIEC", "async": true }'
Respuesta 202
{
  "ok": true,
  "job_id": "job_a1b2c3d4e5f6a7b8c9d0",
  "status": "queued",
  "poll": "/api/v1/jobs/job_a1b2c3d4e5f6a7b8c9d0"
}

Consultar el resultado (polling)

GET /api/v1/jobs/{job_id} es la fuente de verdad y funciona en todos los planes. Estados: queuedrunningdone | failed. En done, el campo result trae la misma respuesta que el modo síncrono (en los endpoints de PDF, el documento viene en result.pdf_base64). Los resultados viven 24 horas.

GET /api/v1/jobs/{id} — job terminado
{
  "ok": true,
  "job_id": "job_a1b2c3d4e5f6a7b8c9d0",
  "status": "done",
  "result": { "ok": true, "pdf_base64": "JVBERi0…", "sentido": "positiva" }
}

Si el trámite falla, status es failed y error trae el mensaje (mismo texto y semántica que los errores síncronos). Los fallos técnicos del portal se reintentan solos con backoff antes de darse por vencidos; los de credencial fallan a la primera (no tiene caso reintentar una CIEC incorrecta).

Webhooks (planes Pro y Scale)

Configura tu URL en el panel (sección Webhooks) o mándala por petición en webhook_url. Al terminar el job te enviamos un POST firmado:

Evento de webhook
POST https://tuservidor.com/hooks/subalans
X-Subalans-Signature: t=1780000000,v1=5f8a…
X-Subalans-Event-Id: evt_job_a1b2c3d4e5f6a7b8c9d0_done

{
  "event": "job.completed",   // o "job.failed"
  "job_id": "job_a1b2c3d4e5f6a7b8c9d0",
  "type": "ocsat",
  "status": "done",
  "result": { … },
  "event_id": "evt_job_a1b2c3d4e5f6a7b8c9d0_done"
}

Verificar la firma

El header X-Subalans-Signature es t=<epoch>,v1=<hex> donde v1 = HMAC-SHA256(secreto, t + "." + cuerpoCrudo). Rechaza cualquier webhook cuya firma no coincida — sin esto, cualquiera podría inyectarte resultados falsos.

Node.js — verificar firma
const crypto = require("node:crypto");
function verificar(sigHeader, cuerpoCrudo, secreto) {
  const [, t, v1] = /t=(\d+),v1=([0-9a-f]+)/.exec(sigHeader) ?? [];
  if (!t) return false;
  const esperado = crypto.createHmac("sha256", secreto).update(t + "." + cuerpoCrudo).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(esperado), Buffer.from(v1));
}
  • Reintentos: si tu servidor no responde 2xx, reintentamos a 1 s, 10 s, 1 min y 10 min. Después de eso, recupera el resultado con GET /api/v1/jobs/{id}.
  • Idempotencia: deduplica con event_id — un mismo evento puede llegar más de una vez.
  • Responde rápido: regresa el 200 de inmediato y procesa después; cortamos la conexión a los 10 s.
¿Cuándo usar cada modo? Poco volumen o scripts simples → síncrono (nada cambia). Ráfagas, carteras completas o documentos lentos (IMSS, Infonavit, CFDI masivo) → async: true. El webhook te ahorra el polling; el polling siempre está ahí de respaldo.
Primeros pasos

Modo prueba (sandbox) Disponible

El modo prueba te permite construir tu integración sin credenciales reales del contribuyente y sin tocar los portales del gobierno: con una llave sk_test_…, ciertos RFCs de prueba devuelven respuestas predefinidas — al instante y de forma determinista — para que puedas probar todos los caminos (éxito y cada error) antes de pasar a producción.

¿Por qué importa? Resuelve el problema del huevo y la gallina: un cliente no entrega su CIEC/e.firma hasta ver el software funcionando, pero no puedes construir el software sin credenciales para probar. Con los RFCs de prueba avanzas tu integración primero y pides las credenciales reales después.

Cada RFC de prueba disparará un escenario distinto:

RFC de prueba (ejemplo)Respuesta simulada
DEMO010101AA1200 Documento de prueba (camino feliz).
DEMO020202BB2422 credencial — credencial incorrecta.
DEMO030303CC3422 sin_medios_contacto (IMSS).
DEMO040404DD4502 tecnico — para probar tus reintentos.
EFOS010101AA1200 (EFOS) Simula un RFC en lista 69-B definitivo.
Cualquier otro RFC válido200 Éxito — camino feliz, igual que DEMO010101AA1.
  • El PDF de prueba también llega binario (Content-Type: application/pdf), igual que en producción — tu código de descarga se prueba tal cual.
  • El sandbox no aplica plan_gate ni el padrón de RFCs: puedes probar todos los servicios y RFCs sin restricciones. El único límite del modo prueba es la tasa: 10 req/min (ver Planes y límites).
Cómo activarlo. Genera una llave sk_test_… en el panel de desarrolladores y úsala igual que la de producción. Las llamadas con llave de prueba no tocan los portales del gobierno ni consumen trámites reales.