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:
- 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
- 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
- Sube el certificado
.p12de firma con su contraseña. Se cifra en reposo y solo se descifra en memoria en el momento de firmar. → Certificados de firma - Emite.
POST /comprobantes/facturaresponde 202 con elcomprobanteIdy la clave de acceso; firmar y transmitir ocurre en segundo plano. MandaIdempotency-Keyy un reintento nunca duplica ni quema otro secuencial. → Comprobantes - Espera la resolución. Polling de
GET /comprobantes/:idcada ~3 s hastaAUTORIZADO,RECHAZADOoDEVUELTA(el SRI suele resolver en 5-30 s), o registra un webhook y te avisamos sin que preguntes. → Webhooks - Entrega el resultado.
GET /comprobantes/:id/ridey/xmldevuelven 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,lectory/owebhooks(default:emisor), pero nunca owner/admin — no puede gestionar usuarios, otras keys ni la configuración de la cuenta.webhookses 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 deGET /auth/mis-cuentas). - Un refresh token no sirve como access token (401).
- Ante un
401por expiración: llamar a/auth/refreshy 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/secuencialeshace lo mismo y se mantiene hasta el 2027-06-30 (responde con headersDeprecationySunset). UsaPATCH: 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": [] }
validoes la estructura y el dígito verificador;encontrado, que consta en el catastro. Una entrada malformada respondevalido: false, nunca 400.agenteRetencionte avisa de que esa contraparte va a retenerte — dato del catastro que nadie te da antes de emitir. También llegancontribuyenteEspecial,tipoContribuyentey 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: truesi además venció su frescura). Sin copia:degradado: trueconmotivo. Nunca 404 ni 503. advertenciastrae las señales de riesgo: RUC suspendido, contribuyente fantasma o transacciones inexistentes.- Accesible con API key (roles
emisor,contadorolector).
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 reutilizarpedido-8841en 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 }] }
estadopor í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-Keydel lote (máx. 190 caracteres) la clave de cada elemento se deriva comoheader:indice; si un elemento trae su propiaidempotencyKey, 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) —
infoLiquidacioncon proveedor (tipoIdentificacionProveedor,razonSocialProveedor,identificacionProveedor), totales ypagos[];detalles[]. - Nota de Crédito (04) —
infoNotaCredito: comprador + documento modificado (codDocModificado:"01",numDocModificado:"001-001-000000001",fechaEmisionDocSustento:"2026-06-01"),totalSinImpuestos,valorModificacion,totalConImpuestos[](sintarifa),motivo;detalles[]. - Nota de Débito (05) —
infoNotaDebito: comprador + documento modificadoimpuestos[]+valorTotal;motivos[]({razon, valor}), sin detalles.
- Retención ATS (07) —
infoCompRetencion(sujeto retenido,periodoFiscal:"mm/aaaa",parteRel),docsSustento[]connumDocSustentode 15 dígitos sin guiones,impuestosDocSustento[],retenciones[](codigo1=Renta/2=IVA,codigoRetencion,porcentajeRetener,valorRetenido) ypagos[]. - Guía de Remisión (06) —
infoGuiaRemision(transportista,placa,dirPartida,fechaIniTransporte/fechaFinTransporte),destinatarios[]con susdetalles[]. 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.devueltoyf104.listo(el borrador del F104 del período se marcó listo: llega con las cifras congeladas y el vencimiento). - Default del alta: sin
eventoste 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 existieraf104.listono lo recibe: actívalo con unPATCHsi 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_facturacalcula 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_facturare-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 yconsultar_reglas_sri(tablas oficiales de tarifas, formas de pago e identificaciones).anular_comprobantemarca 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?
- Crea tu cuenta gratis — incluye 10 comprobantes/mes.
- Configura tu emisor y sube tu certificado
.p12(puedes hacerlo desde el panel o por API). - Prueba el flujo completo en el ambiente de Pruebas del SRI.
- 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.