Referencia de la API · Facturaencr
Bienvenido a la API de Facturación Electrónica de Costa Rica
Esta API le permite emitir comprobantes electrónicos ante el Ministerio de Hacienda desde cualquier sistema: un ERP, un punto de venta, un e-commerce o un script. Usted envía JSON plano; nosotros generamos el XML v4.4, lo validamos contra los esquemas oficiales, lo firmamos con XAdES-EPES, lo enviamos a Hacienda, guardamos la respuesta durante los cinco años que exige la ley y le avisamos por webhook cuando haya veredicto.
No necesita conocer el XSD, ni la especificación de firma digital, ni la API de TRIBU-CR. Necesita saber qué está vendiendo y a quién.
Los datos de los ejemplos son ficticios. Cédulas (
3101000000,100000002…), nombres, correos, claves, identificadores y códigos CABYS (2999999999007…) existen solo para mostrar el formato. Use los suyos: la cédula de su comercio y de sus clientes, y el CABYS real de cada producto, que se busca conGET /catalogs/cabys. Un CABYS de ejemplo copiado tal cual lo rechaza Hacienda con-400.
Qué cubre
Los ocho tipos de comprobante del reglamento vigente, más el Mensaje Receptor:
| Tipo | Comprobante | Endpoint |
|---|---|---|
01 |
Factura Electrónica | POST /documents/factura |
02 |
Nota de Débito | POST /documents/nota-debito |
03 |
Nota de Crédito | POST /documents/nota-credito |
04 |
Tiquete Electrónico | POST /documents/tiquete |
05 06 07 |
Mensaje Receptor (aceptación / parcial / rechazo) | POST /documents/mensaje-receptor |
08 |
Factura Electrónica de Compra | POST /documents/factura-compra |
09 |
Factura Electrónica de Exportación | POST /documents/factura-exportacion |
10 |
Recibo Electrónico de Pago | POST /documents/recibo-pago |
Cumplimiento normativo
La plataforma implementa la versión 4.4 de los comprobantes electrónicos, obligatoria desde el 1.º de septiembre de 2025:
- Reglamento de Comprobantes Electrónicos — Decreto Ejecutivo N.º 41820-H
- Resolución MH-DGT-RES-0027-2024 y Decreto N.º 44739-H
- Anexos y Estructuras v4.4 de la Dirección General de Tributación
- Esquemas XSD v4.4 oficiales publicados por la DGT
Los catálogos que aparecen en esta guía (impuestos, tarifas, condición de venta, medios de pago, referencias, exoneraciones, otros cargos) son los del anexo oficial, corregidos contra el comportamiento real del validador de Hacienda. Donde el XSD publicado y la emisión real no coinciden, esta guía documenta la emisión real y lo señala explícitamente.
Actualización del Anexo v4.4: obligatoria desde el 1 de noviembre de 2026
Hacienda actualizó el Anexo y Estructuras v4.4 (bitácora de ajustes al 22/04/2026). Los sistemas de emisión deben soportar los cambios a partir del 1 de noviembre de 2026, y se pueden usar desde ya. Esta API ya los soporta todos, verificados emitiendo contra Hacienda:
| Cambio | Qué significa | Dónde se explica |
|---|---|---|
| Cédulas jurídicas alfanuméricas | La cédula puede traer letras, con la misma longitud | Cédulas alfanuméricas |
Códigos de referencia 13, 14 y 15 |
Anular, corregir monto o sustituir por error material. El 13 y el 14 se declaran en el período del comprobante original |
Códigos de referencia |
Código 16 |
Sustituir un comprobante que Hacienda rechazó. Va con tipoDocumento 10 |
Sustituir un comprobante rechazado |
Código 17 |
Pago a comprobante electrónico, solo en el Recibo de Pago. La API lo pone por usted | Recibo Electrónico de Pago, en Emisión de comprobantes |
Código 12, nombre ajustado |
«Nota de crédito financiera por exoneración posterior a la facturación» | Códigos de referencia |
tipoDocumento 19 y 20 |
Referenciar una Factura de Exportación (19) o un Recibo de Pago (20) |
Tipo de documento de referencia |
Si su integración no usa estos códigos, no tiene que cambiar nada: los que ya usaba siguen igual.
Primeros pasos
Cuatro pasos desde cero hasta su primera factura aceptada.
Paso 1 — Cree su cuenta de integrador
Regístrese en el portal (/portal/signup). La cuenta queda activa de inmediato en modo
trial, con acceso completo a sandbox.
Paso 2 — Genere su API Key
Desde el portal, cree una llave. Recibirá dos valores:
X-API-Key: efk_EJEMPLO7NO7USAR7EN7PRODUCCION
X-API-Secret: efs_EjemploNoUsarEnProduccion-SuValorRealEsDistinto
El secret se muestra una sola vez. Guárdelo en su gestor de secretos: no hay forma de recuperarlo, solo de rotar la llave.
Verifique que funciona antes de seguir:
curl -X POST https://api.facturaencr.com/v2/efactura/auth/verify \
-H "X-API-Key: $EFACTURA_KEY" \
-H "X-API-Secret: $EFACTURA_SECRET"
{
"ok": true,
"integrator": { "id": "64f0000000000000000000c1", "name": "Su Empresa S.A.", "status": "trial", "defaultEnvironment": "sandbox" },
"apiKey": {
"keyId": "efk_EJEMPLO7NO7USAR7EN7PRODUCCION",
"alias": "produccion-erp",
"scopes": ["documents:write", "documents:read", "certificates:manage", "webhooks:manage", "account:read"],
"environment": "sandbox",
"expiresAt": null
}
}
Fíjese en apiKey.environment: ese campo decide si sus comprobantes tienen valor
fiscal. Vea Ambientes.
Paso 3 — Suba el certificado del comercio
Firmar y enviar son dos cosas distintas, y cada una pide sus propias credenciales: el
.p12 firma el comprobante, las credenciales de TRIBU-CR lo transmiten a Hacienda. Por eso
van los seis campos, todos obligatorios:
| Qué | Campo | Para qué sirve |
|---|---|---|
Certificado .p12 del BCCR |
p12 (archivo, máx 64 KB) |
firmar el comprobante |
PIN del .p12 |
p12Password |
abrir esa llave |
| Usuario de TRIBU-CR | haciendaUsername |
transmitir a Hacienda |
| Contraseña de TRIBU-CR | haciendaPassword |
transmitir a Hacienda |
| Etiqueta para reconocerlo | alias |
identificarlo en su listado |
| Datos del emisor | emisor |
cédula, actividad y ubicación del comercio |
Opcionales: environment (sandbox por defecto) y haciendaClientId.
El PIN del .p12 no es la contraseña de TRIBU-CR. Son dos secretos separados, se
confunden seguido, y mandar uno en lugar del otro falla sin decir cuál de los dos era.
curl -X POST https://api.facturaencr.com/v2/efactura/certificates \
-H "X-API-Key: $EFACTURA_KEY" \
-H "X-API-Secret: $EFACTURA_SECRET" \
-F "p12=@/ruta/certificado.p12" \
-F "p12Password=1234" \
-F "haciendaUsername=cpj-3-101-000000@comprobanteselectronicos.go.cr" \
-F "haciendaPassword=PasswordDelATV" \
-F "alias=Comercio Ejemplo (producción)" \
-F "environment=production" \
-F 'emisor={"tipoIdentificacion":"02","numeroIdentificacion":"3101000000","nombre":"COMERCIO DE EJEMPLO SOCIEDAD ANONIMA","codigoActividad":["1071.9"],"correoElectronico":"facturacion@ejemplo.cr","telefono":{"codigoPais":"506","numTelefono":"71725455"},"ubicacion":{"provincia":"2","canton":"01","distrito":"01","otrasSenas":"Guadalupe, contiguo a la pulpería"}}'
La llave privada se cifra en reposo y no se devuelve por ningún endpoint.
¿Todavía no tiene el certificado de sandbox? No lo espere para empezar. Su cuenta ya puede emitir en sandbox con el emisor de pruebas de la plataforma —
emisorLegalId: "EMISORPRUEBA"— sin subir nada. Vea Probar sin certificado.
Conviene que la ubicación coincida con el padrón.
provincia,cantonydistritodeberían ser los que el contribuyente tiene registrados en Tributación. Si no coinciden, Hacienda devuelve el aviso -37. No bloquea la emisión: medido sobre 426 rechazos, el -37 nunca fue el único motivo, y el 98 % de las veces salió en sandbox, que lo agrega de acompañante. Si lo ve, busque el OTRO código del mensaje: ese es el que rechaza.
Paso 4 — Emita
curl -X POST https://api.facturaencr.com/v2/efactura/documents/factura \
-H "X-API-Key: $EFACTURA_KEY" \
-H "X-API-Secret: $EFACTURA_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: pedido-2026-07-000123" \
-d '{
"emisorLegalId": "3101000000",
"condicionVenta": "01",
"medioPago": ["01"],
"receptor": {
"tipoIdentificacion": "01",
"numeroIdentificacion": "100000002",
"nombre": "Juan Ejemplo",
"correoElectronico": "juan@cliente.com"
},
"detalle": [
{
"cantidad": 1,
"unidadMedida": "Unid",
"codigoCabys": "2999999999007",
"detalle": "Pan pita 400g",
"precioUnitario": 1000,
"impuesto": [ { "codigo": "01", "codigoTarifa": "08", "tarifa": 13 } ]
}
]
}'
{
"documentId": "64f0000000000000000000a1",
"clave": "50624072600310100000000100001010000000866142351111",
"consecutivo": "00100001010000000866",
"status": "queued",
"environment": "sandbox",
"estimatedReadyAt": "2026-07-25T01:07:58.112Z"
}
Listo. El envío a Hacienda sigue en segundo plano; el veredicto llega por webhook o
consultando GET /documents/{documentId}.
Autenticación
Todas las peticiones llevan dos cabeceras, ambas obligatorias:
X-API-Key: efk_...
X-API-Secret: efs_...
No se usa Authorization: Bearer. Si falta cualquiera de las dos, la respuesta es
401 unauthorized.
Scopes
Cada llave lleva permisos explícitos. Un endpoint invocado sin el scope necesario devuelve
403 forbidden_scope.
| Scope | Habilita |
|---|---|
documents:write |
Emitir comprobantes, reenviar, refrescar estado |
documents:read |
Consultar documentos, descargar XML/PDF, buscar en CABYS, consultar contribuyentes |
certificates:manage |
Subir, listar, probar y eliminar certificados |
webhooks:manage |
Crear, editar y probar endpoints de webhook |
account:read |
Consultar la cuenta y el consumo |
Cree llaves con el mínimo necesario: la del servidor de facturación no necesita
certificates:manage.
Aislamiento de datos
Cada llave solo ve los recursos de las cuentas a las que pertenece —la suya, y las que la hayan autorizado explícitamente (ver abajo)—. No existe forma de leer documentos, certificados ni webhooks de una cuenta que no lo haya autorizado; el filtro se aplica en todos los endpoints sin excepción.
Llave de desarrollador: una llave, varias cuentas
Si usted escribe un sistema y lo instala en varios comercios, no necesita una llave por comercio. La integración multicuenta se habilita cuenta por cuenta: escríbanos y se la activamos (no tiene costo). Al activarla recibe un código en su portal, en Desarrollador → Mis clientes. Cada comercio lo agrega desde su propio portal, en Usuarios → Desarrolladores, y elige con qué cédulas puede usted emitir.
Desde ese momento su llave —una sola— emite por todas esas cuentas.
Quién paga. El comercio, siempre. Cada comprobante debita la cartera de la cuenta dueña del emisor, avanza su tramo y aparece en su consumo. A usted no se le cobra nada por operar cuentas ajenas, y no necesita un plan de API propio para hacerlo.
Cómo se sabe de qué cuenta es cada llamada. Por la cédula del emisor, que ya viaja en toda emisión:
POST /documents/factura
{ "emisorLegalId": "3101000000", "receptor": { ... }, "detalle": [ ... ] }
Para llamadas sin cuerpo (consultas, subir un .p12) use la cabecera X-Emisor con la
cédula. Si dos de sus clientes declararan la misma cédula, la API responde 409 emisor_ambiguo en vez de adivinar: agregue X-Cuenta con el id de la cuenta correcta.
Adivinar ahí significaría emitir un documento fiscal en la cuenta equivocada.
| Situación | Respuesta |
|---|---|
| Ninguna cuenta lo ha agregado | 403 sin_clientes |
| La cuenta le quitó el acceso | 403 sin_permiso_en_cuenta |
| Esa cédula no es de ninguna de sus cuentas | 403 emisor_no_asignado |
| El comercio no le habilitó esa cédula | 403 emisor_fuera_de_alcance |
| El plan de ese comercio es ilimitado | 403 plan_ilimitado_sin_desarrollador |
| Dos clientes con la misma cédula | 409 emisor_ambiguo (mande X-Cuenta) |
| No dijo de qué cuenta es la llamada | 400 falta_cuenta |
Planes ilimitados. Si el plan del comercio es ilimitado, su llave no puede emitir en producción por esa cuenta: el plan cubre el volumen de ese negocio por una mensualidad fija, no el de una cartera de clientes. El comercio sí emite, desde su portal o con su propia llave de API. Sandbox no cambia: puede seguir armando y probando la integración con normalidad, porque las pruebas no consumen el plan.
El comercio manda. Puede quitarle el acceso cuando quiera y surte efecto en la llamada siguiente: su llave sigue viva para sus otros clientes. También decide qué pantallas de su cuenta puede ver usted en el portal.
Rotación
- Cree la llave nueva desde el portal.
- Despliéguela a sus servidores.
- Confirme con
POST /auth/verifyque respondeok: true. - Espere 24–48 h a que no queden peticiones en vuelo con la vieja.
- Revoque la vieja.
Si un secret se filtró, revoque primero y reponga después: un secret comprometido puede emitir comprobantes fiscales a nombre de sus comercios.
Formato de respuestas
Las respuestas exitosas devuelven el recurso directamente, sin envoltorio:
{
"documentId": "64f0000000000000000000a1",
"clave": "50624072600310100000000100001010000000866142351111",
"status": "queued"
}
Los listados devuelven items más la paginación:
{
"items": [ { "documentId": "..." } ],
"page": 1,
"limit": 50,
"total": 1835
}
Errores
Todos los errores comparten forma:
{
"error": "validation_error",
"message": "Payload de Factura inválido",
"details": [
{
"path": "detalle.0",
"msg": "La exoneración excede el IVA de la línea...",
"message": "La exoneración excede el IVA de la línea..."
}
],
"requestId": "req_01HZY8Q4X2K7"
}
Programe contra error, nunca contra message. El texto de message está pensado
para humanos y puede cambiar sin aviso; el código es estable.
Tres detalles que conviene saber de entrada:
-
pathusa notación de puntos con índice numérico —detalle.0.impuesto.1, nodetalle[0].impuesto[1]. Viene vacío ("") cuando la regla que falló es transversal y no pertenece a un campo puntual. -
msgymessagetraen el mismo texto, duplicado por compatibilidad histórica. Leamessage. -
Hay dos convenciones de código de error. Las validaciones de esquema devuelven
validation_erroren minúscula condetails; las reglas de negocio devuelven un código propio en mayúscula y sindetails, por ejemplo:{ "error": "MEDIO_PAGO_MONTO_REQUERIDO", "message": "Con más de un medio de pago, cada medio debe incluir su monto (> 0)." }Su manejador debe contemplar ambas:
detailspuede no existir.
Incluya siempre el requestId cuando abra un ticket: con él ubicamos la petición exacta.
Emisión de comprobantes
El flujo asíncrono
Los POST /documents/* responden 202 Accepted en 1 a 3 segundos —el tiempo de
validar, generar el XML y firmarlo— y el envío a Hacienda sigue en segundo plano.
El veredicto de Hacienda tarda de 5 a 60 segundos más. Esa es la diferencia que hace útil el modelo asíncrono: usted no espera por Hacienda, espera por la firma.
202no significa «aceptado por Hacienda». Significa que recibimos su petición, la validamos, generamos el comprobante y lo firmamos. El veredicto fiscal llega después. No entregue mercadería ni cierre la venta contra un202: hágalo contrastatus: "accepted".
Estados
| Estado | Significado | ¿Qué hago? |
|---|---|---|
pending |
Registrado, aún sin procesar | Esperar |
queued |
En cola de emisión | Esperar |
signing |
Generando el XML y firmándolo | Esperar |
sent |
Entregado a Hacienda, sin veredicto | Esperar |
polling |
Consultando el veredicto a Hacienda | Esperar |
accepted |
Aceptado. Tiene valor fiscal | Entregar, cobrar, archivar |
rejected |
Rechazado. No tiene valor fiscal | Leer haciendaMessage, corregir y emitir uno nuevo |
Solo
acceptedyrejectedson finales. Todo lo demás significa «en vuelo». Escriba su máquina de estados con esa regla —final contra no final— y no con una lista cerrada: si aparece un estado nuevo, su código lo tratará como no final en vez de romperse.
Un rejected no se corrige: el consecutivo se consumió. Se emite un comprobante nuevo
con los datos corregidos.
Punto de venta: no bloquee la caja esperando a Hacienda
El error de diseño más común en un POS es emitir de forma síncrona y dejar al cajero —y a la fila— esperando el veredicto. Hacienda tarda entre 5 y 60 segundos, y si su conexión se cae, espera para siempre.
No hace falta. El 202 ya le devuelve la clave y el consecutivo, que es todo lo
que el tiquete físico necesita llevar impreso. El veredicto fiscal llega después y no
cambia esos dos valores.
Tiene tres formas de conseguir el número, de más rápida a más simple. Elija según cuánto pueda esperar la caja:
A · Numeración propia — 0 ms, sin llamar a nadie. Si el consecutivo lo lleva usted, ya sabe cuál sigue sin preguntarnos nada: imprime al instante y encola la emisión en su propio sistema para enviarla cuando quiera. No hay que configurar nada antes — basta con mandar el número en el payload.
// usted decide el número y lo manda en el payload
{ "consecutivoNumero": "0000004471", "branchCode": "002", "terminalCode": "00007", … }
// → consecutivo 00200007040000004471
Es la opción de menor latencia y la natural si viene de un sistema con numeración propia.
A cambio, usted responde por que no haya saltos ni repeticiones, incluso con varias
cajas a la vez. Se activa con PATCH /emisores/{legalId}/config.
B · Reservar por adelantado — una llamada, sin firmar.
POST /clave/reserve
→ { "clave": "…", "consecutivo": "…", "codigoSeguridad": "…", "expiresAt": "…" }
Saca el número del mismo contador que la emisión normal —así no se producen huecos ni
choques al mezclar reservadas con directas—, arma la clave de 50 completa y la guarda con
un TTL de 24 horas. No toca Hacienda, no firma y no cobra. Al emitir, mande esa
clave en el payload: el comprobante sale con ella y el contador no vuelve a avanzar.
Cada reserva se consume una sola vez; reusarla devuelve
409 reserved_clave_already_consumed.
Reserve de a uno, justo antes de usarlo — no en lote. El contador avanza al reservar, no al emitir. Un número reservado que no se use es un hueco en su numeración que después hay que justificar, y la reserva caduca a las 24 horas. Pedir cien claves al abrir caja y usar sesenta deja cuarenta huecos.
C · Emitir y usar lo que devuelve el 202 — lo más simple.
1. Cobrar
2. POST /documents/tiquete → 202 con clave y consecutivo
3. Imprimir el tiquete con esa clave y ese consecutivo
4. Despedir al cliente
5. …el webhook document.accepted llega después, en segundo plano
En cualquiera de las tres, lo que se imprime no cambia después: la clave y el consecutivo quedan fijos desde el principio y el veredicto de Hacienda no los toca.
Qué hacer si el webhook dice
rejected. La venta ya ocurrió y el tiquete ya se imprimió; eso no se deshace. Se corrige emitiendo de nuevo, y por eso conviene registrar eldocumentIdjunto a la venta en su base: es lo que le permite reconciliar al día siguiente qué se aceptó y qué no. Un rechazo en caja es raro —la mayoría son de configuración del emisor, no de la venta— pero su sistema tiene que tener una respuesta.
Anatomía de una emisión
Todos los comprobantes comparten la misma estructura de raíz.
{
// ── Quién firma. Exactamente UNO de los dos ──
"emisorLegalId": "3101000000", // cédula del comercio (recomendado)
// "certificateId": "64f0000000000000000000b1",
// ── Condiciones comerciales ──
"condicionVenta": "01", // 01 = contado
"medioPago": ["01"], // 01 = efectivo
"currency": "CRC",
// ── A quién ──
"receptor": { "tipoIdentificacion": "01", "numeroIdentificacion": "100000002", "nombre": "Juan Ejemplo" },
// ── Qué ──
"detalle": [ /* líneas */ ],
// ── Opcionales ──
"otrosCargos": [],
"observaciones": "Pedido #4471",
"cajero": "Ana Ejemplo", // quién cobró → sale en el PDF y el tiquete
"vendedor": "Luis Ejemplo" // quién vendió → ídem
}
Tres cosas que no se envían nunca:
- El emisor. Se resuelve del certificado. Enviarlo no tiene efecto.
-
Los totales del resumen.
TotalVentaNeta,TotalImpuesto,TotalComprobantey los subtotales por naturaleza los calcula la plataforma a partir de las líneas. Si los manda, se ignoran. Esto es deliberado: los totales son la causa número uno de rechazo por descuadre, y no hay ninguna razón para que los calcule usted. -
La clave y el consecutivo. Se generan solos, salvo que use reserva previa o el modo
integrator(vea Numeración propia y modo integrator).
emisorLegalId frente a certificateId
| Cuándo usarlo | |
|---|---|
emisorLegalId |
Casi siempre. Usa el certificado activo más reciente de esa cédula. Si el comercio renueva el .p12, su código no cambia. |
certificateId |
Cuando necesita fijar un certificado concreto — por ejemplo, un comercio con certificados de sandbox y producción cargados a la vez. |
Enviar los dos, o ninguno, es 400.
Cuál necesito
| Situación | Comprobante |
|---|---|
| Venta con cliente identificado | Factura (01) |
| Venta a consumidor final que no pide factura | Tiquete (04) |
| Devolver, anular o corregir a la baja | Nota de crédito (03) |
| Cobrar de más, intereses o un cargo omitido | Nota de débito (02) |
| Le compro a alguien no inscrito | Factura de compra (08) |
| Vendo fuera de Costa Rica | Factura de exportación (09) |
| Me pagan una factura a crédito con IVA diferido | Recibo de pago (10) |
| Responder a una factura que me emitieron | Mensaje receptor (05/06/07) |
El payload completo, campo por campo
Los ejemplos de cada endpoint son escenarios reales y compactos. Este es lo contrario: todo lo que se puede enviar en una factura, para que vea el mapa completo. Casi nada de esto es obligatorio.
{
// ── Quién firma — exactamente UNO de los dos ──────────────────────────────
"emisorLegalId": "3101000000", // cédula del comercio (recomendado)
"certificateId": "64f00000…", // o el id de un certificado concreto
"environment": "production", // opcional: fuerza el ambiente del certificado
// ── Numeración — opcional; la plataforma la administra ────────────────────
// Ver «Numeración propia y modo integrator» si la lleva su sistema.
"clave": "506…", // clave pre-reservada (POST /clave/reserve)
"claveIntegrador": "506…", // o la clave de 50 que compuso e imprimió su POS
"branchCode": "001", // sucursal (default: la del emisor)
"terminalCode": "00001", // caja (default: la del emisor)
"consecutivoNumero": "0000000866", // opcional: si lo manda, la numeración es suya
"codigoSeguridad": "42240125", // los 8 finales de la clave; sólo junto al consecutivo
// ── Contingencia — opcional; sólo si la venta fue offline ─────────────────
"fechaEmision": "2026-07-20T09:00:00-06:00",
"situacion": "3", // 1 normal · 2 contingencia · 3 sin internet
// ── Actividad económica ───────────────────────────────────────────────────
"codigoActividad": "1071.9", // del emisor (default: su principal)
"codigoActividadReceptor": "960113", // alternativa a receptor.codigoActividad
// ── Condiciones comerciales ───────────────────────────────────────────────
"condicionVenta": "02",
"condicionVentaOtros": "Permuta de mercadería", // obligatorio si es 99
"plazoCredito": "30", // STRING, en días; si es 02
"medioPago": [{ "tipo": "01", "monto": 6000 }, { "tipo": "02", "monto": 4170 }], // opcional (salvo REP)
"currency": "USD",
"exchangeRate": 512.5, // obligatorio si currency ≠ CRC
// ── A quién ───────────────────────────────────────────────────────────────
"receptor": {
"tipoIdentificacion": "02",
"numeroIdentificacion": "3101000003",
"nombre": "Comercial XYZ S.A.",
"nombreComercial": "Comercial XYZ",
"codigoActividad": "960113",
"correoElectronico": "facturacion@comercialxyz.com",
"telefono": { "codigoPais": "506", "numTelefono": "22221111" },
"ubicacion": {
"provincia": "1", "canton": "01", "distrito": "01",
"barrio": "San Rafael", // v4.4: el NOMBRE, no el código
"otrasSenas": "Edificio Plaza, oficina 5"
},
"otrasSenasExtranjero": "…" // en vez de ubicacion, si es tipo 05
},
// ── Qué se vende ──────────────────────────────────────────────────────────
"detalle": [
{
"codigoCabys": "2999999999005", // 13 dígitos, obligatorio
"cantidad": 3,
"unidadMedida": "Unid",
"unidadMedidaComercial": "Caja 12u",
"detalle": "Pan tostado 200g",
"precioUnitario": 1200, // SIN IVA
"codigoComercial": [{ "tipo": "01", "codigo": "SKU-4471" }],
"tipoTransaccion": "01", // v4.4; default venta normal
"ivaCobradoFabrica": "01", // sólo casos de fábrica/mayorista
"descuento": [{
"montoDescuento": 1000,
"codigoDescuento": "02",
"codigoDescuentoOtro": "…", // si es 99
"naturalezaDescuento": "Promoción de temporada"
}],
"impuesto": [
{ "codigo": "01", "codigoTarifa": "08", "tarifa": 13,
"exoneracion": {
"tipoDocumento": "01",
"numeroDocumento": "AL-2026-00123",
"fechaEmision": "2026-01-10T10:00:00-06:00",
"nombreInstitucion": "01", // el CÓDIGO, no el nombre
"tarifaExonerada": 13, // los PUNTOS de tarifa que se exoneran
"articulo": 8, "inciso": 2 // inciso: 0 si el artículo no tiene incisos
}
},
{ "codigo": "04", // específico, ADEMÁS del IVA
"datosImpuestoEspecifico": {
"cantidadUnidadMedida": 350,
"porcentaje": 5,
"impuestoUnidad": 2,
"volumenUnidadConsumo": 350 // requerido para el código 05
}
}
]
// montoTotal, subtotal, baseImponible, impuestoNeto y montoTotalLinea
// se CALCULAN: no los envíe.
}
],
// ── Cargos, referencias y extras ──────────────────────────────────────────
"otrosCargos": [{
"tipoDocumento": "06", "detalle": "Impuesto de servicio",
"porcentaje": 10, "montoCargo": 1130,
"tipoIdentidadTercero": "02", // si tipoDocumento = 04
"numeroIdentidadTercero": "3101000003",
"nombreTercero": "Transportes de Ejemplo S.A."
}],
"referencia": [{ // obligatoria en NC, ND, FEC y REP
"tipoDocumento": "01",
"numero": "506…", // clave de 50 si el tipo es electrónico
"fechaEmision": "2026-07-25T01:07:52-06:00",
"codigo": "01", // opcional, salvo en NC y ND
"razon": "Anulación por devolución total" // opcional
}],
"observaciones": "Pedido #4471",
"cajero": "Ana Ejemplo", // no fiscal: se imprime en PDF y tiquete
"vendedor": "Luis Ejemplo", // ídem (ver «Cajero y vendedor»)
"totalIVADevuelto": "auto", // sólo salud privada pagada con tarjeta
"pdfBase64": "JVBERi0x…" // su propio PDF para el correo
}
Lo que nunca se envía: el emisor (sale del certificado) y los totales del resumen
(TotalVentaNeta, TotalImpuesto, TotalComprobante y los subtotales por naturaleza), que
la plataforma calcula desde las líneas.
La única excepción es totalIVADevuelto, porque no se deriva de las líneas: depende de
cómo pagó el paciente. Ver IVA devuelto.
Un campo en blanco es un campo que no mandó
No hace falta que limpie el JSON antes de enviarlo. Si arma el payload desde su base de
datos, sus columnas vacías llegan como "" o null, y la API las trata igual que si la
clave no viniera:
{
"condicionVenta": "01",
"plazoCredito": "", // contado: no aplica → se ignora
"detalle": [{
"ivaCobradoFabrica": null, // no aplica → se ignora
"unidadMedidaComercial": "",
"precioUnitario": 1500,
"impuesto": [{ "codigo": "01", "codigoTarifa": "10", "tarifa": 0 }]
}]
}
Vale para null, para el string vacío y para el que solo tiene espacios. Un objeto que
queda sin nada adentro desaparece entero ("telefono": { "codigoPais": "", "numTelefono": "" }
no es un teléfono a medias), y los elementos vacíos salen de las listas.
El 0 no es un hueco. "tarifa": 0 y "montoDescuento": 0 son datos y se respetan tal
cual; lo mismo false. Solo se descarta lo que está en blanco.
Lo obligatorio sigue siendo obligatorio. Mandar "condicionVenta": "" es no mandarla:
la respuesta es 400 diciendo que falta, no que está vacía.
Lo que NO tiene que mandar
Estos campos son opcionales en el XSD v4.4 y la API no se los pide. Se documentan porque antes sí se exigían y puede que su integración los esté rellenando de más:
| Campo | Cuándo sí es obligatorio |
|---|---|
medioPago |
Solo en el Recibo Electrónico de Pago. En una venta a crédito no hay medio de pago todavía: omítalo en vez de inventar uno. No ponemos un valor por defecto — cómo le pagaron no nos consta. |
referencia[].numero |
Cuando el documento referenciado es un comprobante electrónico (01–04, 08–20); ahí va la clave completa de 50 y se valida el largo, que es lo que evita un rechazo -80 con el consecutivo quemado. Para un contrato, una nota de despacho, un procedimiento u "otros" (05, 06, 07, 99) es opcional y admite el número que traiga el papel. |
referencia[].codigo |
Solo en la Nota de Crédito, la Nota de Débito y la Factura de Compra. En una nota dice si se anula (01) o se corrige el monto (02), y de eso depende el saldo del comprobante original: no es un dato que podamos poner por usted. En el resto de comprobantes, si no lo envía se escribe 04 (Referencia a otro documento). |
referencia[].razon |
Nunca. Si no la envía se escribe "Referencia a otro documento". |
La dirección: entera o nada
ubicacion es opcional — un comprobante sin ella se acepta sin problema. Pero si la
envía, otrasSenas es obligatoria y de mínimo 5 caracteres: así lo exige el XSD, y un
<Ubicacion> sin ella se rechaza con -1 (cvc-minLength-valid … minLength '5' for type 'OtrasSenasUbicacionType').
Por eso la API la pide cuando manda el bloque, y el error se lo dice: si no tiene la
dirección exacta, omita ubicacion entera. Verificado emitiendo: sin ubicacion →
aceptado; con ubicacion y sin otrasSenas → rechazado; con las dos → aceptado.
Si el comprobante llega por otra vía (portal o punto de venta) con la ubicación a medias,
se emite sin la dirección —un comprobante válido sin un dato opcional le gana a uno
inválido— y recibe el aviso ubicacion_receptor_omitida en warnings.
⚠️ Ojo con estos dos: el XSD dice que son opcionales y Hacienda los exige igual. Los marca
minOccurs="0", pero un comprobante conInformacionReferenciasinCodigoo sinRazonse rechaza con -131 («El nodo denominado 'Codigo' es un campo obligatorio»). Verificado emitiendo. Por eso la API los rellena en vez de pedírselos: usted no los manda, pero salen en el XML. Si le importa qué dicen, mándelos — lo suyo nunca se pisa.
Los totales no se envían — y si los envía, se los verificamos
Los totales del resumen y los de cada línea los calcula la plataforma con la fórmula de Hacienda. Hay dos que sí puede mandar, y sirven para contrastar su cálculo con el nuestro:
| Campo de línea | Qué pasa si lo envía |
|---|---|
montoTotal |
Se compara con cantidad × precioUnitario. Si no coincide, 400 con los dos números. |
subtotal |
Se compara con cantidad × precioUnitario − descuentos. Igual. |
baseImponible, impuestoNeto, montoTotalLinea |
Se descartan y le llega un aviso totales_de_linea_calculados en warnings: los calcula el motor de impuestos (dependen de exoneraciones, IVA cobrado en fábrica e impuestos por unidad). No bloquean. |
Antes se aceptaban los cinco y se descartaban en silencio: usted creía haber fijado un número que nunca llegaba al XML.
El Recibo Electrónico de Pago es la excepción: su detalle no lleva cantidad ni precio unitario, así que ahí
subtotales un dato suyo y es obligatorio.
Pago mixto: la suma tiene que cuadrar
Con más de un medioPago, la suma de los montos debe igualar el total del comprobante.
Hacienda lo cruza con una tolerancia de ₡1 y rechaza con -493; por eso la API corta
antes de firmar, cuando la diferencia es mayor. Si saliera, perdería el consecutivo.
Con totalIVADevuelto el objetivo es el total ya rebajado por la devolución, y como la
devolución se prorratea según lo pagado con tarjeta, la cuenta es circular: fije el monto de
la tarjeta y despeje el otro medio contra el total final. El mensaje de error se lo dice.
Factura Electrónica · tipo 01
Para ventas a un receptor identificado. El receptor es obligatorio.
POST /documents/factura
Para servicios de salud privados pagados con tarjeta admite totalIVADevuelto. Ver
IVA devuelto.
Tiquete Electrónico · tipo 04
Para venta a consumidor final. El receptor es opcional: si el cliente no pide factura, omítalo por completo.
POST /documents/tiquete
El tiquete no da crédito fiscal al comprador. Si un cliente contribuyente pide factura después de haber recibido un tiquete, no se «convierte»: se emite una nota de crédito que anula el tiquete y luego una factura nueva.
Y si lo envía, la identificación del receptor también es opcional. El XSD marca
Identificacion como opcional dentro del receptor del Tiquete, así que basta el nombre:
"receptor": { "nombre": "CLIENTE DE CONTADO" } // sin cédula: válido en tiquete
La identificación va atada: o manda tipoIdentificacion y numeroIdentificacion, o
no manda ninguno. Uno solo produce un <Identificacion> a medias que Hacienda rechaza, así
que la API lo corta antes con un 400.
Lo mismo aplica a la Nota de Crédito, la Nota de Débito y la Factura de Exportación. En la Factura, la Factura de Compra y el Recibo de Pago la identificación sigue siendo obligatoria: ahí el XSD la exige.
Es el comprobante del consultorio: para servicios de salud privados pagados con tarjeta
lleva totalIVADevuelto, que le reembolsa al paciente el 4 % en el acto. Ver
IVA devuelto.
Nota de Crédito · tipo 03
Resta de un comprobante anterior: devoluciones, anulaciones, correcciones a la baja.
referencia es obligatoria.
POST /documents/nota-credito
Para una devolución parcial, incluya solo las líneas devueltas y use codigo: "02"
(corrige monto).
El receptor es opcional en NC/ND: una nota sobre un tiquete no lo lleva.
Si corrige un comprobante de salud privada, la nota lleva su propio totalIVADevuelto,
calculado sobre sus líneas y no sobre las del original. Ver
IVA devuelto.
Nota de Débito · tipo 02
Suma a un comprobante anterior: intereses, cargos omitidos, correcciones al alza. Misma estructura que la nota de crédito.
POST /documents/nota-debito
También admite totalIVADevuelto, con las mismas reglas que la nota de crédito.
Tipo de documento de referencia
Qué clase de documento está referenciando (referencia[].tipoDocumento). Catálogo de la
Nota 10 del Anexo v4.4:
| Código | Documento |
|---|---|
01 |
Factura electrónica |
02 |
Nota de débito electrónica |
03 |
Nota de crédito electrónica |
04 |
Tiquete electrónico |
05 |
Nota de despacho |
06 |
Contrato |
07 |
Procedimiento |
08 |
Comprobante emitido en contingencia |
09 |
Devolución de mercadería — solo en notas de crédito y débito |
10 |
Comprobante electrónico rechazado por el Ministerio de Hacienda |
11 |
Sustituye factura rechazada por el receptor del comprobante |
12 |
Sustituye factura de exportación |
13 |
Facturación mes vencido |
14 |
Comprobante aportado por contribuyente de Régimen Especial |
15 |
Sustituye una Factura Electrónica de Compra |
16 |
Comprobante de Proveedor No Domiciliado — solo en la Factura de Compra |
17 |
Nota de Crédito a Factura Electrónica de Compra |
18 |
Nota de Débito a Factura Electrónica de Compra |
19 |
Factura Electrónica de Exportación |
20 |
Recibo Electrónico de Pago |
99 |
Otros — requiere tipoDocRefOTRO |
Si integró antes de setiembre de 2026, ojo con el
19y el20. Hubo un período en que el anexo los publicaba pero ningún XSD los tenía, y Hacienda los rechazaba por esquema. Ya republicó los esquemas: ambos funcionan —verificado emitiendo una nota de crédito sobre una Factura de Exportación con19—. El01también se acepta y no pierde información (la clave de 50 lleva embebido el tipo del original), pero lo correcto hoy es el código específico.El
12(«Sustituye factura de exportación») no es el19: es para cuando el comprobante que emite sustituye la exportación por otra, no para referenciarla.⚠️ Una nota sobre una Factura de Exportación tiene que repetir la línea tal cual: mismo
codigoCabysy mismapartidaArancelaria. Si la línea usa un CAByS de mercancía y omite la partida, Hacienda rechaza con -503; si cambia el CAByS, con -509.El
13es para servicios que vencen el último día del mes (servicios públicos y similares, que se facturan en los primeros días del mes siguiente pero cuyo ingreso pertenece al mes anterior). En ese caso,fechaEmisionde la referencia lleva la fecha del período fiscal al que corresponde el ingreso.
Códigos de referencia
Por qué lo está referenciando (referencia[].codigo):
| Código | Motivo |
|---|---|
01 |
Anula Documento de Referencia |
02 |
Corrige monto |
04 |
Referencia a otro documento |
05 |
Sustituye comprobante provisional por contingencia |
06 |
Devolución de mercancía |
07 |
Sustituye comprobante electrónico |
08 |
Factura Endosada |
09 |
Nota de crédito financiera |
10 |
Nota de débito financiera |
11 |
Proveedor No Domiciliado |
12 |
Nota de crédito financiera por exoneración posterior a la facturación |
13 |
Anula documento de referencia por error material |
14 |
Corrige monto por error material |
15 |
Sustituye comprobante electrónico por error material |
16 |
Sustituye comprobante electrónico rechazado — con tipoDocumento 10 |
99 |
Otros — requiere codigoReferenciaOTRO |
El
17(pago a comprobante electrónico) es exclusivo del Recibo de Pago. En una nota de crédito o débito, Hacienda lo rechaza: su enumeración para las notas termina en16.El
13y el14cambian el período. A diferencia del01, el02y el06—que pesan en el mes de la nota—, una nota con código13o14refleja su efecto contable en el período del comprobante que modifica: una nota de setiembre que corrige un error material de agosto se declara en agosto.
Cuándo usar los códigos nuevos
Un error material es una equivocación al emitir: un precio, una cantidad o un dato del cliente mal digitado, o un cálculo mal hecho. La venta en sí no cambió. Si la venta sí cambió (el cliente devolvió producto, se le hizo un descuento después), use los códigos de siempre.
| Situación | Código | En qué mes se declara |
|---|---|---|
| Hay que anular una factura porque se emitió con un error de digitación | 13 |
El de la factura original |
| Hay que anular una factura por otro motivo (venta cancelada, cliente equivocado de operación) | 01 |
El de la nota |
| Un monto quedó mal digitado o mal calculado | 14 |
El de la factura original |
| El monto cambió de verdad (ajuste de precio, descuento posterior) | 02 |
El de la nota |
| Se emite el comprobante correcto que reemplaza al anulado por error material | 15 |
El del comprobante nuevo |
| Hacienda rechazó el comprobante y se emite uno nuevo | 16 + tipoDocumento 10 |
El del comprobante nuevo |
| El cliente obtuvo una exoneración después de que se le facturó con impuesto | 12 |
El de la nota |
Ejemplo: una factura de agosto salió con ₡100 000 en vez de ₡10 000 y se corrige en
setiembre. Con 14, la nota baja el IVA de agosto, y setiembre no se mueve. Con 02,
la baja quedaría en setiembre. El borrador del D-150 del portal ya aplica esta regla. Si
lleva la contabilidad en su propio sistema, tiene que aplicarla usted.
El 16 no se usa en una nota de crédito: un comprobante rechazado no existe fiscalmente y
no hay nada que anular. Vea
Sustituir un comprobante rechazado.
No confunda el
13decodigo(anula por error material) con el13detipoDocumento(facturación mes vencido): son dos catálogos distintos.
Los nombres de 01 a 16 son los de la Nota 9 del Anexo y Estructuras v4.4 (pp. 72-73),
el catálogo vigente publicado por Hacienda.
No existe el código
03. Era "corrige monto" en la v4.3; en la v4.4 ese significado pasó al02.Los códigos
13a16ya tienen nombre oficial. Los publicó la Nota 9 del Anexo y Estructuras v4.4 (p. 73), en la bitácora de ajustes al 22/04/2026 —el mismo documento que renombró el12—. Hubo un período en que el validador de Hacienda los aceptaba y la tabla publicada llegaba solo hasta el12; esta doc los dejaba entonces sin descripción. Ya no hace falta: los cuatro están en la tabla de arriba.El código
17no es válido en notas de crédito ni de débito. Hacienda lo rechaza (cvc-enumeration-valid); pertenece únicamente al Recibo Electrónico de Pago.Y una advertencia práctica: Hacienda no valida el contenido de
InformacionReferencia. Puede referenciar una clave inexistente y el comprobante se acepta igual. La consistencia es responsabilidad suya; la plataforma reconcilia la factura original cuando la nota se acepta.
Factura Electrónica de Compra · tipo 08
Para comprarle a un proveedor no inscrito como contribuyente. Aquí los roles se
invierten: usted emite el comprobante de una compra suya, y el receptor del payload es
el vendedor.
POST /documents/factura-compra
Dos particularidades:
receptor.codigoActividades obligatorio (el del vendedor).-
referenciaes obligatoria, pero a diferencia de NC/ND,numerono tiene que ser una clave de 50: puede ser el número físico del documento del proveedor.
La FEC está excluida del envío automático de correo: el receptor tiene que firmarla, y mandarla antes genera confusión. El reenvío manual sigue disponible.
Factura Electrónica de Exportación · tipo 09
Para ventas fuera de Costa Rica. Receptor opcional; cada línea admite
partidaArancelaria de exactamente 12 dígitos.
POST /documents/factura-exportacion
Una exportación es exenta: use codigoTarifa: "10". Si envía receptor extranjero, su
dirección va en otrasSenasExtranjero, no en ubicacion.
Recibo Electrónico de Pago · tipo 10
Documenta el pago de una factura a crédito con IVA diferido. Su estructura es
reducida: no lleva codigoActividad, el receptor es obligatorio y las líneas no llevan
CABYS ni unidad de medida.
POST /documents/recibo-pago
condicionVenta solo admite 09 u 11 en el REP. Cualquier otro valor es 400.
En la referencia, codigo y razon son obligatorios de hecho —Hacienda rechaza con
-131 si faltan—, pero la API los deriva por usted: codigo → "17",
razon → "Pago a comprobante electrónico".
Mensaje Receptor · tipos 05, 06, 07
Su respuesta como receptor a una factura que le emitieron. Es lo que determina si puede acreditar el IVA de esa compra.
POST /documents/mensaje-receptor
mensaje |
Significado | Reglas |
|---|---|---|
05 |
Aceptación | detalleMensaje opcional |
06 |
Aceptación parcial | detalleMensaje obligatorio; exige montoTotalImpuesto y que impuestoAcreditar + gastoCosto = montoTotalImpuesto |
07 |
Rechazo | detalleMensaje obligatorio; montoTotalImpuesto prohibido |
condicionImpuesto (catálogo oficial v4.4, nota 18) define el destino del IVA soportado:
| Código | Condición |
|---|---|
01 |
Genera crédito IVA — se acredita en su totalidad |
02 |
Genera crédito parcial |
03 |
Bienes de capital |
04 |
Gasto corriente — no genera crédito |
05 |
Proporcionalidad (prorrata) |
Un Mensaje Receptor solo se envía una vez por comprobante. Un segundo intento devuelve
409 mr_already_sent.
Impuestos y cálculo de totales
Esta es la sección que decide si sus comprobantes se aceptan. Léala completa antes de emitir en producción.
La regla de oro
Toda línea debe declarar un impuesto de la familia IVA — códigos 01, 07 u 08 —
incluso si el producto es exento, no sujeto o de tarifa cero.
Un producto exento no es «una línea sin impuesto»: es una línea con IVA de tarifa exenta.
// Exento: SÍ lleva impuesto, con codigoTarifa 10
"impuesto": [ { "codigo": "01", "codigoTarifa": "10", "tarifa": 0 } ]
Cómo se comporta la API según lo que mande:
| Lo que envía | Qué pasa |
|---|---|
impuesto con IVA (01/07/08) |
Correcto |
impuesto con impuestos pero ninguno de la familia IVA |
400 antes de emitir. No se consume consecutivo. |
impuesto: [] |
Pasa el filtro y Hacienda lo rechaza con -1 (violación del XSD). Consecutivo consumido. |
Sin la clave impuesto |
Igual que el anterior: -1 y consecutivo consumido. |
Las dos últimas filas son una fuga real: la validación local solo revisa arrays que traen
algo. Si su código construye líneas dinámicamente, asegúrese de que nunca emita un
impuesto vacío, porque eso quema numeración.
Estructura del impuesto por línea
Una línea lleva dos cosas distintas:
- El IVA — siempre, sin excepción.
- Un impuesto adicional — opcional: combustible, licor, tabaco, cemento, selectivo.
El adicional no reemplaza al IVA: lo acompaña como otro elemento del array.
"impuesto": [
{ "codigo": "01", "codigoTarifa": "08", "tarifa": 13 }, // IVA
{ "codigo": "02", "tarifa": 10 } // ISC, ad-valorem
]
Catálogo de impuestos
| Código | Impuesto | Familia | Cómo se calcula |
|---|---|---|---|
01 |
Impuesto al Valor Agregado | IVA | Base imponible × tarifa |
02 |
Selectivo de Consumo (ISC) | ad-valorem | Subtotal × tarifa |
03 |
Único a los Combustibles | por unidad · asumido | ₡ por litro |
04 |
Específico de Bebidas Alcohólicas | por unidad · asumido | ₡ por mL de alcohol absoluto |
05 |
Bebidas envasadas sin alcohol y jabones | por unidad · asumido | ₡ por unidad de consumo |
06 |
Productos de Tabaco | por unidad · se cobra | ₡ por unidad |
07 |
IVA (cálculo especial) | IVA | Base imponible × tarifa |
08 |
IVA Régimen de Bienes Usados (Factor) | IVA | Requiere factorIVA; el monto es subtotal × factorIVA |
12 |
Específico al Cemento | por unidad · asumido | ₡ por saco · tarifa obligatoria = 5 |
99 |
Otros | ad-valorem | Requiere codigoTarifaOtro |
No existen los códigos
09,10ni11. Aparecen en documentación de terceros pero no en el catálogo vigente.
Códigos de tarifa del IVA
Obligatorio para la familia IVA (01, 07, 08).
| Código | Tarifa | Nota |
|---|---|---|
01 |
0 % | Artículo 32, num. 1, RLIVA |
02 |
1 % | Reducida |
03 |
2 % | Reducida |
04 |
4 % | Reducida |
05 |
0 % | Transitorio — solo NC/ND |
06 |
4 % | Transitorio — solo NC/ND |
07 |
8 % | Transitorio — solo NC/ND |
08 |
13 % | General |
09 |
0,5 % | Reducida |
10 |
— | Exenta |
11 |
0 % | Sin derecho a crédito (no sujeta) |
Las tarifas transitorias
05,06y07solo valen en notas de crédito y débito. En una factura o tiquete son rechazo seguro de Hacienda (-505). La API las bloquea antes con400, así que no gasta consecutivo — pero conviene saber por qué: para un 8 % en una factura no hay código vigente.
Base imponible
El IVA no se calcula sobre el subtotal, sino sobre la base imponible:
BaseImponible = SubTotal
+ ISC (02)
+ Bebidas Alcohólicas (04)
+ Bebidas Envasadas (05)
+ Cemento (12)
El combustible (03) y el tabaco (06) NO engrosan la base. Equivocarse acá produce
un rechazo -454.
Cobrado frente a asumido
Este es el concepto que más sorprende y el que más rechazos causa.
Los impuestos por unidad de códigos 03, 04, 05 y 12 ya los pagó el productor o
el importador y viajan dentro del precio. El comercio no se los vuelve a cobrar al
cliente: se declaran para trazabilidad, pero no suman al total ni aparecen en el
desglose de impuestos.
El tabaco (06) es la excepción: sí se cobra y sí suma al total, aunque también sea
por unidad.
| Código | Por unidad | ¿Suma al total? |
|---|---|---|
03 combustible |
Sí | No — asumido |
04 alcohol |
Sí | No — asumido |
05 bebidas envasadas |
Sí | No — asumido |
06 tabaco |
Sí | Sí — se cobra |
12 cemento |
Sí | No — asumido |
Fórmulas de los impuestos por unidad
Se declaran en datosImpuestoEspecifico:
| Código | Fórmula |
|---|---|
03 combustible |
cantidadUnidadMedida × impuestoUnidad |
04 alcohol |
proporcion = cantidadUnidadMedida × porcentaje / 100, luego cantidad × proporcion × impuestoUnidad |
05 bebidas envasadas |
cantidad × cantidadUnidadMedida × (impuestoUnidad / volumenUnidadConsumo) |
05 jabón de tocador (CAByS 3532…) |
cantidad × volumenUnidadConsumo × impuestoUnidad |
06 tabaco |
cantidad × cantidadUnidadMedida × impuestoUnidad |
12 cemento |
cantidadUnidadMedida × impuestoUnidad |
Sobre el código 04: grava el alcohol absoluto, no el líquido. Una botella de 750 mL a
12° tiene 90 mL de alcohol puro (750 × 12 / 100). Sí se divide entre 100; no hacerlo
produce -471 o -473. Además el emisor debe tener cargado su registroFiscal8707
(Ley 8707).
Sobre el código 05: exige volumenUnidadConsumo (los mL del envase). Sin él, -459.
Sobre el código 03 (combustibles):
-
cantidadUnidadMedidason litros. Si no la manda, se usa lacantidadde la línea. Vendiendo por galones o por monto («₡20.000 de súper»), mande los litros encantidadUnidadMedida. - El comprobante guarda los litros con 2 decimales (37,456 L → 37,46) y el monto del impuesto se calcula con ese valor, que es el que verifica Hacienda.
- Máximo 99 999,99 L por línea. Una venta más grande se divide en varias líneas.
-
Venta por monto («₡1 000 de diésel» a ₡688/L): la cantidad admite 3 decimales y
1,453 × 688 = 999,664. Mande la cantidad del surtidor, el precio por litro y
montoTotal: 1000. Si la diferencia es de 1 colón o menos (lo que Hacienda admite), el comprobante sale por ₡1 000 y la respuesta trae el avisomonto_total_pactado. -
impuestoUnidad(₡ por litro) es obligatorio y mayor que 0. Solomontono alcanza: Hacienda rechaza con -468. - El combustible es exento de IVA: lleve el IVA con
codigoTarifa10. -
Se puede exonerar, como dice el Anexo: la exoneración va dentro del
03contarifaExonerada, los puntos de tarifa que otorga el documento de exoneración (13, no 100). El monto exonerado lo calculamos:tarifaExonerada × subtotal ÷ 100, y no puede superar el impuesto (400 exoneracion_supera_impuesto). SintarifaExoneradala respuesta es400 exoneracion_requiere_tarifa. Lo exonerado se le resta al impuesto asumido; el total no cambia y la línea sigue exenta. Una exoneración del IVA va en el impuesto01. -
En la factura de compra y la de exportación no existe el impuesto asumido: si manda el
03, se suma al total. -
Autoconsumo (la estación usa su propio combustible):
tipoTransaccion02en la línea. Devolución de litros: nota de crédito con código de referencia06.
Estos datos Hacienda los rechaza siempre, así que la API los corta con 400 antes de
gastar el consecutivo (en factura, tiquete y notas; impuesto_repetido_en_linea, en todo
comprobante):
| Error | Qué pasó | Hacienda |
|---|---|---|
impuesto_unidad_requerido |
03–06 sin impuestoUnidad mayor que 0 |
-468 |
cantidad_unidad_medida_requerida |
03–06 con cantidadUnidadMedida 0 (o una línea con cantidad 0 que no la manda) |
-467 |
cantidad_unidad_medida_excede |
Más de 99 999,99 en cantidadUnidadMedida |
-1 (XSD) |
exoneracion_requiere_tarifa |
Exoneración en 03–06 sin tarifaExonerada (puntos) |
— |
exoneracion_monto_no_coincide |
Exoneración en 03–06 con un montoExoneracion distinto de puntos × subtotal |
— |
exoneracion_supera_impuesto |
Los puntos exonerados dan más que el impuesto (impuestos asumidos: 03, 04, 05, 12) |
-1 |
impuesto_repetido_en_linea |
El mismo código de impuesto dos veces en una línea (cualquier impuesto y comprobante) | -520 |
Hacienda acepta estos casos, así que la API no los bloquea, pero los avisa en warnings:
| Aviso | Qué pasó |
|---|---|
combustible_con_iva |
CAByS de combustible (exento) con IVA mayor que 0 |
combustible_sin_impuesto_03 |
Factura o tiquete de combustible sin el impuesto 03 |
litros_tomados_de_la_cantidad |
El 03 se calculó con la cantidad de una línea que no está en litros (también en galones: impuestoUnidad es por litro) |
monto_total_pactado |
Se emitió el montoTotal que mandó (hasta 1 colón distinto de cantidad × precio) |
impuesto_03_se_cobra |
03 en una factura de compra o de exportación |
"impuesto": [
{ "codigo": "01", "codigoTarifa": "10", "tarifa": 0 },
{ "codigo": "03", "datosImpuestoEspecifico": { "impuestoUnidad": 276.25 } }
]
Ejemplos resueltos
1 · Servicio gravado al 13 %
{
"cantidad": 1,
"unidadMedida": "Sp",
"codigoCabys": "8999999999013",
"detalle": "Servicio de reclutamiento ejecutivo",
"precioUnitario": 100000,
"impuesto": [ { "codigo": "01", "codigoTarifa": "08", "tarifa": 13 } ]
}
Subtotal 100 000 → IVA 13 000 → Total línea 113 000
2 · Mercancía gravada al 13 %
{
"cantidad": 3,
"unidadMedida": "Unid",
"codigoCabys": "2999999999005",
"detalle": "Pan tostado 200g",
"precioUnitario": 1200,
"impuesto": [ { "codigo": "01", "codigoTarifa": "08", "tarifa": 13 } ]
}
Subtotal 3 600 → IVA 468 → Total línea 4 068
3 · Canasta básica al 1 %
{
"cantidad": 2,
"unidadMedida": "Unid",
"codigoCabys": "2999999999007",
"detalle": "Pan pita 400g",
"precioUnitario": 1000,
"impuesto": [ { "codigo": "01", "codigoTarifa": "02", "tarifa": 1 } ]
}
Subtotal 2 000 → IVA 20 → Total línea 2 020
La tarifa correcta de cada producto la sugiere el propio catálogo CABYS: el buscador
devuelve el campo impuesto con la tarifa asociada.
4 · Producto exento
{
"cantidad": 1,
"unidadMedida": "Unid",
"codigoCabys": "2999999999008",
"detalle": "Producto exento",
"precioUnitario": 5000,
"impuesto": [ { "codigo": "01", "codigoTarifa": "10", "tarifa": 0 } ]
}
Subtotal 5 000 → IVA 0 → Total línea 5 000. El monto se declara en TotalExento, no en
TotalGravado.
5 · Línea con descuento
{
"cantidad": 1,
"unidadMedida": "Unid",
"codigoCabys": "2999999999005",
"detalle": "Pan tostado 200g",
"precioUnitario": 10000,
"descuento": [ { "montoDescuento": 1000, "codigoDescuento": "02" } ],
"impuesto": [ { "codigo": "01", "codigoTarifa": "08", "tarifa": 13 } ]
}
El descuento baja la base: 10 000 − 1 000 = 9 000 → IVA 1 170 → Total línea 10 170.
Los códigos de descuento
01(Regalía) y03(Bonificación) exigen que el descuento sea el 100 % de la línea —el bien se entrega gratis— y el emisor asume el IVA. Un «descuento parcial con código 01» no existe: use02(Promoción) o el que corresponda.
6 · Bebida alcohólica
Cerveza de 350 mL a 5°, dos unidades, con impuesto específico de ₡2 por mL de alcohol absoluto:
{
"cantidad": 2,
"unidadMedida": "Unid",
"codigoCabys": "2999999999009",
"detalle": "Cerveza 350ml",
"precioUnitario": 1500,
"impuesto": [
{ "codigo": "01", "codigoTarifa": "08", "tarifa": 13 },
{
"codigo": "04",
"datosImpuestoEspecifico": {
"cantidadUnidadMedida": 350,
"porcentaje": 5,
"impuestoUnidad": 2
}
}
]
}
proporcion = 350 × 5 / 100 = 17,5 mL → monto = 2 × 17,5 × 2 = ₡70. Ese específico
engrosa la base del IVA pero no se le cobra al cliente.
Exoneración
TarifaExonerada no es «el porcentaje de la exoneración»: es la tarifa que se está
exonerando. Hacienda valida:
MontoExoneracion = BaseImponible × TarifaExonerada / 100
Exonerar el 100 % de un IVA del 13 % sobre ₡10 000 son 13 de tarifa y ₡1 300 de monto — no 100. Anexo v4.4, pág. 46: «Debe de indicarse los puntos de la tarifa otorgado de exoneración (…) la tarifa del 13 % se debe reflejar como 13, la del 1 % como 1, o bien la del 0.5 % como 0.5».
Diga cuánto se exonera con UNO de estos tres (al menos uno, mayor que 0):
| campo | qué es |
|---|---|
tarifaExonerada |
recomendado — los puntos de tarifa: 13, 1, 0.5 |
porcentajeExoneracion |
histórico y ambiguo (ver abajo) |
montoExoneracion |
el monto de impuesto, si ya lo tiene calculado |
montoExoneracion ya no es obligatorio: si no lo manda, lo calculamos. Si lo manda, se
respeta el suyo. Si no cuadra la igualdad de arriba: -190.
La API además rechaza con 400 cualquier exoneración que supere el IVA que la línea
genera, con un mensaje que le dice el máximo exacto:
La exoneración excede el IVA de la línea: montoExoneracion=9999 pero el IVA (13%)
sobre la base 1000.00 es 130.00 como máximo.
Una exoneración parcial parte la base: exonerar la mitad de un IVA del 13 % sobre ₡10 000 declara ₡5 000 gravados y ₡5 000 exonerados.
IVA devuelto (servicios de salud privados)
Los servicios de salud privados van con tarifa reducida del 4 % (art. 11, inc. 1, subinc. b de la Ley del IVA). Y el art. 39 del Reglamento (Decreto 41779-H) obliga a reembolsar ese mismo 4 % en el acto cuando el paciente paga con tarjeta de crédito o débito: el paciente termina pagando sólo la base.
El impuesto se factura normal en la línea y la devolución se resta en el resumen:
TotalComprobante = TotalVentaNeta + TotalImpuesto + TotalOtrosCargos − TotalIVADevuelto
Se activa con un campo en la raíz del payload:
{
"condicionVenta": "01",
"medioPago": ["02"], // 02 = tarjeta: es lo que dispara la devolución
"totalIVADevuelto": "auto", // o el monto exacto: 1600
"detalle": [{
"codigoCabys": "9999999999015", // consulta médica → 4 %
"cantidad": 1, "unidadMedida": "Sp",
"detalle": "Consulta de medicina general",
"precioUnitario": 40000,
"impuesto": [{ "codigo": "01", "codigoTarifa": "04", "tarifa": 4 }]
}]
}
Resultado: base ₡40 000 · IVA 4 % ₡1 600 · devuelto −₡1 600 · total a pagar ₡40 000.
| Valor | Qué hace |
|---|---|
"auto" |
Calcula el monto que Hacienda espera. Es lo que debe usar. |
| un número | Ese monto exacto. Sólo si necesita anular nuestro cálculo — se valida igual. |
| ausente | No hay devolución: el 4 % se cobra completo. Con tarjeta no se permite (ver abajo). |
Disponible en Factura, Tiquete, Nota de Crédito y Nota de Débito. Los XSD de la Factura
de Compra, la de Exportación y el Recibo Electrónico de Pago no tienen el campo:
enviarlo ahí devuelve 400.
Con tarjeta es OBLIGATORIO (-523)
Si el comprobante lleva un servicio de salud (CAByS 931…) al 4 % y algún medioPago es
"02", Hacienda exige TotalIVADevuelto y rechaza con -523 si falta. Tampoco
acepta 0. Por eso, en Factura y Tiquete, lo cortamos antes con 400
IVA_DEVUELTO_REQUERIDO: mande "auto".
Dos casos especiales, medidos contra el validador de Hacienda (sandbox, oct-2026):
| Línea de salud | Lo que exige Hacienda | Qué hace "auto" |
|---|---|---|
Regalada (descuento 01 regalía o 03 bonificación) |
Devolver el IVA asumido de la línea (MontoTotal × 4 %), aunque no se haya cobrado. Con 0 rechaza. |
Lo calcula: consulta de ₡30 000 regalada → devuelve ₡1 200 |
Exonerada del todo (tarifaExonerada 4) |
IVA devuelto en 0: con monto rechaza («se debe utilizar únicamente cuando… la tarifa corresponda al 4%»). | Devuelve 0. Si no manda el campo, también sale bien. |
Hacienda RECALCULA el monto
No se limita a aceptar lo que usted mande: recalcula la devolución y la compara. Rechaza
con -496 en dos variantes:
-
«El Total IVA Devuelto no debe de estar presente si no existe Medio de Pago con Tarjeta» — sin un
medioPago"02", el comprobante se cae. -
«El Total IVA Devuelto no concuerda con la suma de impuestos sobre Servicios Médicos pagados con Tarjeta (X)» — con pago mixto, Hacienda prorratea:
TotalIVADevuelto = IVA_al_4% × (Σ montos con medioPago "02") ÷ TotalVentaNeta
Con un solo medio de pago el XML no lleva TotalMedioPago, no hay nada que prorratear,
y va el 4 % completo.
⚠️ En pago mixto hay una vuelta de tuerca: el total del comprobante baja por la devolución, y la devolución depende de cuánto se pagó con tarjeta. Fije el monto de la tarjeta y despeje el resto:
IVA al 4 % = 1 600 · TotalVentaNeta = 50 000 · tarjeta = 41 300
devolución = 1 600 × 41 300 ÷ 50 000 = 1 321,60
total = 50 000 + 2 900 − 1 321,60 = 51 578,40
efectivo = 51 578,40 − 41 300 = 10 278,40
Mande "auto" y esa cuenta la hacemos nosotros; usted sólo tiene que cuadrar los montos
de medioPago contra el total resultante (si no cuadran, el 400 le dice cuál es).
Todo lo que Hacienda valida, lo cortamos antes
Un comprobante rechazado no se corrige ni se reenvía: hay que emitirlo de nuevo con
otro consecutivo, y el rechazado le queda registrado al contribuyente. Por eso estas
comprobaciones son 400 antes de gastar un número de la secuencia:
| Código | Cuándo |
|---|---|
IVA_DEVUELTO_REQUERIDO |
Salud al 4 % pagada con tarjeta sin totalIVADevuelto (Hacienda -523). Mande "auto". |
IVA_DEVUELTO_SIN_TARJETA |
Ningún medioPago es "02". En pago mixto basta con que uno lo sea. |
IVA_DEVUELTO_MONTO_DISTINTO |
El monto no es el que Hacienda recalcula. El mensaje trae el esperado. |
IVA_DEVUELTO_EXCEDE_IVA_AL_4 |
Se devuelve más IVA del cobrado al 4 % (típico: contar las líneas al 13 %). |
IVA_DEVUELTO_SIN_LINEAS_AL_4 |
"auto" no encontró nada al 4 %: CAByS o tarifa equivocados. |
Que los montos por medio no sumen el total ya rebajado por la devolución no bloquea:
sale como aviso medio_pago_suma_distinta (ver Pago mixto).
La condición de venta es la única que queda como aviso, no como bloqueo: Hacienda
acepta la devolución en un comprobante a crédito, así que la emisión sigue y la respuesta
trae un warning. La regla ahí es sólo legal — el art. 39 devuelve en el acto del pago:
{
"documentId": "6a8c...",
"status": "queued",
"warnings": [
{
"code": "iva_devuelto_a_credito",
"message": "Se está devolviendo el IVA en un comprobante a CRÉDITO (condicionVenta 02). La devolución del art. 39 es en el acto del pago con tarjeta; en una venta a crédito no procede hasta que el pago se realice."
}
]
}
Régimen simplificado
Un emisor de régimen simplificado no traslada IVA, así que no hay IVA del 4 % que devolver y el comprobante queda mal emitido. Hacienda no valida esto: le sale aceptado.
Por defecto no lo bloqueamos — se emite exactamente lo que usted envía. Si activa
«Exención automática para Régimen Simplificado» en su cuenta, enviar totalIVADevuelto
desde un emisor simplificado devuelve 422 · iva_devuelto_no_aplica_simplificado.
Moneda extranjera
La devolución viaja en la moneda del documento, no convertida a colones. Una consulta
de US$ 80 al 4 % devuelve 3.20000 y el TipoCambio del resumen no la toca.
Notas de crédito y débito
La nota calcula su devolución sobre sus propias líneas, no sobre las del documento que corrige. Una NC que sólo reversa la consulta médica devuelve el IVA de esa consulta, aunque la factura original tuviera más líneas y una devolución mayor.
⚠️ El prefijo del CAByS no es la regla. Casi todo 931… es salud humana al 4 %, pero
hay excepciones en ambas direcciones:
-
Dentro de
931…hay códigos al 13 %: internamiento, quirófano, UCI, sala de recuperación, sala de observación, consultorio con equipo especializado, ambulancias, autopsia y certificado de defunción. -
Fuera de la salud humana también hay tarifa del 4 %: veterinaria (
835…) y tiquete aéreo (641…,642…). Ahí no hay devolución.
Lea la tarifa del catálogo CAByS código por código —
GET /catalogs/cabys la devuelve con su tarifa— en vez de deducirla del prefijo. Y
recuerde que la actividad económica del emisor debe ser de salud: división 86 de CIIU 4
(8610.0–8690.9) desde la migración del 6 de octubre de 2025.
Régimen del emisor
Cada certificado trae un emisor.regimen detectado automáticamente contra el registro de
Hacienda:
| Régimen | Qué significa |
|---|---|
tradicional |
Traslada IVA con normalidad. |
simplificado |
Por disposición de Hacienda no traslada IVA. |
Por defecto la plataforma no toca sus números. Si usted envía IVA para un emisor de régimen simplificado, el comprobante sale con ese IVA. Hacienda lo acepta —el XML v4.4 no lleva ningún campo de régimen y no cruza la cédula contra el padrón—, así que el cálculo y la responsabilidad fiscal son suyos.
Exención automática (opcional)
Si prefiere que nosotros lo resolvamos, puede activar Exención automática desde su cuenta del portal, en Perfil → Emisión. Con ella encendida, a los emisores en régimen simplificado les reescribimos cada línea a exenta:
// Lo que envía // Lo que se emite
{ "codigo": "01", <CodigoTarifaIVA>10</CodigoTarifaIVA>
"codigoTarifa": "08", <Tarifa>0.00</Tarifa>
"tarifa": 13 } <Monto>0.00000</Monto>
<TotalExento>1000.00000</TotalExento>
Y siempre se lo avisamos en warnings de la respuesta de emisión, con el número de
líneas que tocamos:
{
"documentId": "6a7f...",
"status": "queued",
"warnings": [
{
"code": "simplificado_exento_forzado",
"message": "El emisor está en Régimen Simplificado y tu cuenta tiene activada la exención automática: se reescribieron 2 líneas a EXENTA (impuesto 0).",
"lineas": 2
}
]
}
Si concilia lo que envió contra lo que devolvió la API, revise
warnings. Con la exención automática encendida, para un emisor simplificado esperetotalImpuesto: 0.
Los impuestos que no son IVA nunca se tocan: el impuesto específico a combustibles, bebidas alcohólicas, tabaco o cemento se cobra por unidad y un emisor simplificado los sigue debiendo.
Exoneración
Un emisor de régimen simplificado no puede emitir con exoneración, tenga o no
encendida la exención automática: la API responde 422 exoneracion_no_aplica_simplificado.
No traslada IVA, así que no hay impuesto que exonerar.
Consultarlo antes de tener el certificado
emisor.regimen existe una vez que el certificado está cargado. Para averiguarlo antes
—al dar de alta a un comercio, o para saber qué esperar de un receptor— hay un endpoint
directo contra el registro de Hacienda:
curl "https://api.facturaencr.com/v2/efactura/contribuyentes/3101000001/regimen" \
-H "X-API-Key: sk_live_..." -H "X-API-Secret: ..."
{
"identificacion": "3101000001",
"encontrado": true,
"contribuyente": true,
"regimen": { "codigo": 2, "clave": "simplificado", "descripcion": "Régimen simplificado",
"simplificado": true, "trasladaIva": false }
}
Sin /regimen el mismo endpoint devuelve además la inscripción, la morosidad, la
administración tributaria y las actividades económicas. Lo importante de esa respuesta:
| Campo | Para qué |
|---|---|
regimen.trasladaIva |
false = simplificado: prepare su conciliación para totalImpuesto: 0. |
contribuyente |
false = no inscrito: Hacienda le rechaza lo que emita, y como receptor no acepta Factura Electrónica → emítale un Tiquete (04). |
encontrado |
false = la identificación no está en el registro. Casi siempre es una cédula mal escrita. |
situacion.moroso / omiso |
Informativos. No impiden facturar. |
Tres advertencias sobre cómo leerlo:
-
contribuyente: falseconencontrado: trueno es lo mismo que conencontrado: false. El primero existe pero no está inscrito (el motivo textual de Hacienda viene ensituacion.mensaje); el segundo no existe. contribuyente: nullsignifica no verificable ahora, no "no". No lo trate como un no.-
Si Hacienda no responde, el endpoint devuelve 503, nunca un
false. Un falso negativo lo llevaría a emitir el documento equivocado.
La consulta no consume cuota ni se cobra: solo va acotada por el rate limit. Programe
contra regimen.clave y situacion.estado (estables), no contra codigo ni
descripcion (los define Hacienda y puede cambiarlos).
La respuesta se cachea 6 horas. Como el régimen y la inscripción sí cambian, agregue
?refresh=true cuando necesite el dato del momento. Tenga presente que el registro de
Hacienda tarda en reflejar los cambios hechos en TRIBU-CR: refresh fuerza nuestra
reconsulta, no acelera a Hacienda. Mientras el registro siga diciendo simplificado, la
plataforma sigue emitiendo exento — que es lo fiscalmente correcto hasta que el cambio
rija.
Consulta y descargas
Los endpoints y la respuesta completa están en Emisión de comprobantes, más abajo. Acá va solo lo que hay que decidir.
Qué archivar
El PDF no es el documento legal. El comprobante fiscal es el XML firmado; el PDF es una representación gráfica de cortesía que se genera al vuelo y no se almacena. Archive el XML —y la respuesta de Hacienda— por su cuenta, aunque nosotros los custodiemos cinco años.
Polling, si no puede usar webhooks
Espere entre 5 y 60 segundos y aplique backoff. No consulte en bucle cerrado:
+10 s → +20 s → +40 s → +60 s → cada 5 min hasta 30 min
Si a los 30 minutos sigue en sent, use POST /documents/{id}/refresh para forzar una
reconsulta contra Hacienda.
Más allá de emitir
Emitir el XML es el mínimo. Estas piezas son las que normalmente hay que construir aparte —y mantener— cuando se integra facturación electrónica; acá vienen resueltas.
Correo al receptor, sin montar un servidor de email
Costa Rica no obliga a enviarle el comprobante al receptor: Hacienda ya custodia el XML. Pero en la práctica todos lo esperan. Montarlo por su cuenta significa SMTP, plantillas, reputación de dominio, rebotes, reintentos y anti-spam.
El add-on de email lo hace por usted: al aceptarse el comprobante, se envía automáticamente al correo del receptor con el XML firmado, la respuesta de Hacienda y el PDF adjuntos. Se activa una vez, a nivel de cuenta, desde el portal (Facturación → Envío de comprobante por email).
Para que un correo salga se necesitan cuatro cosas: add-on activo, cuenta no suspendida, documento aceptado y correo de receptor en el XML.
La Factura de Compra (08) está excluida del envío automático: la firma el receptor, y mandarla antes genera confusión. El reenvío manual sí funciona.
Reenvío idempotente
POST /documents/{id}/reenviar-email
El cliente dice que no le llegó. Usted reenvía. El cliente vuelve a escribir. Usted reenvía otra vez. En un servicio facturado por envío, eso es dinero.
Por eso el reenvío deduplica solo: si ya se envió ese comprobante a ese correo en
los últimos 5 minutos, responde 200 con deduped: true, no reenvía y no cobra.
{ "id": "...", "status": "sent", "recipientEmail": "juan@cliente.com",
"deduped": true, "billed": false, "lastSentAt": "2026-07-25T01:10:00.000Z" }
La ventana es por par (documento, correo): enviar a otra dirección nunca se deduplica.
Si de verdad quiere reenviar dentro de la ventana —y cobrarlo—, mande force: true.
También acepta email para mandarlo a una dirección alterna sin tocar el XML.
El PDF: nuestro, o el suyo
Cada comprobante tiene un PDF de cortesía que se genera al vuelo desde el XML firmado. No se almacena, así que nunca queda desincronizado del documento legal.
Tiene dos formas de personalizarlo:
1 · Branding por emisor. Configure una vez el aspecto del PDF de cada comercio:
PUT /emisores/{legalId}/branding
{
"logo": "data:image/png;base64,iVBORw0KGgo...",
"accentColor": "#7C3AED",
"paymentInfo": "Cuenta IBAN CR00000000000000000000 (BAC, colones)\nSINPE Móvil 8888-8888",
"thankYouMessage": "¡Gracias por su compra!",
"contactPhone": "2222-1111",
"contactEmail": "facturacion@comercialxyz.com",
"contactAddress": "San José, Catedral, Edificio Plaza, oficina 5",
"website": "https://comercialxyz.com"
}
Es un merge parcial: mande solo lo que quiere cambiar; un string vacío ("") limpia el
campo. El logo se guarda en almacenamiento de objetos y se inyecta al generar el PDF —las
respuestas devuelven hasLogo, no el binario, así que listar emisores sigue siendo barato.
Esto importa si usted es un integrador con muchos comercios: cada uno recibe su propio PDF con su marca, sin que usted genere PDFs.
2 · Su propio PDF. Si ya tiene una representación gráfica que le gusta, mándela en
pdfBase64 al emitir (o al reenviar) y esa se adjunta al correo en lugar de la nuestra.
Debe empezar con %PDF- y pesar 15 MB o menos.
Saldo del comprobante ya reconciliado
Cuando una nota de crédito o débito que referencia un comprobante es aceptada, el documento original se actualiza solo:
"credito": {
"acreditado": 0, "impuestoAcreditado": 0, "debitado": 0,
"saldo": 1130, "anulado": false, "anuladoEn": null, "notas": []
}
No tiene que cruzar notas contra facturas por su cuenta: saldo y anulado ya vienen
calculados, con la lista de notas que afectaron el documento.
La respuesta del receptor, sin buzón propio
Cuando alguien acepta o rechaza un comprobante suyo, lo sabrá:
receiverMRStatus—pending,received,no_responseonot_applicable.GET /documents/{id}/receiver-mr— la respuesta estructurada.GET /documents/{id}/receiver-mr/xml— el XML firmado del receptor.- Webhook
document.receiver_mr_receiveden cuanto llega.
Eso le dice si su cliente acreditó el IVA de la factura que usted emitió.
Clave por adelantado
POST /clave/reserve
Devuelve una clave y un consecutivo antes de emitir. Sirve cuando necesita imprimir
o mostrar el número antes de tener el comprobante —tiquetes de caja, órdenes—. Después
envía esa clave en el payload y el comprobante usa la reservada.
Su numeración, y el POS que imprime la clave antes de transmitir
Si viene de un sistema con numeración propia, o su punto de venta cobra e imprime el tiquete antes de transmitir —a veces horas después, si se cayó el internet—, mande usted el consecutivo, o la clave de 50 dígitos ya compuesta, y firmamos exactamente esa. No hay que configurar nada antes.
Tiene capítulo propio, con los dos ejemplos emitidos y aceptados por Hacienda: Numeración propia y modo integrator.
Custodia de cinco años
Los XML firmados y las respuestas de Hacienda se conservan el plazo legal completo. Cada
documento expone su retentionUntil. Aun así, archive sus propios XML: son la prueba
fiscal de sus operaciones.
Emisión masiva: muchos comprobantes en una sola llamada
POST /lotes recibe hasta 2000 comprobantes con el mismo cuerpo que la emisión
individual y los emite en segundo plano, a ritmo parejo (respeta su límite por minuto y le
deja margen para lo que emita en paralelo).
POST /api/v2/efactura/lotes
Idempotency-Key: 8f1c…
{
"items": [
{ "tipo": "tiquete", "ref": "pedido-1001", "payload": { "emisorLegalId": "3101123456", "condicionVenta": "01", "medioPago": ["01"], "detalle": [ … ] } },
{ "tipo": "factura", "ref": "pedido-1002", "payload": { … } }
],
"confirmar": true
}
-
Se valida todo antes de crear nada: el esquema de cada tipo, el emisor y su
certificado, los CAByS contra el catálogo, el saldo de las notas de crédito (sumando las
del mismo lote) y el cupo del plan. Con un solo error responde
422con la listaerrores(fila= posición enitems,campo= suref) y no emite nada. -
confirmar: true(por defecto) lo pone en cola de una vez. Confalsequedavalidado24 horas para revisarlo y confirmarlo conPOST /lotes/{id}/confirmar. -
tipo:factura,tiquete,nota_credito,nota_debito,recibo_pago,factura_compraofactura_exportacion. -
Cada comprobante emitido dispara sus webhooks de siempre (
document.accepted,document.rejected). El avance del lote:GET /lotes/{id}(conteos) yGET /lotes/{id}/items?estado=error,rechazado(fila por fila, con clave y consecutivo). -
POST /lotes/{id}/cancelardetiene lo pendiente (lo emitido queda) yPOST /lotes/{id}/reanudarretoma uno pausado (p. ej. por saldo). -
También acepta la plantilla de Excel del portal:
GET /lotes/plantillayPOST /lotes/excel(multipart, campofile), que quedavalidadohasta confirmarlo.
Se cobra en el plan de su llave, igual que la emisión individual, y emite con los certificados que su llave puede usar (una llave de sandbox nunca firma producción). Máximo 3 cargas abiertas a la vez. En los planes ilimitados, hasta 2000 comprobantes por mes.
Facturas recurrentes
POST /recurrentes programa un comprobante que se emite solo: diario, semanal (varios
días), mensual (varios días, -1 = último), anual o en fechas exactas.
POST /api/v2/efactura/recurrentes
Idempotency-Key: 2b7e…
{
"nombre": "Mensualidad cliente 42",
"tipo": "factura",
"payload": { "emisorLegalId": "3101123456", "environment": "production", "receptor": { … }, "detalle": [ … ] },
"frecuencia": { "tipo": "mensual", "diasMes": [1], "hora": "08:00" },
"inicio": "2026-11-01",
"fin": null,
"maxEmisiones": 12
}
- La plantilla se valida al programar con el mismo esquema de la emisión: un error se ve ahora, no el día 1 a las 8 a. m.
-
POST /recurrentes/vista-previadevuelve las próximas fechas con el mismo cálculo que usa el programador. Hora de Costa Rica. - Nunca emite atrasadas en lote: si el servicio estuvo caído, emite la última y registra las omitidas. Cada ocurrencia tiene su propia llave de idempotencia (sin dobles).
-
En moneda extranjera usa el tipo de cambio del día de cada emisión, salvo
tipoCambioModo: "fijo"contipoCambioFijo. -
PATCH /recurrentes/{id}pausa (estado: "pausada"), reanuda o edita;DELETEla borra (lo emitido queda).GET /recurrentes/{id}trae el historial de ejecuciones. - Emite con la llave que la creó: si la revoca, las siguientes ocurrencias fallan con ese motivo. Solo emisores con numeración automática.
Iniciar sesión con Facturaencr
Si usted desarrolla un sistema (un ERP, un punto de venta, una tienda), puede poner en su pantalla de ingreso un botón “Iniciar sesión con Facturaencr”. El usuario inicia sesión en nuestro portal, con su contraseña de siempre, aprueba su aplicación y vuelve a su sistema. Usted recibe quién es y, si lo pidió, su llave queda autorizada para emitir por esa cuenta. Nadie copia llaves ni códigos.
Es el flujo de código de OAuth 2 con PKCE, con una diferencia: no hay client_secret.
El canje del código se autentica con su API key (X-API-Key + X-API-Secret). No se
crea ninguna credencial nueva: ni un secreto de cliente ni un token de acceso que guardar,
renovar o proteger.
Requisito. Su cuenta necesita el modo desarrollador (ver “Llave de desarrollador”). Las
aplicaciones se registran en el portal, en Desarrollador → Aplicaciones: nombre, sitio
web y las direcciones de vuelta (redirect_uri). Al registrarla recibe su client_id, que
es público.
Permisos (scope)
| Scope | Qué recibe usted | Quién puede aprobarlo |
|---|---|---|
perfil (siempre) |
La cuenta y el usuario que inició sesión: id, nombre y correo | Cualquier usuario de la cuenta |
emitir |
Además, su llave queda autorizada sobre esa cuenta, con las cédulas que elija el usuario | El dueño o un usuario con el permiso “Usuarios” |
Con emitir pasa exactamente lo mismo que cuando el comercio lo agrega con su código: aparece
en Usuarios → Desarrolladores de esa cuenta, paga el comercio y él puede quitarle el
acceso cuando quiera. Si ya estaban vinculados, se reactiva con las cédulas elegidas esta vez.
1. Mandar al usuario a autorizar
GET https://api.facturaencr.com/v2/efactura/apps/authorize
?response_type=code
&client_id=fcr_app_0123456789abcdef0123456789abcdef
&redirect_uri=https://misistema.ejemplo.cr/facturaencr/vuelta
&state=Rk9x2v8LqW1t
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&scope=perfil%20emitir
-
redirect_uritiene que ser idéntica a una de las registradas. Si no lo es, no se redirige a ningún lado: el error se muestra en nuestra página. -
state: un valor aleatorio (8 a 500 caracteres) que usted guarda en la sesión del usuario y compara a la vuelta. Es lo que impide que alguien le inyecte su propia cuenta. -
code_challenge: el SHA-256 en base64url de uncode_verifieraleatorio (43 a 128 caracteres) que usted guarda en su servidor. Solo se aceptaS256.
El usuario aprueba (o cancela) y vuelve a su redirect_uri:
https://misistema.ejemplo.cr/facturaencr/vuelta?code=…&state=Rk9x2v8LqW1t
https://misistema.ejemplo.cr/facturaencr/vuelta?error=access_denied&state=Rk9x2v8LqW1t
El código vale 5 minutos y se usa una sola vez.
2. Canjear el código (desde su servidor)
curl -X POST https://api.facturaencr.com/v2/efactura/apps/token \
-H "X-API-Key: $FACTURAENCR_KEY" \
-H "X-API-Secret: $FACTURAENCR_SECRET" \
-H "Content-Type: application/json" \
-d '{
"grant_type": "authorization_code",
"code": "…",
"code_verifier": "…",
"redirect_uri": "https://misistema.ejemplo.cr/facturaencr/vuelta",
"client_id": "fcr_app_0123456789abcdef0123456789abcdef"
}'
{
"cuenta": { "id": "64f0a1b2c3d4e5f600000001", "nombre": "Comercio Ejemplo S.A." },
"usuario": { "id": "64f0a1b2c3d4e5f600000002", "nombre": "Ana Ejemplo", "email": "ana@ejemplo.cr", "esDueno": false },
"scope": "perfil emitir",
"acceso": { "header": "X-Cuenta", "valor": "64f0a1b2c3d4e5f600000001", "emisores": ["3101000000"] }
}
- Solo sirve una API key de la cuenta dueña de la aplicación, creada en el portal.
usuario.ides estable: úselo para reconocer al usuario la próxima vez que inicie sesión.-
accesoviene solo conemitir. Desde ahí emite como cualquier llave de desarrollador, conX-Cuenta: <acceso.valor>.emisoresvacío significa todas las cédulas de la cuenta. - Si la cuenta que inició sesión es la suya, no se crea ningún vínculo: ya es su cuenta.
| Situación | Respuesta |
|---|---|
Faltan o no sirven X-API-Key / X-API-Secret |
401 invalid_client |
| La aplicación no existe, está desactivada o no es de la cuenta de la llave | 401 invalid_client |
| El código venció, ya se usó o no es de esta aplicación | 400 invalid_grant |
redirect_uri o code_verifier no coinciden (el código queda inutilizado) |
400 invalid_grant |
GET /apps, con su llave, lista sus aplicaciones y las URLs del flujo. Es solo lectura:
registrar y editar aplicaciones se hace en el portal.
Seguridad
-
Haga el canje solo desde su servidor.
/apps/tokenlleva el secreto de su llave; por eso no acepta llamadas desde un navegador (no tiene CORS). Una app móvil o de escritorio debe pasar por su backend. -
Las direcciones de vuelta van en
https.httpsolo se acepta enlocalhost, para desarrollar. No se admiten comodines, fragmentos ni esquemas propios. -
Compare siempre el
state. En la página de vuelta, cambie la URL (history.replaceState) para que el código no quede en el historial ni viaje en elReferer. - El usuario ve en la pantalla de autorización el nombre de su aplicación, el suyo como desarrollador y el dominio real al que vuelve. La pantalla no se puede mostrar dentro de un iframe.
- Desactivar una aplicación o cambiar sus direcciones de vuelta anula al instante las autorizaciones a medio terminar. Las cuentas ya vinculadas conservan el acceso hasta que el comercio (o usted, desde Mis clientes) lo quite.
Conector de IA (MCP)
La cuenta se puede conectar a un asistente de IA (Claude, ChatGPT u otro cliente MCP) para consultar y emitir comprobantes conversando. El conector es un servidor MCP que por dentro llama a esta misma API: pasa por los mismos scopes, lista blanca de IP, límite de peticiones, idempotencia y validaciones.
| URL | https://api.facturaencr.com/v2/efactura/mcp |
| Transporte | Streamable HTTP, sin sesión: solo POST (GET y DELETE responden 405) |
Cómo se autentica
-
OAuth 2.1 (lo normal desde Claude o ChatGPT). Agregue la URL como conector; la IA lo
manda al portal, a la página Conectar IA, donde usted inicia sesión y aprueba los
permisos:
consultar(leer comprobantes y catálogos) yemitir. Sinemitir, las herramientas de emisión ni aparecen. Las conexiones se ven y se revocan en esa misma página. -
Con su llave de API, para clientes MCP que se configuran a mano: las mismas cabeceras
de la API,
X-API-KeyyX-API-Secret, másX-Cuentasi es una llave de desarrollador.
Sin credenciales responde 401 con la cabecera WWW-Authenticate, que es lo que dispara el
OAuth en el cliente.
Herramientas
| Herramienta | Qué hace |
|---|---|
listar_emisores |
Emisores de la cuenta, con cédula, ambiente y actividad económica |
buscar_cabys |
Busca códigos CAByS por texto o código, con su tarifa de IVA |
buscar_productos |
Busca en los productos guardados en el portal |
buscar_clientes |
Busca en los clientes guardados en el portal |
consultar_contribuyente |
Busca una cédula en el registro de Hacienda (nombre, régimen, actividades) |
consultar_comprobante |
Estado y datos de un comprobante por documentId, clave o consecutivo |
listar_comprobantes |
Comprobantes emitidos, del más reciente al más antiguo, con totales |
listar_plantillas_pdf |
Diseños del PDF y cuál usa cada emisor |
cambiar_plantilla_pdf |
Cambia el diseño del PDF de un emisor para sus comprobantes nuevos |
consumo_conector |
Qué incluye el plan para emitir por el conector y cuánto lleva el mes |
listar_aplicaciones |
Para desarrolladores: sus apps de “Iniciar sesión con Facturaencr” y cómo se integra el flujo |
preparar_factura |
Arma y valida una factura (cliente identificado) sin emitirla |
preparar_tiquete |
Arma y valida un tiquete (consumidor final) sin emitirlo |
preparar_anulacion |
Arma la nota de crédito que anula por completo un comprobante aceptado |
emitir_comprobante |
Emite un borrador ya confirmado y espera el veredicto de Hacienda |
preparar_lote |
Arma y valida varios comprobantes de una vez, sin emitir |
emitir_lote |
Emite un lote ya confirmado, en segundo plano y de a uno |
consultar_lote |
Avance de un lote: aceptados, rechazados, con error y pendientes |
Emitir son dos pasos
-
Preparar.
preparar_factura,preparar_tiquete,preparar_anulacionopreparar_lotevalidan con las mismas reglas que la API y devuelven un borrador con el resumen (emisor, ambiente, receptor, líneas, totales). No emiten nada. La IA tiene que mostrárselo y esperar su confirmación. - Emitir.
emitir_comprobante(oemitir_lote) solo acepta un borrador preparado.
Reglas que conviene conocer:
- Un borrador vale 30 minutos y solo lo puede usar la misma llave o conexión que lo creó.
-
En producción hace falta
confirmo_produccion: true: el comprobante es real y solo se anula con una nota de crédito. - Idempotente: si la IA reintenta con el mismo borrador, recibe el mismo comprobante; no se emite otro. Lo mismo vale para cada comprobante de un lote.
-
Un lote lleva hasta 500 comprobantes. Salen en segundo plano, de a uno y con pausa;
el avance se consulta con
consultar_lote. - Con un plan de API, el conector por ahora solo emite en sandbox. En producción emita por la API o desde el portal; las consultas por la IA siguen disponibles.
Catálogos oficiales v4.4
Los catálogos de referencia (
referencia[].tipoDocumentoyreferencia[].codigo) no están en esta sección. Viven arriba, dentro de Emisión de comprobantes, junto a la Nota de Crédito y la Nota de Débito que son quienes los usan: busque "Tipo de documento de referencia" y "Códigos de referencia".
Condición de venta
| Código | Condición |
|---|---|
01 |
Contado |
02 |
Crédito — exige plazoCredito |
03 |
Consignación |
04 |
Apartado |
05 |
Arrendamiento con opción de compra |
06 |
Arrendamiento en función financiera |
07 |
Cobro a favor de un tercero |
08 |
Servicios prestados al Estado a crédito |
10 |
Venta a crédito en IVA hasta 90 días (art. 27, LIVA) |
12 |
Venta de mercancía no nacionalizada |
13 |
Venta de bienes usados no contribuyente |
14 |
Arrendamiento operativo |
15 |
Arrendamiento financiero |
99 |
Otros — exige condicionVentaOtros |
La condición de venta no es igual en todos los comprobantes. Cada esquema de Hacienda lleva su propia lista, y mandar un código fuera de ella se rechaza con
-1(cvc-enumeration-valid) gastando el consecutivo:
Comprobante Códigos admitidos Factura electrónica (01) 01–08,10,12–15,99Tiquete (04), Compra (08), Exportación (09) igual, pero sin el 12Notas de crédito y débito (03, 02) todos, 01–15y99Recibo Electrónico de Pago (10) solo 09y11Los códigos
09y11corresponden al pago de una factura emitida con08y con10respectivamente: por eso viven en el REP y en las notas que ajustan esas facturas, no en la factura original.
Dos campos condicionales que conviene no olvidar:
-
plazoCreditoes unstring, no un número. Va en días (v4.4 cambió la unidad; antes eran meses)."30","60","90". Enviar30sin comillas devuelve400 · "plazoCredito" must be a string. -
condicionVentaOtros(5–100 caracteres) es obligatorio concondicionVenta: "99". La API lo exige antes de emitir, así que no se gasta consecutivo.
Medios de pago
| Código | Medio |
|---|---|
01 |
Efectivo |
02 |
Tarjeta |
03 |
Cheque |
04 |
Transferencia o depósito bancario |
05 |
Recaudado por terceros |
06 |
SINPE Móvil (nuevo en v4.4) |
07 |
Plataforma digital (nuevo en v4.4) |
99 |
Otros — requiere otros |
De uno a cuatro medios. Dos formas de enviarlos:
"medioPago": ["01"] // simple
"medioPago": [ // mixto
{ "tipo": "01", "monto": 3000 },
{ "tipo": "02", "monto": 2000 }
]
Con más de un medio, cada objeto exige monto (> 0). Si falta alguno, la emisión se
rechaza con 400 y MEDIO_PAGO_MONTO_REQUERIDO: sin los montos el reparto no existe y no
hay forma de deducirlo.
Lo ideal es que la suma iguale el total del comprobante —total que incluye impuestos y otros cargos—, pero si no cuadra el comprobante se emite igual. Hacienda no valida ese dato, así que no le negamos la emisión: en su lugar la respuesta trae un aviso.
{
"id": "...",
"clave": "...",
"status": "queued",
"warnings": [
{
"code": "medio_pago_suma_distinta",
"message": "La suma de los montos por medio de pago (150.00) no coincide con el total del comprobante (1130.00). El comprobante se emitió igual: Hacienda no valida este dato. Revisalo si llevás arqueo o cartera por medio de pago.",
"sumaMedios": 150,
"totalComprobante": 1130
}
]
}
Como el total lo calcula la plataforma, conviene calcular el reparto después de conocer el total, o usar un solo medio. Si su conciliación depende del desglose por medio de pago, trate ese aviso como un error suyo aunque nosotros emitamos.
El código
08no existe en v4.4. La API lo rechaza con400.
Tipos de identificación
| Código | Tipo | Longitud | ¿Emisor? | ¿Receptor? |
|---|---|---|---|---|
01 |
Física | 9 | Sí | Sí |
02 |
Jurídica | 10 | Sí | Sí |
03 |
DIMEX | 11–12 | Sí | Sí |
04 |
NITE | 10 | Sí | Sí |
05 |
Extranjero no domiciliado | 1–20, admite letras | No | Exportación y FEC (proveedor) |
06 |
No contribuyente | 1–20 | No | Todos menos el Tiquete |
Para un receptor extranjero: tipoIdentificacion: "05", el identificador (pasaporte, tax
ID) en numeroIdentificacion, y la dirección en otrasSenasExtranjero en lugar de
ubicacion.
Dónde vale cada uno lo decide Hacienda, no el XSD. Medido contra Hacienda:
-
05como cliente: solo en la Factura de Exportación. En Factura, Tiquete y notas es -409. -
06: vale en Factura, notas, Exportación, REP y Factura de Compra. En el Tiquete es -409. - Factura de Compra (el receptor es su proveedor):
- con
05, Hacienda exigeotrasSenasExtranjero(-505); - con
06, exigecondicionVenta: "13"(-502); - con los dos, no se manda
codigoActividad, porque no tienen actividad inscrita (-407).
- con
La API corta todos esos casos con un 400 antes de gastar el consecutivo.
Hacienda valida DIMEX y NITE contra su padrón: un número con formato válido pero inexistente se rechaza con -38.
Unidades de medida
unidadMedida acepta los códigos del catálogo oficial: Unid, Sp (servicios
profesionales), kg, g, L, mL, m, cm, m2, m3, h, d, Al (alquiler),
Os (otros servicios), entre otros.
La API no valida este campo contra el catálogo — solo el largo (máximo 15 caracteres). Un typo o una mayúscula equivocada viaja tal cual al XML. Use exactamente los códigos oficiales y trátelos como sensibles a mayúsculas.
Regla práctica: Sp para servicios, Unid para mercancías que se cuentan, y la unidad
física real (kg, L, m) cuando se venda a granel.
Otros cargos
Hasta 15 por comprobante.
| Código | Tipo de cargo |
|---|---|
01 |
Contribución parafiscal |
02 |
Timbre de la Cruz Roja |
03 |
Timbre del Benemérito Cuerpo de Bomberos |
04 |
Cobro de un tercero — exige los datos del tercero |
05 |
Costos de exportación |
06 |
Impuesto de servicio 10 % |
07 |
Timbre de Colegios Profesionales |
08 |
Depósitos de garantía |
09 |
Multas o penalizaciones |
10 |
Intereses moratorios |
99 |
Otros — requiere tipoDocumentoOtros |
El caso 06 (servicio 10 %) es el más frecuente en restaurantes:
"otrosCargos": [
{ "tipoDocumento": "06", "detalle": "Impuesto de servicio", "porcentaje": 10, "montoCargo": 1130 }
]
Códigos de descuento
| Código | Naturaleza |
|---|---|
01 |
Descuento por Regalía — 100 % de la línea |
02 |
Descuento por Regalía, IVA cobrado al cliente |
03 |
Descuento por Bonificación — 100 % de la línea |
04 |
Descuento por volumen |
05 |
Descuento por Temporada (estacional) |
06 |
Descuento promocional |
07 |
Descuento Comercial |
08 |
Descuento por frecuencia |
09 |
Descuento sostenido |
99 |
Otros — requiere codigoDescuentoOtro y naturalezaDescuento |
codigoDescuento es obligatorio siempre que exista un descuento. Sin él, Hacienda rechaza
con -41.
Instituciones de exoneración
| Código | Institución |
|---|---|
01 |
Ministerio de Hacienda |
02 |
Ministerio de Relaciones Exteriores y Culto |
03 |
Ministerio de Agricultura y Ganadería |
04 |
Ministerio de Economía, Industria y Comercio |
05 |
Cruz Roja Costarricense |
06 |
Benemérito Cuerpo de Bomberos |
07 |
Asociación Obras del Espíritu Santo |
08 |
Fecrunapa |
09 |
EARTH |
10 |
INCAE |
11 |
Junta de Protección Social |
12 |
Aresep |
99 |
Otros — requiere nombreInstitucionOtros |
nombreInstitucionespera el código, no el nombre escrito.
Tipos de documento de exoneración
| Código | Tipo |
|---|---|
01 |
Compras autorizadas por la Dirección General de Tributación |
02 |
Ventas exentas a diplomáticos |
03 |
Autorizado por Ley Especial |
04 |
Exenciones DGH — Autorización Local Genérica |
05 |
Exenciones DGH — Transitorio V (ingeniería, arquitectura, topografía, obra civil) |
06 |
Servicios turísticos inscritos ante el ICT |
07 |
Transitorio XVII (recolección, clasificación y almacenamiento de reciclaje) |
08 |
Exoneración a Zona Franca |
09 |
Servicios complementarios para la exportación (artículo 11 RLIVA) |
10 |
Órgano de las corporaciones municipales |
11 |
Exenciones DGH — Autorización de Impuesto Local Concreta |
99 |
Otros — requiere tipoDocumentoOTRO |
Los tipos 02, 03, 06, 07 y 08 exigen además articulo (el número de artículo de
la ley). Sin él: -478.
Y con articulo hay que mandar inciso. Si ese artículo no tiene incisos, el valor es
0 —no se omite—: Hacienda rechaza con -479 («De no existir un inciso, se indicará un
cero en el campo»). Si envía articulo sin inciso, la API pone el cero por usted.
Los tipos 01, 05, 06, 07 y 11 el Anexo v4.4 los reserva a notas de crédito y
débito que ajusten comprobantes emitidos bajo ese régimen (notas al pie 39-44 de la nota
10.1). En una factura o tiquete la API los acepta, pero lo avisa en warnings. Para una
venta de hoy, lo normal es el 04.
Al XML viajan la TARIFA y el MONTO
TarifaExonerada son los puntos de tarifa (decimal(4,2): tope 99,99) y MontoExoneracion
es el monto de impuesto, mayor que cero. Exonerar el 100 % de un IVA del 13 % sobre
₡10 000 son 13 y ₡1 300.
El porcentajeExoneracion no existe en la v4.4 (la v4.3 sí lo tenía). Si lo envía, se
resuelve así: si el valor cabe dentro de la tarifa del impuesto son puntos de tarifa
(8 sobre un IVA del 13 % exonera 8 puntos); si la excede, se toma como la convención
vieja de «qué parte del impuesto» (100 sobre un IVA del 13 % exonera todo). Las dos
lecturas coinciden en el caso que importa. Para no depender de eso, use tarifaExonerada.
⚠️ inciso va en CERO cuando el artículo no tiene incisos, no se omite: Hacienda
rechaza con -479 («De no existir un inciso, se indicará un cero en el campo»). La API
lo rellena sola si envía articulo sin inciso.
Una exoneración parcial reparte la venta entre gravada y exonerada, línea por línea: cada línea aporta su base en la misma proporción en que se exoneró su impuesto. Exonerar la mitad del IVA de una venta de ₡10 000 declara ₡5 000 gravados y ₡5 000 exonerados; con el 100 % la línea entera va a exonerada.
⚠️ En una fórmula:
Total<Grupo>Exonerado = Σ (base de la línea × MontoExoneracion ÷ impuesto BRUTO de la línea)Antes decía
Σ (MontoExoneracion ÷ TarifaExonerada × 100), que es lo mismo sólo si se exonera la tarifa completa. Con una exoneración parcial de tarifa divergen: 8 puntos de un IVA del 13 % sobre ₡10 000 son ₡6 153,85 exonerados y ₡3 846,15 gravados, no la línea entera. Verificado emitiendo la versión equivocada: Hacienda la rechazó con -111/-106/-108 nombrando esos mismos números — la base que cada exoneración implica. El anexo lo escribe como «(Σ montos exonerados / Σ montos de impuesto) × monto total de la venta» sin aclarar el alcance de esas sumas: es la línea, no el comprobante. Sumando por grupo, dos mercancías de ₡10 000 —una al 13 % exonerada y otra al 2 % sin exonerar— salieron rechazadas con -111, -106 y -108. La diferencia solo aparece con tarifas mezcladas.
Las exentas y no sujetas conservan su propio total. TotalImpuesto también es por línea
(impuesto de las no exoneradas + ImpuestoNeto de las exoneradas).
La Factura de Exportación y el Recibo Electrónico de Pago no tienen nodo de
exoneración en el esquema v4.4: mandarlo devuelve 422 exoneracion_no_aplica_documento.
Y un emisor de régimen simplificado no traslada IVA, así que tampoco puede exonerar
(422 exoneracion_no_aplica_simplificado).
Verificar la autorización antes de emitir
GET /exoneraciones/{autorizacion} consulta el registro de exoneraciones de Hacienda
(EXONET) y devuelve tipo, institución, fecha, tarifa autorizada, vigencia y los CAByS
cubiertos. Es el mismo registro contra el que Hacienda cruza los tipos 04 y 11 al
recibir el comprobante, y por eso no se puede probar en el sandbox: ahí toda
exoneración de esa familia sale rechazada con -125, sea real o inventada.
curl -H "X-API-Key: $KEY" -H "X-API-Secret: $SECRET" \
"https://api.facturaencr.com/api/v2/efactura/exoneraciones/AL-00012345-20?cabys=2999999999005"
Al emitir, la API hace esa consulta por su cuenta para los tipos 04 y 11 y devuelve en
warnings lo que no calce (número no registrado, fecha distinta, tarifa mayor a la
autorizada, CAByS fuera de la autorización, beneficiario distinto del receptor). Avisa,
no bloquea: la palabra final la tiene Hacienda.
También quedan expuestos los otros dos registros públicos:
GET /productores/agropecuario/{identificacion} (MAG) y
GET /productores/pesca/{identificacion} (INCOPESCA y acuicultura). Ojo: Hacienda
responde esos dos con HTTP 200 aunque la persona no esté inscrita, poniendo el
"Not Found" dentro del cuerpo; acá eso se traduce a encontrado:false.
Medicamentos y medios de transporte
Tres campos de la línea que dependen de qué se vende. Son opcionales en el esquema y se vuelven obligatorios según el CABYS; si faltan cuando corresponden, Hacienda emite una advertencia — no rechaza el comprobante.
| Campo | Cuándo | Qué lleva |
|---|---|---|
numeroVINoSerie |
vehículos, aeronaves y embarcaciones | VIN, número de serie o consecutivo de fabricación (17 caracteres). Si la línea cubre varias unidades, mandá un arreglo |
registroMedicamento |
medicamentos con registro sanitario | el número de registro del Ministerio de Salud |
formaFarmaceutica |
medicamentos con registro sanitario | el código de la nota 19 |
formaFarmaceutica es el código NUMÉRICO del catálogo «Código de forma Farmacéutica» de
Hacienda: del 01 al 229. No es una abreviatura: 04 es cápsula, 10 cápsula de
gelatina dura, 229 ungüento. Se acepta con o sin el cero a la izquierda (4 = 04).
{
"codigoCabys": "3999999999011",
"detalle": "Acetaminofén 500 mg",
"cantidad": 1, "unidadMedida": "Unid", "precioUnitario": 1500,
"registroMedicamento": "MS-2026-12345",
"formaFarmaceutica": "04",
"impuesto": [{ "codigo": "01", "codigoTarifa": "08", "tarifa": 13 }]
}
Los tres van en la Factura, el Tiquete, la Factura de Compra, la de Exportación y las notas de crédito y débito. El Recibo Electrónico de Pago no los tiene: su línea ni siquiera lleva CABYS.
Exportar mercancía con impuestos especiales
impuesto[].montoExportacion es el monto de impuesto de exportación, y solo aplica a
mercancías con impuestos especiales (licores, combustibles, cemento…). Existe únicamente
en la Factura de Exportación y en las notas que la ajusten; en los demás
comprobantes se ignora. No se calcula: es el monto que declara el exportador.
Tipo de transacción
Nuevo en v4.4. Si se omite, se asume venta normal.
| Código | Tipo |
|---|---|
01 |
Venta normal de bienes y servicios |
02 |
Mercancía de autoconsumo exento |
03 |
Mercancía de autoconsumo gravado |
04 |
Servicio de autoconsumo exento |
05 |
Servicio de autoconsumo gravado |
06 |
Cuota de afiliación |
07 |
Cuota de afiliación exenta |
08 |
Bienes de capital para el emisor |
09 |
Bienes de capital para el receptor |
10 |
Bienes de capital para emisor y receptor |
11 |
Bienes de capital de autoconsumo exento para el emisor |
12 |
Bienes de capital sin contraprestación a terceros exento para el emisor |
13 |
Sin contraprestación a terceros |
Los tipos
08,09y10determinan el crédito de IVA de su cliente. Marcar mal una venta de bienes de capital le cuesta dinero al comprador.
CABYS
Cada línea exige un código CABYS de 13 dígitos del Catálogo de Bienes y Servicios del BCCR. El CABYS hace dos cosas:
-
Clasifica la línea. El primer dígito determina si es bien o servicio: 0–4 = mercancía, 5–9 = servicio. De ahí sale si el monto suma a
TotalMercanciasGravadaso aTotalServGravados. Hacienda re-deriva esta clasificación del CABYS: si sutipoVentala contradice, gana el CABYS.Ojo con el 5: la sección es "Construcciones y servicios de construcción" y cuenta como servicio, no como mercancía. Es la que más confunde, porque incluye tanto la obra (
53…) como los servicios de construcción (54…). -
Sugiere la tarifa de IVA que le corresponde al producto.
Buscador
curl "https://api.facturaencr.com/v2/efactura/catalogs/cabys?q=pan%20pita&top=5" \
-H "X-API-Key: $EFACTURA_KEY" -H "X-API-Secret: $EFACTURA_SECRET"
{
"items": [
{ "codigo": "2999999999007", "descripcion": "Pan pita o pan árabe, sin congelar", "impuesto": 1 },
{ "codigo": "2999999999008", "descripcion": "Pan pita o pan árabe, congelado", "impuesto": 1 }
]
}
q— texto o código, mínimo 3 caracteres. Menos de 3:400 query_too_short.top— máximo de resultados, 1 a 50 (por defecto 30). El parámetro estop, nolimit:limitse ignora en silencio.impuesto— tarifa de IVA asociada al código, en porcentaje.
Buscar por código exacto devuelve cero o un resultado; sirve como validador.
Verifique sus códigos antes de facturar
La API no valida el CABYS contra el catálogo: solo comprueba que sean 13 dígitos. Un código bien formado pero inexistente pasa la validación local, consume consecutivo y lo rechaza Hacienda con -400:
-400 En la línea (1) el código indicado en el campo 'Código de Producto/Servicio' no se encuentra en el Catálogo de Bienes y Servicios CABySSi carga productos desde un maestro propio, páselo una vez por
GET /catalogs/cabys?q={codigo}y marque los que devuelvanitems: []. Es la causa más común de rechazo en una integración nueva, y no se detecta en pruebas si sus datos de prueba usan códigos válidos.
Contingencia: cuando se cae el internet
La venta ocurre el lunes con el sistema caído. Usted la transmite el miércoles. ¿Qué fecha lleva el comprobante?
La del lunes. La FechaEmision es la de la operación, no la de la transmisión, y
determina el período tributario: fecharla el miércoles mete la venta en el mes equivocado
del D-104. Y no se puede corregir después — habría que anularla con una nota de crédito y
volver a emitir.
Para eso existen dos campos:
{
"fechaEmision": "2026-07-20T09:00:00-06:00", // cuándo ocurrió la venta
"situacion": "3" // por qué se transmite tarde
}
situacion |
Significado |
|---|---|
1 |
Normal — emisión y transmisión en línea. Es el default; no lo envíe. |
2 |
Contingencia — falló su sistema |
3 |
Sin internet — falló la conectividad |
Ambos campos son opcionales y en el flujo normal no se envían: la plataforma pone la
hora del servidor y situación 1.
Las dos salvaguardas
La fecha no puede ser futura. Se toleran hasta 5 minutos adelantados por desfase de reloj:
en ese caso el comprobante sale con la hora del servidor. Más de 5 minutos en el futuro es 400.
Retrofechar más de 15 minutos con situacion: "1" genera un aviso, no un error. El
comprobante se emite:
"warnings": [
{
"code": "fecha_emision_retrofechada",
"message": "fechaEmision está 612 minutos en el pasado y situacion es \"1\" (normal). El comprobante se emite igual —Hacienda acepta la retrofecha— pero quedará fechado en ese momento…",
"minutos": 612
}
]
Está verificado emitiendo: Hacienda acepta la retrofecha con situación normal — se probó con 30 minutos, 6 horas, 12 horas, 2 días, 7 días y hasta 30 días de atraso, y los aceptó todos. Por eso avisamos en vez de bloquear: un punto de venta que transmite en lote al final del día tiene fechas correctas y con horas de atraso, y bloquearlo le impedía facturar.
El aviso está porque el riesgo sigue siendo real en el otro caso: un reloj mal configurado emite con fecha equivocada, el comprobante es válido para Hacienda, cae en el período tributario que no era y ya no se corrige. Los 15 minutos de tolerancia absorben el desfase normal entre su reloj y el nuestro.
Si la venta se hizo sin internet, declárelo con situacion: "3": es lo correcto y además
no genera aviso.
Los consecutivos siguen su orden normal. La contingencia cambia la fecha y el dígito de situación de la clave, no la numeración: emita en el orden en que va transmitiendo. Si su POS compone e imprime la clave antes de transmitir, vea Numeración propia y modo integrator.
Cédulas alfanuméricas
Desde la v4.4 —oficio DGL-195-2026, Decreto 44648-MJP— las cédulas jurídicas y físicas pueden contener letras una vez agotado el consecutivo numérico. La longitud no cambia; solo el conjunto de caracteres.
| Tipo | Longitud | Ejemplo válido |
|---|---|---|
01 Física |
9 | 1A234B567 |
02 Jurídica |
10 | 310123A456 |
03 DIMEX |
11–12 | 12345678901 |
04 NITE |
10 | 1234567890 |
05 Extranjero |
1–20 | X-9912345 |
06 No contribuyente |
1–20 | 100000001 |
Esto rompe tres suposiciones que casi todo sistema viejo tiene:
- No valide con
^\d+$. El patrón correcto es[0-9A-Za-z]con la longitud del tipo. -
No guarde la cédula como número. Un
INTo unparseIntdestruye las letras y, de paso, los ceros a la izquierda. - No normalice a mayúsculas ni minúsculas por su cuenta. Envíela tal como está inscrita.
El consecutivo (consecutivoNumero, 10 caracteres) admite el mismo juego alfanumérico por
la misma razón.
La cédula del emisor se rellena con ceros a la izquierda hasta 12 caracteres dentro de la clave de 50. El relleno respeta las letras:
310123A456→00310123A456.
Numeración propia y modo integrator
El consecutivo tiene 20 dígitos y, por defecto, lo genera la plataforma de forma atómica:
001 00001 01 0000000866
│ │ │ └── número secuencial (10)
│ │ └───────── tipo de comprobante (2)
│ └────────────── terminal / caja (5)
└─────────────────── sucursal (3)
Si su sistema ya lleva su propia numeración —porque migró de otro proveedor, o porque su punto de venta imprime la clave antes de transmitir—, mándela usted. Acá está todo lo que hace falta para eso.
Los tres caminos para numerar
| Cómo | Cuándo usarlo | Qué envía |
|---|---|---|
| La API numera | Caso normal. Olvídese del tema. | Nada |
claveIntegrador |
Su POS ya compuso e imprimió la clave | La clave completa de 50 |
| Las piezas | Su numeración es propia, pero no imprime la clave antes | consecutivoNumero + codigoSeguridad + branchCode + terminalCode + situacion |
Existe además clave, sólo para claves pre-reservadas con POST /clave/reserve (ver
Reserva previa de clave, al final de este capítulo).
claveyclaveIntegradorson excluyentes. Mandar las dos no es redundante, es ambiguo: una dice «usá la que reservé» y la otra «usá la que compuse».
Requisito previo: ninguno
Los dos últimos caminos no exigen configurar el emisor. Mande el número y ya: ese
documento sale con su numeración. Repetir un número devuelve 409
(consecutivo_duplicate).
Nosotros anotamos cada número que nos manda, así que
GET /emisores/{legalId}/consecutivo/next sigue devolviendo el primero que sabemos libre y
usted puede alternar —o devolvernos el contador— sin repetir nada. La respuesta de emisión
trae un aviso si el número que envió va por debajo de lo ya anotado
(consecutivo_por_debajo_de_la_marca) o si pega un salto de más de mil
(consecutivo_salto_grande): el contador no retrocede, así que un dedazo de diez
dígitos quema la serie hasta ahí.
Dos cosas que ese registro no sabe:
-
Lo que usted emitió por fuera de esta API. Tras una migración, siembre el arranque con
PUT /emisores/{legalId}/consecutivoo empiece en un bloque libre alto; si no, Hacienda contesta-99(numeración ya existente en sus bases). - Cómo seguir una numeración alfanumérica (v4.4): ahí no hay contador que llevar, la serie no es una recta.
El modo integrator: para que la API exija el número
El modo sirve para lo contrario de lo que suele pensarse: no habilita mandar el consecutivo —eso ya se puede—, sino que lo vuelve obligatorio.
PATCH /emisores/{legalId}/config
{ "consecutivoMode": "integrator" }
| Modo | Qué hace | Cuándo usarlo |
|---|---|---|
platform (por defecto) |
Numera la plataforma cuando el payload no trae número | Casi siempre |
integrator |
Exige el número: sin consecutivoNumero ni claveIntegrador es 400 (consecutivo_required_in_integrator_mode) |
Si migró de otro sistema y ningún documento suyo debe salir con numeración nuestra |
Es la red de seguridad de quien migró: un descuido de su sistema falla en vez de recibir nuestro número 1. Fuera de eso, el modo no decide nada: quién numera lo decide el payload, documento por documento.
Cuando el número lo pone usted, es suya la responsabilidad de que no haya saltos ni repeticiones, incluso con concurrencia. Un consecutivo repetido lo rechaza Hacienda; uno saltado hay que justificarlo.
La única excepción a todo lo anterior es el emisor compartido de pruebas: su serie es de
la plataforma y la comparten todas las cuentas de sandbox, así que ahí la numeración propia
se rechaza con shared_cert_numbering_is_automatic.
Sembrar el contador al migrar
PUT /emisores/{legalId}/consecutivo
{ "codeDoc": "01", "currentNumber": 4470, "branchCode": "001", "terminalCode": "00001" }
currentNumber es el último número ya emitido: el próximo comprobante sale con el
siguiente. branchCode y terminalCode son opcionales (se usan los del emisor) y la serie
es por ambiente, por sucursal, por terminal y por tipo de documento.
Vale en los dos modos, y es la única vía que puede hacer bajar el contador —la emisión
sólo lo sube—. Por eso existe: para corregir una siembra mal puesta. Si lo baja, la respuesta
trae aviso.code = consecutivo_marca_retrocedida, porque los números de en medio se vuelven
a repartir y los que Hacienda ya recibió volverán rechazados con -99.
Sucursales: la dirección de cada local
Los 3 primeros dígitos del consecutivo dicen de qué local salió el comprobante: 001 es
la casa matriz y 002 en adelante son las sucursales. Hacienda compara la ubicación del
<Emisor> con la que esa sucursal tiene inscrita en el RUT, así que una factura de la
002 tiene que llevar la dirección de la 002, no la de la casa matriz. Si no coincide,
Hacienda contesta con un aviso (-37); no rechaza el comprobante.
Hay dos formas, y puede usar la que le acomode (o las dos):
- Declararla una sola vez por sucursal y olvidarse (recomendado).
- Mandarla en cada comprobante, en el bloque
sucursal.
Si llegan las dos, gana la del comprobante. Si no hay ninguna, sale la del emisor.
Opción 1 · Declararla una vez:
PUT /emisores/3101000000/sucursales
{
"defaultBranchCode": "001", // la que se usa si la emisión no manda branchCode
"defaultTerminalCode": "00001",
"sucursales": [
{
"code": "001",
"nombre": "Casa matriz", // sin ubicacion → usa la del emisor
"terminales": [{ "code": "00001", "nombre": "Caja 1" }]
},
{
"code": "002",
"nombre": "Local Escazú",
"nombreComercial": "Comercio Ejemplo Escazú", // opcional, 3–80 caracteres
"ubicacion": {
"provincia": "1",
"canton": "02",
"distrito": "01",
"barrio": "San Rafael", // opcional, el NOMBRE (5–50)
"otrasSenas": "Centro comercial Plaza Real, local 14" // obligatorio, 5–250
},
"terminales": [
{ "code": "00001", "nombre": "Caja 1" },
{ "code": "00002", "nombre": "Caja 2" }
]
}
]
}
A partir de ahí no hay que hacer nada distinto al emitir: todo comprobante cuyo consecutivo
sea de la 002 sale con la dirección y el nombre comercial de la 002, en el XML y en
el PDF. Da igual cómo se numeró —por la plataforma con "branchCode": "002", con
consecutivoNumero o con la claveIntegrador completa—: manda el consecutivo final. En la
factura de compra (FEC) esos datos van en el <Receptor>, que es donde va usted.
-
GET /emisores/{legalId}/sucursalesdevuelve la lista conubicacionynombreComercial(null= esa sucursal usa los del emisor). -
En un
PUT, si una sucursal no traeubicaciononombreComercial, se conserva lo guardado. Para borrarlos y volver a los del emisor, mandenull. -
Declararlas no es obligatorio para emitir: un
branchCodeque no esté en la lista se sigue aceptando, y sale con la dirección del emisor (o la que mande en el comprobante). - El código
000no existe.
Opción 2 · Mandarla en el comprobante. Para el sistema que ya tiene la dirección de cada local en su base y prefiere mandarla siempre, sin configurar nada antes:
POST /documents/factura
{
"emisorLegalId": "3101000000",
"branchCode": "002",
"sucursal": { // opcional, solo para ESTE comprobante
"nombreComercial": "Comercio Ejemplo Escazú", // opcional, 3–80
"ubicacion": { // opcional; si viene, completa
"provincia": "1", "canton": "02", "distrito": "01",
"barrio": "San Rafael", // opcional
"otrasSenas": "Centro comercial Plaza Real, local 14"
}
},
"condicionVenta": "01",
"medioPago": ["01"],
"detalle": [ /* líneas */ ]
}
Va campo por campo: si manda solo nombreComercial, la dirección sigue saliendo de lo
declarado (o del emisor). Funciona en todos los tipos con dirección del emisor (FE, TE, NC,
ND, FEE y FEC); el Recibo de Pago y el Mensaje Receptor no la llevan.
⚠️ La que mande tiene que ser la que esa sucursal tiene inscrita en el RUT. La API no la
puede comparar contra el RUT antes de firmar; si no coincide, se entera por el aviso -37
de la respuesta de Hacienda.
Cajero y vendedor
Dos campos opcionales para todos los comprobantes salvo el Recibo de Pago y el Mensaje
Receptor (cuyo XML no tiene dónde ponerlos). No son datos fiscales: van en el bloque
<Otros> del XML, que Hacienda acepta y no interpreta, y se imprimen en el PDF y en el
tiquete junto a la condición de venta.
POST /documents/factura
{
"emisorLegalId": "3101000000",
"branchCode": "002",
"condicionVenta": "01",
"medioPago": ["01"],
"receptor": { "tipoIdentificacion": "02", "numeroIdentificacion": "3101000002", "nombre": "CLIENTE DE EJEMPLO S.A." },
"detalle": [{ "codigoCabys": "2999999999006", "cantidad": 2, "unidadMedida": "Unid",
"detalle": "Pan casero", "precioUnitario": 1500,
"impuesto": [{ "codigo": "01", "codigoTarifa": "08", "tarifa": 13 }] }],
"observaciones": "Pedido #4471",
"cajero": "Ana Ejemplo", // hasta 100 caracteres
"vendedor": "Luis Ejemplo" // hasta 100 caracteres
}
Así queda en el XML firmado:
<Otros>
<OtroTexto>Pedido #4471</OtroTexto>
<OtroTexto codigo="Cajero">Ana Ejemplo</OtroTexto>
<OtroTexto codigo="Vendedor">Luis Ejemplo</OtroTexto>
</Otros>
El punto de venta que imprime la clave antes de transmitir
Un POS cobra, imprime el tiquete y después transmite —a veces horas después, si se cayó el internet—. Para imprimir necesita la clave en ese momento.
Mandarla en piezas ya servía, pero así la clave se compone dos veces: usted para imprimirla y nosotros para firmarla. Si las dos versiones divergen, el papel que se llevó el cliente dice una clave y el XML legal dice otra, y nada lo delata.
Con claveIntegrador hay una sola versión: firmamos exactamente la que usted imprimió.
Ejemplo A — claveIntegrador
{
"emisorLegalId": "3101000002",
"codigoActividad": "4711.2",
// La clave que el POS compuso e imprimió en el tiquete
"claveIntegrador": "50627082600310100000200100009040000000015342240125",
"fechaEmision": "2026-08-27T11:45:00-06:00", // la de la OPERACIÓN, con hora
"condicionVenta": "01",
"currency": "CRC",
"exchangeRate": 1,
"receptor": {
"tipoIdentificacion": "01",
"numeroIdentificacion": "100000002",
"nombre": "Juan Ejemplo Ejemplo",
"correoElectronico": "juan@ejemplo.cr"
},
"detalle": [
{ "numeroLinea": 1, "codigoCabys": "0999999999002",
"cantidad": 0.5, "unidadMedida": "Kg",
"detalle": "CEBOLLA BLANCA A GRANEL", "precioUnitario": 346.55,
"impuesto": [{ "codigo": "01", "codigoTarifa": "02", "tarifa": 1 }] },
{ "numeroLinea": 2, "codigoCabys": "2999999999004",
"cantidad": 2, "unidadMedida": "Unid",
"detalle": "ARROZ BLANCO 1 kg", "precioUnitario": 1850,
"descuento": [{ "montoDescuento": 200, "codigoDescuento": "02" }],
"impuesto": [{ "codigo": "01", "codigoTarifa": "08", "tarifa": 13 }] }
],
"medioPago": [
{ "tipo": "02", "monto": 4000 },
{ "tipo": "01", "monto": 130.00775 }
],
"observaciones": "Venta offline #4471 - clave impresa en el tiquete"
}
La situacion no va en el payload: sale de la clave (dígito 42, acá 3 = sin internet).
Si la envía y contradice a la clave, manda la clave y la respuesta trae un aviso
(clave_integrador_situacion_ignorada). La sucursal y la terminal también salen de la clave.
fechaEmision sí va, porque la clave sólo lleva el día y el XML necesita la hora.
Ejemplo B — las piezas
El mismo tiquete con la clave compuesta por la API. Sirve cuando su numeración es propia pero no necesita imprimir la clave antes de transmitir:
{
"emisorLegalId": "3101000002",
"codigoActividad": "4711.2",
// Las piezas, en vez de la clave entera
"branchCode": "001",
"terminalCode": "00009",
"consecutivoNumero": "0000000016",
"codigoSeguridad": "42240125",
"situacion": "3",
"fechaEmision": "2026-08-27T11:45:00-06:00",
"condicionVenta": "01",
"currency": "CRC",
"exchangeRate": 1,
"receptor": {
"tipoIdentificacion": "01",
"numeroIdentificacion": "100000002",
"nombre": "Juan Ejemplo Ejemplo",
"correoElectronico": "juan@ejemplo.cr"
},
"detalle": [
{ "numeroLinea": 1, "codigoCabys": "0999999999002",
"cantidad": 0.5, "unidadMedida": "Kg",
"detalle": "CEBOLLA BLANCA A GRANEL", "precioUnitario": 346.55,
"impuesto": [{ "codigo": "01", "codigoTarifa": "02", "tarifa": 1 }] },
{ "numeroLinea": 2, "codigoCabys": "2999999999004",
"cantidad": 2, "unidadMedida": "Unid",
"detalle": "ARROZ BLANCO 1 kg", "precioUnitario": 1850,
"descuento": [{ "montoDescuento": 200, "codigoDescuento": "02" }],
"impuesto": [{ "codigo": "01", "codigoTarifa": "08", "tarifa": 13 }] }
],
"medioPago": [
{ "tipo": "02", "monto": 4000 },
{ "tipo": "01", "monto": 130.00775 }
],
"observaciones": "Venta offline #4472"
}
Los dos producen exactamente el mismo comprobante. Ambos fueron emitidos y aceptados por Hacienda, y sus totales coinciden al último decimal:
TotalVenta 3873.27500 ← 173.275 (cebolla) + 3700 (arroz)
TotalDescuentos 200.00000
TotalVentaNeta 3673.27500
TotalImpuesto 456.73275 ← 1.73275 al 1% + 455 al 13%
TotalComprobante 4130.00775 ← y la suma de los medioPago da exactamente esto
Con pago mixto, Σ
medioPago[].montodebe dar elTotalComprobanteexacto. Acá son ₡4 000 con tarjeta más ₡130,00775 en efectivo. Si no cuadra, la API responde400diciendo cuál es el total que espera.
Si lee el XML firmado: los totales condicionales del resumen (
TotalServGravados,TotalMercanciasExentas,TotalExonerado,TotalNoSujeto,TotalDescuentos,TotalImpuesto,TotalOtrosCargos,TotalIVADevuelto,TotalImpAsumEmisorFabrica…) solo vienen cuando alguna línea cae en esa categoría, como pide el Anexo v4.4. Un total que no viene vale 0: no lo trate como un error. Los totales del JSON de la API vienen siempre.
Cómo se arma la clave
506 + ddmmaa + cédula(12) + consecutivo(20) + situación(1) + códigoSeguridad(8)
506 270826 003101000002 00100009040000000011 3 42240125
y el consecutivo de 20 es:
branchCode(3) + terminalCode(5) + tipoDoc(2) + consecutivoNumero(10)
001 00009 04 0000000011
Tipo de documento: 01 factura · 02 nota de débito · 03 nota de crédito · 04 tiquete
· 08 factura de compra · 09 exportación · 10 recibo de pago.
⚠️ Las dos trampas que producen una clave válida pero equivocada:
La fecha va en hora de Costa Rica, no en UTC. Son los dígitos 4 a 9. Un servidor en UTC que la calcule a partir de las 18:00 hora tica genera el día siguiente.
La cédula se rellena a 12 con ceros a la izquierda. 3101000002 son 10 dígitos y en la
clave va 003101000002.
Las dos se validan antes de firmar.
Errores de numeración
Ninguno de estos gasta consecutivo: se cortan antes de generarlo.
| Código | Qué pasó |
|---|---|
clave_integrador_cedula_mismatch |
La cédula de las posiciones 10-21 no es la del emisor que firma |
clave_integrador_fecha_mismatch |
El ddmmaa de la clave no cuadra con fechaEmision (¿la calculó en UTC?) |
clave_tipo_documento_mismatch |
El tipo de la clave no corresponde al endpoint: una clave …04… es un tiquete y va a POST /documents/tiquete |
codigo_seguridad_requiere_consecutivo |
Mandó codigoSeguridad suelto: sin el consecutivo la clave la compone la API y su código se descartaría |
consecutivo_required_in_integrator_mode |
El emisor está en modo integrator y la emisión no trae número |
shared_cert_numbering_is_automatic |
Numeración propia sobre el emisor compartido de pruebas |
reserved_clave_not_found |
Mandó en clave una que compuso usted. Para eso es claveIntegrador |
consecutivo_duplicate (409) |
Ese número ya se emitió por esta API |
Y los avisos, que no bloquean y viajan en warnings de la respuesta:
consecutivo_por_debajo_de_la_marca, consecutivo_salto_grande,
clave_integrador_situacion_ignorada.
Reserva previa de clave
Si necesita imprimir o mostrar la clave antes de emitir, pero prefiere no componerla:
POST /clave/reserve → { "clave": "...", "consecutivo": "..." }
Luego envíe esa clave en el payload de emisión. El comprobante usará la reservada en vez
de generar una nueva.
Idempotencia
Los POST de emisión exigen la cabecera Idempotency-Key:
Idempotency-Key: pedido-2026-07-000123
| Situación | Resultado |
|---|---|
| Misma clave, mismo body | Devuelve el resultado original. No se emite de nuevo. |
| Misma clave, body distinto | 409 idempotency_conflict |
| Misma clave, todavía procesándose | 409 idempotency_in_progress |
| Clave nueva | Emisión nueva |
La ventana es de 24 horas.
Derive la clave de su dominio, no de su reintento. Use
pedido-4471,orden-2026-07-000123: algo que identifique la venta. Si genera un UUID nuevo en cada intento, la idempotencia no lo protege de nada — que es justamente el escenario que la hace necesaria: su proceso envía la factura, se cae antes de guardar la respuesta y reintenta. Con una clave estable, el reintento devuelve la factura que ya existe. Sin ella, factura dos veces al mismo cliente.
Los 429 y los 5xx son seguros de reintentar con la misma clave: si la petición no llegó
a emitir, no consumió consecutivo.
La clave se reserva ANTES de emitir
Apenas recibimos la petición, la clave queda tomada. No se libera hasta que la emisión termina, bien o mal. Esto importa porque emitir no es instantáneo: hay que firmar, tomar el consecutivo y hablar con Hacienda.
Si durante esa ventana llega otra petición con la misma clave, recibe:
{
"error": "idempotency_in_progress",
"message": "La Idempotency-Key \"pedido-4471\" está siendo procesada en este momento.",
"retryAfterSeconds": 5
}
No es un error de su lado, y no significa que la emisión haya fallado. Significa que ya hay una en curso con esa clave. Espere unos segundos y vuelva a pedir con la misma clave: si el comprobante se emitió, va a recibir su respuesta original.
Lo que este 409 evita es el caso que rompe una integración de verdad: su proceso manda la factura, se cae antes de recibir la respuesta, y reintenta. Sin la reserva previa no había rastro de la clave y se emitía un segundo comprobante para la misma venta, con su propio consecutivo y su propio cobro.
Qué pasa cuando la emisión falla
| Resultado | Qué pasa con la clave |
|---|---|
2xx |
Queda tomada con la respuesta guardada. Reintentar devuelve esa respuesta. |
4xx / 5xx |
Se libera. Puede reintentar con la misma clave y el payload corregido. |
| El proceso se cae a mitad | Queda reservada ~2 minutos y después se libera sola. |
Que un error libere la clave es deliberado: si un 400 por un CABYS mal puesto la dejara
tomada, usted tendría que inventar una clave nueva por cada corrección, y perdería la
protección justo cuando más la necesita.
Comprobantes recuperados
Si nuestro proceso se interrumpe mientras firma su comprobante, lo terminamos solo —
con la misma clave numérica y el mismo consecutivo que ya se habían reservado. Usted
recibe un webhook document.queued con "recuperado": true y el documento sigue su curso
normal.
No hay que hacer nada de su lado, y no queda un salto en su numeración.
Probar sin certificado
Conseguir el .p12 de sandbox y las credenciales TRIBU-CR de stag toma días. Para que eso no
frene su integración, podemos habilitarle un emisor de pruebas de la plataforma: emite
contra el sandbox de Hacienda con nuestra cédula, y usted no sube ni configura nada.
Ya está habilitado en su cuenta: no hay nada que pedir ni que configurar. Aparece en
GET /certificates junto a sus emisores, marcado "shared": true, con el identificador
EMISORPRUEBA. Emita poniéndolo en emisorLegalId:
curl -X POST https://api.facturaencr.com/v2/efactura/documents/tiquete \
-H "X-API-Key: $EFACTURA_KEY" -H "X-API-Secret: $EFACTURA_SECRET" \
-H "Idempotency-Key: $(uuidgen)" -H "Content-Type: application/json" \
-d '{ "emisorLegalId": "EMISORPRUEBA", "condicionVenta": "01",
"medioPago": ["01"], "detalle": [ … ] }'
EMISORPRUEBA no es una cédula: es el identificador con el que la plataforma publica
este emisor. El backend lo traduce al certificado y firma con la identidad real, que
usted no necesita conocer. Todo lo demás del payload funciona igual que con un emisor
suyo.
Recibe un 202 con su clave y consecutivo, y a los pocos segundos el veredicto real de
Hacienda: accepted o rejected con su motivo. Es una emisión de verdad contra sandbox,
no un simulador — que es justamente el punto: lo que pasa acá es lo que va a pasar en
producción.
Lo que cambia respecto a usar su propio certificado:
| Con el emisor de pruebas | Con su certificado | |
|---|---|---|
| Ambiente | Solo sandbox | Sandbox y producción |
| Sucursal / terminal / consecutivo | Los asigna la plataforma | Los elige usted |
| Descargar XML firmado y respuesta | No | Sí |
| Sí, con la identidad del emisor tapada | Completo | |
| Datos del emisor en la ficha | "Emisor de pruebas" |
Los suyos |
codigoActividad en el payload |
Se ignora: usa la del emisor | La que usted elija |
Los tres motivos, por si le sirven:
-
La numeración es automática. Cada cuenta recibe su propia sucursal y terminal dentro del emisor compartido —a partir de
050/00999— y por eso dos integradores no pueden generar el mismo consecutivo sobre la misma cédula. Si mandabranchCode,terminalCode,consecutivoNumero,codigoSeguridadoclaveIntegrador, la respuesta es400 shared_cert_numbering_is_automatic— se rechaza en vez de ignorarse en silencio, para que no crea que se aplicó. Es el único emisor donde la numeración propia no se admite. -
El XML firmado no se descarga (
403 shared_test_emisor_no_download). Lleva una firma digital real de nuestra cédula sobre contenido que escribió usted. El estado, la respuesta de Hacienda y el PDF sí los tiene. -
La identidad del emisor va tapada. En el PDF la razón social sale como
PRUEBAy la cédula comoCert de prueba; la ubicación y la actividad económica no se imprimen. El resto del comprobante —sus líneas, sus impuestos, sus totales— es suyo y se ve completo. Si mandacodigoActividadse ignora y se usa la del emisor: las actividades inscritas son un dato de su dueño, y usted igual no puede elegir entre ellas.Una cosa que no podemos tapar: la clave de 50 dígitos lleva la cédula del emisor embebida (posiciones 9-21). Es el formato de Hacienda y la clave hay que devolvérsela — sin ella usted no puede consultar su comprobante ni rastrearlo.
Su certificado siempre manda. Si sube uno propio para la misma cédula, la API usa el suyo y el compartido deja de aparecerle — sin que cambie una línea de código, y sin las restricciones de arriba (podrá descargar el XML y elegir su numeración).
Si alguna cuenta abusa del emisor compartido, podemos desactivárselo puntualmente; el resto lo sigue teniendo.
Ambientes
Hay una sola URL base:
https://api.facturaencr.com/v2/efactura
El ambiente no se elige por subdominio ni por parámetro: lo determina el campo
environment de la API Key que usa (y, en su defecto, el del certificado).
| Sandbox | Producción | |
|---|---|---|
| Destino | Sandbox oficial de Hacienda | Hacienda producción |
| Valor fiscal | Ninguno | Sí, es una factura real |
| Certificado | .p12 de sandbox del BCCR |
.p12 de producción |
| Cobro | Progresión propia, separada | Tramos de producción |
| Numeración | Serie propia, separada | Su serie fiscal real |
Esto significa que la misma petición, con otra llave, emite de verdad. Antes de mover
algo a producción, confirme con POST /auth/verify qué llave tiene cargada su aplicación.
La numeración de sandbox y producción es independiente. Puede probar todo lo que quiera en sandbox sin mover un dígito de su consecutivo fiscal: cada ambiente lleva su propia serie por sucursal/terminal y tipo de comprobante. La primera emisión de pruebas de un emisor arranca su serie de sandbox desde el número que ya tenía en producción, así que tampoco repite una clave.
Un certificado de sandbox no puede emitir en producción ni al revés: la combinación se valida antes de firmar.
Límites
-
Rate limiting por API key. Al excederlo:
429conRetry-After. Respételo; no reintente de inmediato. -
Aplane las ráfagas. Si tiene que emitir mil comprobantes, distribúyalos en el tiempo
en lugar de dispararlos en paralelo. Hacienda responde
202sin cabeceras de rate limit, así que su límite real es de comportamiento: quien ráfaguea termina bloqueado. - Máximo 1 000 líneas por comprobante, 15 otros cargos, 5 impuestos por línea, 4 medios de pago, 10 referencias.
- PDF adjunto máximo 15 MB.
Retención y disponibilidad
Los XML firmados y las respuestas de Hacienda se conservan cinco años, como exige la
normativa. Cada documento expone su retentionUntil.
Aun así, archive sus propios XML. Son la prueba fiscal de sus operaciones y no conviene que su única copia viva en un tercero.
La clave de 50 dígitos
Identifica el comprobante ante Hacienda de forma única y permanente.
506 240726 003101000000 00100001010000000866 1 42351111
│ │ │ │ │ └── código de seguridad (8)
│ │ │ │ └──────── situación: 1 normal, 2 contingencia, 3 sin internet
│ │ │ └───────────────────── consecutivo (20)
│ │ └───────────────────────────────────── cédula del emisor, 12 con ceros a la izquierda
│ └─────────────────────────────────────────────── fecha DDMMAA
└───────────────────────────────────────────────────── código de país
Guárdela: es la llave para consultar (GET /documents/clave/{clave}), para referenciar en
notas de crédito y débito, y para responder con un Mensaje Receptor.
Si la compone usted —un POS que la imprime antes de transmitir—, mándela entera en
claveIntegrador: vea Numeración propia y modo integrator.
Códigos de error
HTTP
| Código | Significado | Acción |
|---|---|---|
200 / 201 |
OK | — |
202 |
Aceptado y encolado | Esperar veredicto |
400 |
Payload inválido o regla de negocio | Corregir y reenviar. No consumió consecutivo. |
401 |
Credenciales ausentes o inválidas | Revisar cabeceras |
403 |
Scope insuficiente, cuenta suspendida, plan sin API, descarga de XML con el emisor de pruebas, o certificado_de_otro_ambiente (llave de un ambiente con el certificado del otro) |
Revisar scopes y estado; emitir con la llave del mismo ambiente que el certificado |
404 |
No existe en su cuenta | Revisar el id |
409 |
Idempotency-Key reusada con otro cuerpo, o MR ya enviado |
Cambiar la clave |
413 |
PDF adjunto de más de 15 MB | Reducir el PDF |
422 |
Regla fiscal incumplible | Leer el mensaje |
429 |
Rate limit | Respetar Retry-After y reintentar con la misma clave |
500 / 503 |
Fallo nuestro. 503 servicio_no_disponible: el servicio se está reconectando a su base de datos |
Reintentar con backoff y la misma Idempotency-Key |
Certificado que no puede firmar (422)
Se responden antes de reservar el consecutivo y de firmar: no se usa ningún número ni se cobra.
error |
Qué pasó | Cómo se arregla |
|---|---|---|
certificado_vencido |
La llave criptográfica (.p12) del emisor venció (notAfter). |
Generá una llave nueva en ATV y subila con POST /certificates (reemplaza a la actual). |
certificado_no_vigente |
La llave todavía no está vigente (notBefore en el futuro). |
Esperá a la fecha de inicio o subí otra. |
certificado_inactivo |
El certificado está marcado como vencido o revocado. | Subí uno vigente. |
certificado_bloqueado |
Hacienda rechazó un comprobante de este emisor por la firma o el certificado (por ejemplo «El certificado empleado se encuentra revocado» o «verifique que esté firmando con la llave correcta [STAGING o PRODUCCION]»). Desde ese rechazo no se emite más con esa llave. Un rechazo por otro motivo no bloquea. En GET /certificates el certificado queda con estadoFirma: "bloqueado" y el detalle en bloqueo. |
Subí una llave nueva y vigente; al reemplazarla se desbloquea. details.documento trae la clave del comprobante que lo disparó. En el momento del bloqueo se envía el webhook certificate.blocked (también a quien escucha document.rejected) y un correo al dueño de la cuenta. |
certificado_ambiente_incorrecto |
La llave (.p12) o el usuario de Hacienda del certificado son del otro ambiente (por ejemplo, un .p12 de pruebas o un usuario @stag en un certificado de producción). Hacienda lo rechazaría. details trae environment, llave y credenciales. |
Subí de nuevo el certificado con la llave, el usuario y la contraseña del ambiente en que emitís (POST /certificates); reemplaza al actual. |
Las llaves que emite Hacienda (ATV) no publican CRL ni OCSP consultables (verificado: el certificado trae solo el acceso «CA Issuers»), así que una revocación se conoce por el rechazo de Hacienda, no antes.
Errores de Hacienda
Aparecen en haciendaMessage cuando status es rejected. Los que más se ven:
| Código | Qué pasó | Cómo se arregla |
|---|---|---|
| -1 | Violación del esquema XSD | Casi siempre una línea sin impuesto o un enum inválido |
| -37 | Provincia/cantón/distrito del emisor no coinciden con el padrón. No bloquea: el sandbox lo agrega de acompañante y nunca es el motivo real | Ignórelo y busque el OTRO código del mensaje, que es el que rechaza. Si igual quiere limpiarlo: PATCH /emisores/:legalId/config con la ubicacion exacta de TRIBU-CR |
| -38 | DIMEX o NITE inexistente | Verificar la cédula del receptor |
| -41 | Descuento sin codigoDescuento |
Añadirlo |
| -45 | Monto de impuesto por línea mal calculado | Revisar base × tarifa |
| -111 | Clasificación bien/servicio inconsistente | Hacienda la re-deriva del CABYS |
| -125 | Exoneración inválida | Revisar documento y montos |
| -131 | REP sin codigo o razon en la referencia |
La API los deriva |
| -190 | MontoExoneracion no cuadra con TarifaExonerada |
Recalcular |
| -400 | CABYS inexistente | Validar contra GET /catalogs/cabys |
| -451 | ivaCobradoFabrica: "02" sin tarifa exenta |
Usar codigoTarifa: "10" |
| -454 | Base imponible mal armada | Revisar qué impuestos engrosan la base |
| -459 | Código 05 sin volumenUnidadConsumo |
Añadirlo |
| -471 / -473 | Alcohol absoluto mal calculado | Dividir entre 100 |
| -478 | Exoneración sin articulo |
Los tipos 02, 03, 06, 07 y 08 lo exigen |
| -479 | Exoneración con articulo pero sin inciso |
El inciso es 0 si el artículo no tiene incisos, no se omite. La API lo rellena |
| -496 | IVA devuelto: no hay pago con tarjeta, o el monto no es el que Hacienda recalcula | Exige un medioPago "02". Con pago mixto Hacienda prorratea y el mensaje trae entre paréntesis el monto que espera. Mande totalIVADevuelto: "auto" |
| -505 | Tarifa transitoria en factura o tiquete | Solo valen en NC/ND |
| -523 | IVA devuelto obligatorio: salud (CAByS 931…) al 4 % pagada con tarjeta sin TotalIVADevuelto, o con 0; o con monto en una línea exonerada del todo |
Mande totalIVADevuelto: "auto": incluye el IVA asumido de una línea regalada y da 0 si va exonerada. Sin el campo, lo cortamos con 400 IVA_DEVUELTO_REQUERIDO |
| -508 | El receptor no coincide con el del documento referenciado | En un REP, todas las referencias deben ser de comprobantes del mismo cliente |
| -509 | CABYS de la NC/ND ausente en el documento referenciado | Use los mismos CABYS; para algo nuevo, emita una factura aparte |
| -58 | Estructura inválida del plazo de crédito | Con condicionVenta: "02", plazoCredito es obligatorio y es un string |
| -513 | Línea sin impuesto de la familia IVA | Declarar IVA aunque sea exento |
Cuando un comprobante sale rejected, GET /documents/{id} trae además un objeto
rechazo ya masticado: el resumen encabezado por el código que de verdad bloqueó, y
cada código con su explicacion, su comoArreglarlo y —cuando corresponde—
bloqueaPorSiSolo: false, que marca los acompañantes:
{
"status": "rejected",
"rechazo": {
"resumen": "-400: El código CAByS no existe en el catálogo.",
"codigos": [
{ "codigo": "-400", "mensajeHacienda": "…", "explicacion": "…", "comoArreglarlo": "Validá el código contra GET /catalogs/cabys…" },
{ "codigo": "-37", "mensajeHacienda": "…", "explicacion": "…", "bloqueaPorSiSolo": false }
],
"siguientePaso": "Un comprobante rechazado no se corrige ni se reenvía: emitilo de nuevo con los datos arreglados."
}
}
Un rechazo suele traer varios códigos y no todos son la causa. El
-37es el caso claro: medido sobre 426 rechazos de 30 días apareció en 306, pero nunca solo — el 100 % de las veces venía acompañado del código que de verdad rechazaba, y 301 de esos 306 eran de sandbox. Los mismos emisores emiten sin problema. Antes de perseguir un código, mire si aparece solo en el documento.
Un rejected no se corrige ni se reintenta: emita un comprobante nuevo con los datos
arreglados. El consecutivo rechazado queda registrado como tal.
Sustituir un comprobante rechazado
Un comprobante que Hacienda rechazó no tiene validez. El Reglamento de Comprobantes Electrónicos (Decreto 44739-H, art. 19) pide dos cosas:
- emitir de inmediato un comprobante nuevo, con consecutivo nuevo;
- que ese comprobante indique cuál está sustituyendo.
Y no se hace nota de crédito sobre el rechazado (art. 10): no existe fiscalmente, así
que no hay nada que anular. La API corta con 422 reference_rejected una nota que lo
intente.
Hay dos formas de hacerlo; las dos dejan la misma referencia en el XML:
POST /documents/{id}/reemitir ← mismo tipo que el rechazado; cuerpo = comprobante corregido
POST /documents/tiquete ← cualquier tipo, con "sustituye": "<clave del rechazado>"
| Campo de la referencia | Valor |
|---|---|
tipoDocumento (TipoDocIR) |
10 · Comprobante electrónico rechazado por el Ministerio de Hacienda |
codigo |
16 · Sustituye comprobante electrónico rechazado |
numero |
la clave de 50 del rechazado |
El tipo tiene que ser
10, no01. Con01(factura), Hacienda busca la clave entre los comprobantes válidos, no la encuentra y rechaza también el nuevo con -29 «La clave numérica de referencia no existe». Verificado en sandbox sobre la misma factura rechazada: tiquete con10+16aceptado; con01+16, rechazado con -29. Si arma la referencia a mano y apunta a un comprobante suyo que Hacienda rechazó, la API corrige el tipo a10y lo avisa enwarnings(referencia_rechazado_tipo_10).
Antes de pasar a producción
-
POST /auth/verifydevuelve la llave de producción que espera. - El certificado
.p12es el de producción y no vence pronto (notAfter). -
provincia,cantonydistritodel emisor coinciden con el padrón de Tributación (si no, Hacienda agrega el aviso -37 a sus respuestas; no bloquea, pero ensucia el diagnóstico de los rechazos reales). -
Todos los CABYS de su maestro de productos devuelven resultado en
GET /catalogs/cabys. - Ninguna línea puede salir con
impuestovacío o ausente. -
Idempotency-Keyderivada de su número de pedido, no de un UUID por reintento. - Webhook registrado, firma verificada y handler idempotente.
-
Si algún emisor es de régimen simplificado, su conciliación espera
totalImpuesto: 0. - Sabe qué hace su sistema ante un
rejected. - Guarda
documentId,claveyconsecutivode cada emisión.
Soporte
Al reportar un problema, incluya el requestId de la respuesta y, si aplica, la
clave del comprobante. Con eso ubicamos la petición exacta y su traza completa.
- Correo: soporte@facturaencr.com
- Especificación en crudo:
GET /docs/openapi.yaml·GET /docs/openapi.json - Colección Postman lista para importar:
GET /docs/postman.json - Esta guía en Markdown:
GET /docs/guia.md - Todo junto en un ZIP:
GET /docs/bundle.zip