Contenido de esta página

¿Prefieres una referencia interactiva? Explora la referencia OpenAPI (todos los endpoints con esquemas y ejemplos) o descarga el spec OpenAPI 3.1 para generar tu SDK, o importa la colección Postman con los flujos esenciales listos. ¿Prefieres no escribir código? Usa el servidor MCP con tu asistente de IA.

API de Contadeo™ — Documentación para integradores

Emite comprobantes electrónicos autorizados por el SRI desde tu propio sistema (ERP, punto de venta, e-commerce o software contable) con una API REST. Tú envías el JSON; nosotros generamos el XML, lo firmamos con el certificado del emisor, lo transmitimos al SRI con reintentos automáticos y te devolvemos el comprobante autorizado con su RIDE (PDF) y XML.


Descripción general

  • Base URL: https://contadeo.com/api
  • Todos los cuerpos son JSON (Content-Type: application/json).
  • Autenticación con API key (Authorization: Bearer cdo_…) para integraciones, o JWT de usuario para el flujo del panel.
  • Empieza gratis: crea una cuenta (10 comprobantes/mes sin costo) y prueba todo el flujo en el ambiente de Pruebas del SRI antes de pasar a Producción.

Por API se emiten los seis comprobantes del SRI: factura (01), liquidación de compra (03), nota de crédito (04), nota de débito (05), guía de remisión (06) y comprobante de retención (07) — uno por request, o hasta 100 facturas en un lote.

Los ejemplos usan $API y $TOKEN:

API=https://contadeo.com/api
TOKEN="<accessToken>"

La integración, de punta a punta

Seis pasos desde cero hasta el PDF en manos del comprador:

  1. Autentícate. Crea una API key en el panel (Configuración → API) y mándala en cada request: no expira ni necesita refresh. → Autenticación
  2. Registra el emisor. RUC y régimen de la empresa, su establecimiento y su punto de emisión. Si el RUC ya facturaba con otro sistema, sincroniza el secuencial de una vez. → Organización
  3. Sube el certificado .p12 de firma con su contraseña. Se cifra en reposo y solo se descifra en memoria en el momento de firmar. → Certificados de firma
  4. Emite. POST /comprobantes/factura responde 202 con el comprobanteId y la clave de acceso; firmar y transmitir ocurre en segundo plano. Manda Idempotency-Key y un reintento nunca duplica ni quema otro secuencial. → Comprobantes
  5. Espera la resolución. Polling de GET /comprobantes/:id cada ~3 s hasta AUTORIZADO, RECHAZADO o DEVUELTA (el SRI suele resolver en 5-30 s), o registra un webhook y te avisamos sin que preguntes. → Webhooks
  6. Entrega el resultado. GET /comprobantes/:id/ride y /xml devuelven URLs prefirmadas del PDF y del XML autorizado. Si mandas el correo del comprador, el RIDE le llega solo. → Notificación por email

Lo que no tienes que programar: el armado del XML de la ficha técnica vigente, la firma XAdES-BES, los reintentos ante el SRI, la contingencia cuando el servicio está caído y el cuadre de impuestos (se valida antes de enviar y devuelve 400 con el error de negocio, no un rechazo del SRI tres minutos después).

Pruebas y Producción

Cada cuenta emite en un ambiente y se cambia con un solo campo: PATCH /tenants/current con {"ambienteActivo": 2} (1 = Pruebas, 2 = Producción). En Pruebas el SRI autoriza igual, pero los comprobantes no tienen validez tributaria y no gastan el cupo de tu plan (van contra 500/mes aparte). Prueba ahí el flujo entero —con tu .p12 real— antes de pasar a Producción.

Esta documentación en Markdown

Cada sección de esta página se copia o se descarga en Markdown desde su título, y el documento entero está en contadeo.com/desarrolladores.md. Pégaselo a tu asistente de IA junto con el spec OpenAPI y tiene el contexto completo para escribir la integración. ¿Prefieres que el asistente facture él mismo? Eso es el servidor MCP.


1. Autenticación

Hay dos formas de autenticarse. Para integraciones de sistemas (ERP, POS, e-commerce) recomendamos API keys; el flujo JWT es el que usa el panel web.

API keys (recomendado para integraciones)

Se crean en el panel: Configuración → API (rol owner o admin). La key se muestra una sola vez al crearla — guárdala en tu gestor de secretos.

curl $API/comprobantes -H "authorization: Bearer cdo_tu_api_key"
  • Se envía igual que un token: Authorization: Bearer cdo_....
  • No expira ni necesita refresh — ideal para servidores.
  • Permisos limitados por diseño: una key puede tener los roles emisor, contador, lector y/o webhooks (default: emisor), pero nunca owner/admin — no puede gestionar usuarios, otras keys ni la configuración de la cuenta. webhooks es opt-in y habilita gestionar los webhooks de la cuenta (ver la sección Webhooks).
  • Revocación instantánea desde el panel (las integraciones que la usen reciben 401 de inmediato).
  • Hasta 5 keys activas por cuenta (una por sistema integrado, recomendado).

JWT de usuario (el flujo del panel)

JWT Bearer. El access token dura 15 minutos; el refresh token 7 días. Todas las rutas exigen Authorization: Bearer <accessToken> salvo auth/login, auth/refresh, health/* y tenants/register.

Método Ruta Descripción
POST /auth/login Inicia sesión. Devuelve ambos tokens.
POST /auth/refresh Rota el par de tokens con un refresh token válido.
POST /auth/password Cambia la contraseña propia: {actual, nueva} (≥ 8 chars). 204.

El cambio de contraseña revoca los refresh tokens emitidos antes del cambio (401 Sesión revocada al intentar refrescar); hay que volver a iniciar sesión.

curl -X POST $API/auth/login -H 'content-type: application/json' \
  -d '{"email":"owner@miempresa.ec","password":"********"}'
# → { "accessToken": "...", "refreshToken": "..." }

curl -X POST $API/auth/refresh -H 'content-type: application/json' \
  -d '{"refreshToken":"..."}'
# → { "accessToken": "...", "refreshToken": "..." }   (par nuevo)
  • El login no elige empresa: entra a la del último acceso. Con varias cuentas, cámbiate con POST /auth/cambiar-cuenta ({tenantId}, tomado de GET /auth/mis-cuentas).
  • Un refresh token no sirve como access token (401).
  • Ante un 401 por expiración: llamar a /auth/refresh y reintentar.

Roles

Cada usuario tiene roles por cuenta (en el JWT): owner > admin > emisor > contador / lector. Cada endpoint lista sus roles permitidos. Para integraciones máquina-a-máquina recomendamos crear un usuario dedicado con rol emisor.

Formato de errores

// Error genérico
{ "statusCode": 404, "message": "Emisor no encontrado" }

// Validación de negocio pre-firma (evita rechazos del SRI)
{
  "mensaje": "Comprobante inválido",
  "problemas": [
    { "codigo": "IDENTIFICACION_INVALIDA", "campo": "identificacionComprador",
      "severidad": "error", "mensaje": "Identificación inválida..." }
  ]
}
HTTP Significado
400 Cuerpo inválido o validación de negocio fallida (problemas[])
401 Token ausente/inválido/expirado
403 Rol insuficiente
402 Cupo mensual del plan agotado (upgrade: true en el cuerpo) — ver precios
404 Recurso inexistente (o de otra cuenta — aislamiento multi-tenant)
409 Conflicto de unicidad (RUC/código/identificación duplicados)
429 Rate-limit del registro (5 altas por IP cada 10 min)

Referencia OpenAPI

Además de esta guía narrativa, la API publica su especificación OpenAPI 3.1: referencia interactiva (todos los endpoints con esquemas, parámetros y errores) y el spec descargable para generar SDKs (openapi-generator, orval, etc.).

Versionado y compatibilidad

  • La URL base actual es la v1 de la API (sin prefijo de versión). Toda respuesta incluye el header X-API-Version (fecha de la versión de la superficie, p. ej. 2026-06).
  • Los cambios aditivos (campos nuevos en respuestas, endpoints nuevos, parámetros opcionales) pueden ocurrir en cualquier momento: tu integración debe tolerar campos desconocidos.
  • Los cambios incompatibles (renombrar/eliminar campos o endpoints, cambiar códigos de estado) se anuncian en esta página con mínimo 90 días de aviso y el endpoint antiguo se mantiene durante la ventana de deprecación.

2. Onboarding y cuenta

Método Ruta Roles Descripción
POST /tenants/register público Crea la cuenta + usuario owner (plan free). Devuelve tokens.
GET /tenants/current cualquiera Cuenta del token (incluye plan y planExpiraAt).
GET /tenants/current/uso-plan cualquiera El plan que rige la emisión y su consumo: {regimen, plan, limiteComprobantes, usadosEsteMes, limiteUsuarios, usuarios, planExpiraAt, tiposPermitidos, propio}. En una empresa patrocinada (regimen: 'pool') el nivel superior es el plan del patrocinador — el mismo que aplica el servidor — y lo contratado por la empresa va en propio.
PATCH /tenants/current owner, admin Edita nombre, ambienteActivo (1=Pruebas, 2=Producción).

El registro exige ruc (con dígito verificador válido), declaraTitularidadRuc: true, aceptaPrivacidad: true y aceptaTerminos: true (consentimiento LOPDP del Aviso de Privacidad) — 400 si falta cualquiera. El slug lo genera el sistema: no se pide ni se acepta del cliente.

curl -X POST $API/tenants/register -H 'content-type: application/json' \
  -d '{"nombre":"Mi Empresa","email":"yo@empresa.ec","password":"secreta123",
       "ruc":"0912345675001","declaraTitularidadRuc":true,
       "aceptaPrivacidad":true,"aceptaTerminos":true}'

# Cambiar a Producción (¡los comprobantes pasan a tener validez tributaria!)
curl -X PATCH $API/tenants/current -H "authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' -d '{"ambienteActivo":2}'

Equipo (usuarios de la cuenta)

Método Ruta Roles Descripción
GET /equipo todos Miembros de la cuenta con sus roles.
POST /equipo owner, admin Alta: {email, nombre, password, roles[]}.
PATCH /equipo/:membershipId owner, admin Cambia roles[].
DELETE /equipo/:membershipId owner Quita la membresía. 204.

Reglas: roles válidos owner, admin, emisor, contador, lector; un admin no puede otorgar owner ni gestionar a un owner; la cuenta debe conservar al menos un owner activo. El alta respeta el límite de usuarios del plan — 400 al alcanzarlo.


3. Organización: emisores, establecimientos, puntos

Método Ruta Roles
GET /emisores todos
GET /emisores/:id todos
POST /emisores owner, admin
PATCH /emisores/:id owner, admin
DELETE /emisores/:id owner (409 si ya emitió comprobantes)
GET /emisores/:emisorId/establecimientos todos
POST /emisores/:emisorId/establecimientos owner, admin
PATCH /establecimientos/:id owner, admin
DELETE /establecimientos/:id owner, admin
GET /establecimientos/:estabId/puntos-emision todos
POST /establecimientos/:estabId/puntos-emision owner, admin
PATCH /puntos-emision/:id owner, admin
DELETE /puntos-emision/:id owner, admin
GET /emisores/:emisorId/secuenciales todos
PATCH /emisores/:emisorId/secuenciales owner, admin

Secuenciales (migración desde otro sistema)

Si el RUC ya emitió comprobantes con otro facturador, el SRI devolverá el error 45 («secuencial registrado») hasta alcanzar el último número usado. Contadeo se autorecupera (reemite con el siguiente secuencial), pero puedes sincronizar el contador de una vez:

curl -X PATCH $API/emisores/$EMISOR_ID/secuenciales \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"tipoComprobante":"01","establecimiento":"001","puntoEmision":"001",
       "ambiente":2,"ultimoNumero":4582}'
# ultimoNumero = el último YA USADO; el siguiente comprobante será el 4583.
# 400 si intentas bajar de un secuencial ya emitido en Contadeo (duplicados).

Deprecado: PUT /emisores/:emisorId/secuenciales hace lo mismo y se mantiene hasta el 2027-06-30 (responde con headers Deprecation y Sunset). Usa PATCH: el body es parcial, no un reemplazo del recurso.

curl -X POST $API/emisores -H "authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -d '{"ruc":"0912345675001","razonSocial":"MI EMPRESA S.A.",
       "dirMatriz":"Av. Principal 123, Guayaquil",
       "obligadoContabilidad":false,"regimen":"GENERAL"}'
# regimen: GENERAL | RIMPE_EMPRENDEDOR | RIMPE_NEGOCIO_POPULAR
# El RUC se valida (dígito verificador) → 400 si es inválido.

curl -X POST $API/emisores/$EMISOR_ID/establecimientos \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"codigo":"001","direccion":"Matriz centro","nombre":"Matriz"}'
# codigo: exactamente 3 dígitos. Igual para puntos-emision.

4. Certificados de firma (.p12)

Método Ruta Roles
GET /certificados?emisorId= todos (sin material secreto)
POST /certificados owner, admin
curl -X POST $API/certificados -H "authorization: Bearer $TOKEN" \
  -H 'content-type: application/json' \
  -d "{\"emisorId\":\"$EMISOR_ID\",
       \"p12Base64\":\"$(base64 -w0 certificado.p12)\",
       \"passphrase\":\"<contraseña del .p12>\",\"alias\":\"firma 2026\",
       \"declaraTitularidadFirma\":true}"
# → { "id": "<certificadoId>", "subject": "...", "notAfter": "2029-..." }

El .p12 y su contraseña se cifran con envelope AES-256-GCM; solo se descifran en memoria al momento de firmar. El GET nunca devuelve secretos.

5. Catálogos: productos y clientes

Método Ruta Roles
GET / POST /productos?q=&limit= todos / owner, admin, emisor
PATCH / DELETE /productos/:id owner, admin, emisor / owner, admin
GET / POST /clientes?q=&limit= todos / owner, admin, emisor
PATCH / DELETE /clientes/:id owner, admin, emisor / owner, admin
GET /clientes/export.csv todos (descarga CSV; LOPDP: portabilidad)
GET /clientes/lookup/:identificacion?tipo=04|05 owner, admin, emisor, contador
PUT /clientes/por-identificacion/:tipo/:identificacion owner, admin, emisor
GET /sri/catalogos todos

GET /sri/catalogos devuelve las tablas de referencia del SRI con las que valida el backend (tarifa general de IVA vigente, tarifas por código, formas de pago, tipos de identificación/comprobante, regla de consumidor final). Consúmelas desde tu integración en vez de hardcodearlas: cuando una tarifa cambie (como el IVA 12%→15% de 2024), tu sistema seguirá cuadrando.

Productos: codigoPrincipal único por cuenta (409), precioUnitario numérico, tarifaCodigo ("4"=IVA 15%, "0"=0%, "7"=exento, "6"=no objeto). El DELETE es lógico. Clientes: tipoIdentificacion+identificacion únicos por cuenta (409).

En ambos listados, ?q= busca por texto (clientes: razón social, identificación, email; productos: nombre y códigos) y ?limit= acota la respuesta (máx. 200). Sin limit se devuelve el catálogo completo — envíalo siempre desde integraciones.

Prellenado del comprador por RUC/cédula

GET /clientes/lookup/:identificacion?tipo=04|05 busca primero en tu directorio de clientes (devuelve también email/teléfono) y, si no está, en el catastro público del SRI — ideal para autocompletar los datos del comprador en tu ERP antes de emitir. Para cédula (tipo=05) consulta el RUC de persona natural (cédula+001).

curl "$API/clientes/lookup/1790016919001?tipo=04" \
  -H "authorization: Bearer $TOKEN"
# → { "fuente": "sri", "razonSocial": "CORPORACION FAVORITA C.A.",
#     "direccion": "...", "estadoSri": "ACTIVO", "regimen": "GENERAL",
#     "advertencias": ["..."] }   // advertencias: RUC suspendido/fantasma
Error Significado
400 Identificación con dígito verificador inválido o tipo no consultable
404 Sin datos (ni en tu directorio ni en el SRI)
503 Catastro del SRI no disponible — trátalo como "sin prellenado"

Validación de RUC (catastro del SRI)

GET /sri/ruc/:ruc valida cualquier RUC contra el catastro público del SRI y devuelve siempre 200 con un veredicto — pensado para integraciones: verificar a un cliente o proveedor antes de facturar, sin manejar errores.

curl "$API/sri/ruc/1790016919001" -H "authorization: Bearer $TOKEN"
# → { "ruc": "1790016919001", "valido": true, "encontrado": true,
#     "fuente": "sri",
#     "contribuyente": { "razonSocial": "CORPORACION FAVORITA C.A.",
#       "estado": "ACTIVO", "regimen": "GENERAL", "regimenSri": "GENERAL",
#       "obligadoContabilidad": true, "contribuyenteEspecial": true,
#       "agenteRetencion": true, "actividadEconomica": "VENTA AL POR..." },
#     "advertencias": [] }
  • valido es la estructura y el dígito verificador; encontrado, que consta en el catastro. Una entrada malformada responde valido: false, nunca 400.
  • agenteRetencion te avisa de que esa contraparte va a retenerte — dato del catastro que nadie te da antes de emitir. También llegan contribuyenteEspecial, tipoContribuyente y la actividad económica (la descripción del catastro, no un código CIIU).
  • Resiliente: caché de 24 horas y, si el SRI se cae, respondemos con la última copia guardada (hasta 7 días, fuente: "cache"; degradado: true si además venció su frescura). Sin copia: degradado: true con motivo. Nunca 404 ni 503.
  • advertencias trae las señales de riesgo: RUC suspendido, contribuyente fantasma o transacciones inexistentes.
  • Accesible con API key (roles emisor, contador o lector).

Upsert por clave natural

PUT /clientes/por-identificacion/:tipo/:identificacion crea o actualiza el cliente con esa identificación ("recordar cliente"). Solo pisa los campos que llegan con valor — un upsert sin email no borra el email guardado. Ideal tras emitir: guarda los datos del comprador para autocompletarlos la próxima vez.

curl -X PUT $API/clientes/por-identificacion/04/1790016919001 \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' \
  -d '{"razonSocial":"CORPORACION FAVORITA C.A.","email":"pagos@favorita.ec"}'

6. Comprobantes (emisión)

La emisión es asíncrona: el POST valida, reserva el secuencial atómico, genera la clave de acceso y encola la firma/transmisión. Responde 202:

{ "comprobanteId": "uuid", "claveAcceso": "49 dígitos", "estado": "BORRADOR" }

Idempotencia (recomendada siempre). Manda el header Idempotency-Key con un identificador tuyo (el ID del pedido, por ejemplo): si el request se corta y reintentas con la misma key, recibes el MISMO comprobante (reutilizado: true) en vez de crear un duplicado y quemar otro secuencial.

  • Alcance: la clave es única por (cuenta, emisor, tipo de comprobante), así que reutilizar pedido-8841 en la factura y en su nota de crédito no colisiona.
  • Sin caducidad: la clave vive con el comprobante. Un reintento de hace un mes sigue devolviendo el original — no hay ventana de 24 horas como en otros proveedores.
  • Misma clave con cuerpo distinto → 422: el payload se huella (SHA-256); si la clave llega con otro contenido, la API rechaza en vez de devolver un comprobante que no corresponde.
  • Máximo 200 caracteres (en el header de lote, 190; ver abajo).
curl -X POST https://contadeo.com/api/comprobantes/factura \
  -H "Authorization: Bearer cdo_..." \
  -H "Idempotency-Key: pedido-8841" \
  -d @factura.json

Lote. POST /comprobantes/lote acepta hasta 100 facturas por request (body de hasta 2 MB, solo en esta ruta) con semántica parcial: cada factura se valida por separado y una inválida se reporta con su índice sin frenar a las demás. Dos errores sí detienen el lote — 402 (cupo del plan agotado) y 403 (sin permiso sobre el emisor) — porque afectarían igual a todas las restantes, que quedan en NO_INTENTADA. Corrige y reenvía el MISMO lote: la idempotencia hace el reintento seguro.

La respuesta resume y detalla por ítem:

{ "total": 100, "encoladas": 97, "fallidas": 2, "noIntentadas": 1,
  "resultados": [{ "indice": 0, "referencia": "pedido-8841",
    "estado": "BORRADOR", "comprobanteId": "uuid",
    "claveAcceso": "49 dígitos", "reutilizado": false, "error": null }] }
  • estado por ítem: BORRADOR (encolada), ERROR (falló esta), NO_INTENTADA (el lote se detuvo antes de intentarla).
  • referencia: tu identificador por factura (máx. 120 caracteres); vuelve tal cual, para casar la respuesta con tus pedidos.
  • Idempotencia: con el header Idempotency-Key del lote (máx. 190 caracteres) la clave de cada elemento se deriva como header:indice; si un elemento trae su propia idempotencyKey, esa manda. Sin header no se inventa ninguna.
  • El lote se encola con prioridad menor que la emisión individual: tus emisiones sueltas no esperan detrás de un lote de 100.
Método Ruta codDoc Roles
POST /comprobantes/factura 01 owner, admin, emisor
POST /comprobantes/liquidacion 03 owner, admin, emisor
POST /comprobantes/nota-credito 04 owner, admin, emisor
POST /comprobantes/nota-debito 05 owner, admin, emisor
POST /comprobantes/retencion 07 owner, admin, emisor
POST /comprobantes/guia-remision 06 owner, admin, emisor
GET /comprobantes?estado=&emisorId=&limit= todos
GET /comprobantes/export.csv?estado=&emisorId=&desde=&hasta= todos (CSV, máx. 10 000 filas)
GET /comprobantes/:id todos
GET /comprobantes/:id/ride todos (URL prefirmada del PDF)
GET /comprobantes/:id/xml todos (URL prefirmada del XML autorizado)
POST /comprobantes/:id/anular owner, admin, emisor
POST /comprobantes/:id/reenviar-email owner, admin, emisor (202)

Anular: solo sobre AUTORIZADO; marca el estado interno ANULADO. Aplica las reglas de la Resolución NAC-DGERCGC25-00000017 (vigente desde enero 2026): anulable solo hasta el día 7 (inclusive) del mes siguiente a la emisión, y nunca sobre comprobantes a consumidor final (9999999999999) — 400 en ambos casos. No tramita la anulación ante el SRI: el trámite formal se hace en el portal SRI en línea (el emisor solicita, el receptor acepta). Reenviar email: solo AUTORIZADO con RIDE; cuerpo opcional {para} — sin él, usa el email del campo adicional del XML (400 si no hay).

Campos comunes de todo POST de emisión:

{ "emisorId": "uuid", "certificadoId": "uuid",
  "establecimiento": "001", "puntoEmision": "001" }

La emisión aplica el plan de la cuenta antes de reservar el secuencial (ver precios): tipo no incluido en el plan → 400 (campo tipoComprobante); cupo mensual agotado → 402 con {mensaje, problemas[], upgrade: true}.

La fechaEmision la fija el servidor (hora de Ecuador) — no se envía. Excepción: la guía de remisión recibe fechaIniTransporte/fechaFinTransporte.

Estados del comprobante

BORRADOR → FIRMADO → ENVIADO → AUTORIZADO ✓
                   ↘ DEVUELTA (recepción rechazó)   RECHAZADO ✗ (autorización)
        (SRI caído) ↘ CONTINGENCIA → reenvío automático → ENVIADO → ...

Hacer polling de GET /comprobantes/:id cada ~3 s hasta estado terminal (AUTORIZADO, RECHAZADO, DEVUELTA). El SRI suele resolver en 5–30 s.

Cuando el comprobante termina en DEVUELTA o RECHAZADO, tanto GET /comprobantes/:id como el listado GET /comprobantes incluyen mensajesSri: los mensajes literales del SRI, con la forma [{identificador, tipo, mensaje, informacionAdicional}]. Úsalos para mostrar la razón al usuario (p. ej. [45] ERROR SECUENCIAL REGISTRADO).

Flujo completo (ejemplo: factura)

# 1) Emitir (10.00 + 15% IVA = 11.50)
RES=$(curl -s -X POST $API/comprobantes/factura \
  -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{
  "emisorId":"'$EMISOR_ID'","certificadoId":"'$CERT_ID'",
  "establecimiento":"001","puntoEmision":"001",
  "infoFactura":{
    "tipoIdentificacionComprador":"05",
    "razonSocialComprador":"JUAN PEREZ","identificacionComprador":"1745678902",
    "obligadoContabilidad":"NO",
    "totalSinImpuestos":10,"totalDescuento":0,
    "totalConImpuestos":[{"codigo":"2","codigoPorcentaje":"4",
      "baseImponible":10,"tarifa":15,"valor":1.5}],
    "importeTotal":11.5,"pagos":[{"formaPago":"01","total":11.5}]},
  "detalles":[{"descripcion":"Producto","cantidad":1,"precioUnitario":10,
    "descuento":0,"precioTotalSinImpuesto":10,
    "impuestos":[{"codigo":"2","codigoPorcentaje":"4","tarifa":15,
      "baseImponible":10,"valor":1.5}]}]
}')
ID=$(echo $RES | jq -r .comprobanteId)

# 2) Poll hasta resolución
curl -s $API/comprobantes/$ID -H "authorization: Bearer $TOKEN"
# → { "estado": "AUTORIZADO", "claveAcceso": "...", "numeroAutorizacion": "...",
#     "mensajesSri": null }   // en DEVUELTA/RECHAZADO trae los mensajes del SRI

# 3) Descargar RIDE (PDF) y XML autorizado (URLs prefirmadas, expiran en 1 h)
curl -s $API/comprobantes/$ID/ride -H "authorization: Bearer $TOKEN"  # {"url": ...}
curl -s $API/comprobantes/$ID/xml  -H "authorization: Bearer $TOKEN"  # {"url": ...}

Cuerpos por tipo (campos propios)

  • Factura (01)infoFactura (comprador, totales, pagos[]), detalles[].
  • Liquidación de Compra (03)infoLiquidacion con proveedor (tipoIdentificacionProveedor, razonSocialProveedor, identificacionProveedor), totales y pagos[]; detalles[].
  • Nota de Crédito (04)infoNotaCredito: comprador + documento modificado (codDocModificado:"01", numDocModificado:"001-001-000000001", fechaEmisionDocSustento:"2026-06-01"), totalSinImpuestos, valorModificacion, totalConImpuestos[] (sin tarifa), motivo; detalles[].
  • Nota de Débito (05)infoNotaDebito: comprador + documento modificado
    • impuestos[] + valorTotal; motivos[] ({razon, valor}), sin detalles.
  • Retención ATS (07)infoCompRetencion (sujeto retenido, periodoFiscal:"mm/aaaa", parteRel), docsSustento[] con numDocSustento de 15 dígitos sin guiones, impuestosDocSustento[], retenciones[] (codigo 1=Renta/2=IVA, codigoRetencion, porcentajeRetener, valorRetenido) y pagos[].
  • Guía de Remisión (06)infoGuiaRemision (transportista, placa, dirPartida, fechaIniTransporte/fechaFinTransporte), destinatarios[] con sus detalles[]. Sin montos.

Validaciones de negocio (devuelven 400 antes de llegar al SRI)

Regla Error SRI que previene
Dígito verificador de cédula/RUC 62
Cuadre de totales (líneas, impuestos, importe) 52
Fecha futura / extemporánea 65
Consumidor final (07) con total > $50 69
Clave de acceso (módulo 11) 39

Notas: en retención el código de retención debe coincidir con su tarifa de la tabla del SRI (p. ej. 312 → 2.0%), o el SRI rechaza con error 52. IVA 15% = codigo:"2", codigoPorcentaje:"4".


7. Operación y monitoreo

Método Ruta Roles Descripción
GET /reportes/ventas?emisorId=&desde=&hasta= todos Agregados del período: conteo por estado, total $ autorizado, desglose por tipo y por mes.
GET /admin/colas owner, admin Conteos por estado de las colas de emisión.
POST /admin/colas/:nombre/reintentar owner, admin Reencola los fallidos.
GET /health/live público Proceso vivo.
GET /health/ready público Plataforma operativa.
GET /health/sri público Alcanzabilidad del WS del SRI.

8. Webhooks

La alternativa al polling: registra un endpoint HTTPS y te avisamos cuando un comprobante llega a estado final.

Método Ruta Roles Descripción
GET /webhooks owner, admin, webhooks Endpoints registrados.
POST /webhooks owner, admin, webhooks Alta: {url, eventos?, descripcion?}. El secret (whsec_…) viaja solo en esta respuesta.
PATCH /webhooks/:id owner, admin, webhooks Pausa o reanuda: {activo}.
DELETE /webhooks/:id owner, admin, webhooks Baja inmediata.
POST /webhooks/:id/rotar-secreto owner, admin, webhooks Nuevo secreto; el anterior deja de firmar al instante.
GET /webhooks/entregas?limit= owner, admin, webhooks Bitácora de entregas (estado, intentos, código HTTP, último error).
  • Eventos: comprobante.autorizado, comprobante.rechazado, comprobante.devuelto y f104.listo (el borrador del F104 del período se marcó listo: llega con las cifras congeladas y el vencimiento).
  • Default del alta: sin eventos te suscribes a todo el catálogo VIGENTE al crear el webhook (el array se materializa en ese momento). Por eso un webhook guardado antes de que existiera f104.listo no lo recibe: actívalo con un PATCH si lo quieres. Si solo te interesan los estados de comprobante, decláralos explícitos.
  • Gestión por API key: requiere una key con el rol webhooks (opt-in al crearla). También se gestionan desde el panel: Configuración → Equipo → Webhooks.
  • Reintentos: 8 intentos con backoff exponencial si tu servidor no responde 2xx. Máximo 5 endpoints por cuenta. Solo HTTPS.

Verificación de la firma. Cada entrega llega con X-Contadeo-Signature: t=<timestamp>,v1=<hex>, donde hex = HMAC_SHA256(secret, "<timestamp>.<cuerpo crudo>"). Verifica sobre el cuerpo crudo (sin re-serializar) y en tiempo constante:

import { createHmac, timingSafeEqual } from "node:crypto";

function verificar(secret, firma, cuerpoCrudo) {
  const { t, v1 } = Object.fromEntries(
    firma.split(",").map((p) => p.split("=")),
  );
  const esperado = createHmac("sha256", secret)
    .update(`${t}.${cuerpoCrudo}`)
    .digest("hex");
  return timingSafeEqual(Buffer.from(v1, "hex"), Buffer.from(esperado, "hex"));
}

El SDK (contadeo-sdk) trae verificarFirmaWebhook() ya hecha.

Límites de uso

Límite Valor
Global 300 requests/min por cuenta (no por IP)
Emisión individual 60/min
Lote 10/min (hasta 100 facturas cada uno)
Ambiente de Pruebas 500 comprobantes/mes (no gastan tu cupo)

Al superarlos recibes 429; espera y reintenta (con Idempotency-Key el reintento es seguro).

9. Notificación por email al receptor

Si el comprobante incluye en infoAdicional un campo cuyo nombre contenga email/correo, al autorizarse se envía automáticamente el RIDE (PDF) + XML al receptor:

"infoAdicional": [{ "nombre": "Email", "valor": "cliente@correo.com" }]

10. Buzón de compras (recepción por correo)

Cada cuenta tiene una dirección de buzón (<token>@buzon.contadeo.com, con un token opaco y rotable). Reenvía ahí el correo con el que tu proveedor te mandó su factura y el XML adjunto entra solo al módulo Compras: el emisor se reconoce por el RUC receptor del comprobante (por eso un correo reenviado por cualquiera no puede meterte compras ajenas), la clave de acceso deduplica, y los PDF sin XML quedan guardados para revisión en la bandeja.

Método Ruta Roles Descripción
GET /buzon owner, admin, contador La dirección del buzón (se crea al primer uso) y si está habilitado.
POST /buzon/rotar owner, admin Token nuevo; la dirección anterior muere al instante.
GET /buzon/mensajes todos La bandeja: cada correo con su saldo por adjunto (compra creada, duplicado, PDF a revisión, error con motivo).

Quien reenvía un correo no ve ninguna respuesta: la bandeja es la respuesta. El cuerpo del correo no se guarda (solo remitente, asunto y el resultado de cada adjunto).


Conecta tu asistente de IA (MCP)

¿Usas Claude u otro asistente compatible con MCP? Con el servidor contadeo-mcp tu asistente factura por ti: "emite una factura de 2 horas de consultoría a $75 para Juan Pérez, pago por transferencia, y mándale el PDF".

{
  "mcpServers": {
    "contadeo": {
      "command": "npx",
      "args": ["-y", "contadeo-mcp"],
      "env": { "CONTADEO_API_KEY": "cdo_tu_api_key" }
    }
  }
}

La matemática tributaria no la hace la IA — la hace el servidor, con las reglas oficiales del SRI embebidas:

  • preparar_factura calcula líneas, IVA por tarifa y totales con redondeo oficial, valida el dígito verificador de cédulas/RUC y la regla de consumidor final (máx. $50) — y devuelve un resumen que el asistente debe confirmar contigo antes de emitir.
  • emitir_factura re-valida el cuadre y rechaza payloads calculados a mano.
  • Catálogos: buscar y crear clientes y productos conversacionalmente ("regístrame estos 20 clientes").
  • esperar_autorizacion (polling al SRI), descarga de RIDE/XML, reporte de ventas y consultar_reglas_sri (tablas oficiales de tarifas, formas de pago e identificaciones).
  • anular_comprobante marca como anulada una factura autorizada, y por ser una acción sensible el asistente la confirma contigo antes de llamarla. Es el registro interno: el trámite formal sigue haciéndose en el portal del SRI. Fuera del plazo legal o a consumidor final no se anula — se reversa con una nota de crédito.

Autentica con una API key dedicada con rol emisor (Configuración → API) y prueba primero con tu cuenta en ambiente de Pruebas del SRI. El paquete incluye además un Agent Skill para Claude con el flujo completo y el manejo de errores del SRI.

Guía completa, ejemplos y preguntas frecuentes en contadeo.com/mcp.


¿Listo para integrar?

  1. Crea tu cuenta gratis — incluye 10 comprobantes/mes.
  2. Configura tu emisor y sube tu certificado .p12 (puedes hacerlo desde el panel o por API).
  3. Prueba el flujo completo en el ambiente de Pruebas del SRI.
  4. Cambia a Producción cuando estés listo (ambienteActivo: 2).

SDK oficial para TypeScript/Node: contadeo-sdk (npm install contadeo-sdk) — emisión individual y por lote, espera de autorización, descargas y verificación de firma de webhooks en tiempo constante. Para otros lenguajes, la spec OpenAPI trae los payloads de emisión completamente tipados: cualquier generador produce un cliente utilizable.

¿Dudas o volúmenes altos? Escríbenos a soporte@contadeo.com o desde el panel (Tickets) — el plan Empresa incluye soporte prioritario para integraciones.