v2.0.0
OpenAPI 3.1.0

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 con GET /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, canton y distrito deberí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

  1. Cree la llave nueva desde el portal.
  2. Despliéguela a sus servidores.
  3. Confirme con POST /auth/verify que responde ok: true.
  4. Espere 24–48 h a que no queden peticiones en vuelo con la vieja.
  5. 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:

  1. path usa notación de puntos con índice numérico — detalle.0.impuesto.1, no detalle[0].impuesto[1]. Viene vacío ("") cuando la regla que falló es transversal y no pertenece a un campo puntual.

  2. msg y message traen el mismo texto, duplicado por compatibilidad histórica. Lea message.

  3. Hay dos convenciones de código de error. Las validaciones de esquema devuelven validation_error en minúscula con details; las reglas de negocio devuelven un código propio en mayúscula y sin details, 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: details puede 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.

HaciendaAPISu sistemaValida · genera clave yconsecutivoXML v4.4 · firma XAdES-EPESAsíncrono (5 a 60 segundos)alt[Aceptado][Rechazado]POST /documents/factura202 Accepted + clave + consecutivoXML firmadoRespuesta fiscalwebhook document.acceptedwebhook document.rejected

202 no 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 un 202: hágalo contra status: "accepted".

Estados

queued

signing

sent

polling

accepted

rejected

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 accepted y rejected son 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 el documentId junto 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, TotalComprobante y 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 con InformacionReferencia sin Codigo o sin Razon se 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í subtotal es 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 19 y el 20. 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 con 19—. El 01 tambié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 el 19: 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 codigoCabys y misma partidaArancelaria. 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 13 es 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, fechaEmision de 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 en 16.

El 13 y el 14 cambian el período. A diferencia del 01, el 02 y el 06 —que pesan en el mes de la nota—, una nota con código 13 o 14 refleja 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 13 de codigo (anula por error material) con el 13 de tipoDocumento (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ó al 02.

Los códigos 13 a 16 ya 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ó el 12—. Hubo un período en que el validador de Hacienda los aceptaba y la tabla publicada llegaba solo hasta el 12; esta doc los dejaba entonces sin descripción. Ya no hace falta: los cuatro están en la tabla de arriba.

El código 17 no 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.codigoActividad es obligatorio (el del vendedor).
  • referencia es obligatoria, pero a diferencia de NC/ND, numero no 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:

  1. El IVA — siempre, sin excepción.
  2. 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, 10 ni 11. 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, 06 y 07 solo 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 con 400, 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):

  • cantidadUnidadMedida son litros. Si no la manda, se usa la cantidad de la línea. Vendiendo por galones o por monto («₡20.000 de súper»), mande los litros en cantidadUnidadMedida.
  • 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 aviso monto_total_pactado.
  • impuestoUnidad (₡ por litro) es obligatorio y mayor que 0. Solo monto no alcanza: Hacienda rechaza con -468.
  • El combustible es exento de IVA: lleve el IVA con codigoTarifa 10.
  • Se puede exonerar, como dice el Anexo: la exoneración va dentro del 03 con tarifaExonerada, 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). Sin tarifaExonerada la respuesta es 400 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 impuesto 01.
  • 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): tipoTransaccion 02 en la línea. Devolución de litros: nota de crédito con código de referencia 06.

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) y 03 (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: use 02 (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:

  1. «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.

  2. «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 espere totalImpuesto: 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:

  1. contribuyente: false con encontrado: true no es lo mismo que con encontrado: false. El primero existe pero no está inscrito (el motivo textual de Hacienda viene en situacion.mensaje); el segundo no existe.
  2. contribuyente: null significa no verificable ahora, no "no". No lo trate como un no.
  3. 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_response o not_applicable.
  • GET /documents/{id}/receiver-mr — la respuesta estructurada.
  • GET /documents/{id}/receiver-mr/xml — el XML firmado del receptor.
  • Webhook document.receiver_mr_received en 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 422 con la lista errores (fila = posición en items, campo = su ref) y no emite nada.
  • confirmar: true (por defecto) lo pone en cola de una vez. Con false queda validado 24 horas para revisarlo y confirmarlo con POST /lotes/{id}/confirmar.
  • tipo: factura, tiquete, nota_credito, nota_debito, recibo_pago, factura_compra o factura_exportacion.
  • Cada comprobante emitido dispara sus webhooks de siempre (document.accepted, document.rejected). El avance del lote: GET /lotes/{id} (conteos) y GET /lotes/{id}/items?estado=error,rechazado (fila por fila, con clave y consecutivo).
  • POST /lotes/{id}/cancelar detiene lo pendiente (lo emitido queda) y POST /lotes/{id}/reanudar retoma uno pausado (p. ej. por saldo).
  • También acepta la plantilla de Excel del portal: GET /lotes/plantilla y POST /lotes/excel (multipart, campo file), que queda validado hasta 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-previa devuelve 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" con tipoCambioFijo.
  • PATCH /recurrentes/{id} pausa (estado: "pausada"), reanuda o edita; DELETE la 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_uri tiene 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 un code_verifier aleatorio (43 a 128 caracteres) que usted guarda en su servidor. Solo se acepta S256.

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.id es estable: úselo para reconocer al usuario la próxima vez que inicie sesión.
  • acceso viene solo con emitir. Desde ahí emite como cualquier llave de desarrollador, con X-Cuenta: <acceso.valor>. emisores vací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/token lleva 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. http solo se acepta en localhost, 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 el Referer.
  • 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) y emitir. Sin emitir, 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-Key y X-API-Secret, más X-Cuenta si 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

  1. Preparar. preparar_factura, preparar_tiquete, preparar_anulacion o preparar_lote validan 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.
  2. Emitir. emitir_comprobante (o emitir_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[].tipoDocumento y referencia[].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, 99
Tiquete (04), Compra (08), Exportación (09) igual, pero sin el 12
Notas de crédito y débito (03, 02) todos, 01–15 y 99
Recibo Electrónico de Pago (10) solo 09 y 11

Los códigos 09 y 11 corresponden al pago de una factura emitida con 08 y con 10 respectivamente: 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:

  • plazoCredito es un string, no un número. Va en días (v4.4 cambió la unidad; antes eran meses). "30", "60", "90". Enviar 30 sin comillas devuelve 400 · "plazoCredito" must be a string.
  • condicionVentaOtros (5–100 caracteres) es obligatorio con condicionVenta: "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 08 no existe en v4.4. La API lo rechaza con 400.

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:

  • 05 como 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 exige otrasSenasExtranjero (-505);
    • con 06, exige condicionVenta: "13" (-502);
    • con los dos, no se manda codigoActividad, porque no tienen actividad inscrita (-407).

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

nombreInstitucion espera 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, 09 y 10 determinan 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:

  1. 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 TotalMercanciasGravadas o a TotalServGravados. Hacienda re-deriva esta clasificación del CABYS: si su tipoVenta la 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…).

  2. 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 es top, no limit: limit se 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 CAByS

Si carga productos desde un maestro propio, páselo una vez por GET /catalogs/cabys?q={codigo} y marque los que devuelvan items: []. 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:

  1. No valide con ^\d+$. El patrón correcto es [0-9A-Za-z] con la longitud del tipo.
  2. No guarde la cédula como número. Un INT o un parseInt destruye las letras y, de paso, los ceros a la izquierda.
  3. 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).

clave y claveIntegrador son 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}/consecutivo o 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):

  1. Declararla una sola vez por sucursal y olvidarse (recomendado).
  2. 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}/sucursales devuelve la lista con ubicacion y nombreComercial (null = esa sucursal usa los del emisor).
  • En un PUT, si una sucursal no trae ubicacion o nombreComercial, se conserva lo guardado. Para borrarlos y volver a los del emisor, mande null.
  • Declararlas no es obligatorio para emitir: un branchCode que 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 000 no 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[].monto debe dar el TotalComprobante exacto. Acá son ₡4 000 con tarjeta más ₡130,00775 en efectivo. Si no cuadra, la API responde 400 diciendo 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í
PDF 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 manda branchCode, terminalCode, consecutivoNumero, codigoSeguridad o claveIntegrador, la respuesta es 400 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 PRUEBA y la cédula como Cert 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 manda codigoActividad se 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: 429 con Retry-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 202 sin 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 -37 es 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, no 01. Con 01 (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 con 10 + 16 aceptado; con 01 + 16, rechazado con -29. Si arma la referencia a mano y apunta a un comprobante suyo que Hacienda rechazó, la API corrige el tipo a 10 y lo avisa en warnings (referencia_rechazado_tipo_10).



Antes de pasar a producción

  • POST /auth/verify devuelve la llave de producción que espera.
  • El certificado .p12 es el de producción y no vence pronto (notAfter).
  • provincia, canton y distrito del 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 impuesto vacío o ausente.
  • Idempotency-Key derivada 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, clave y consecutivo de 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
Server:https://api.facturaencr.com/v2/efactura

Base URL única (el ambiente se decide por la API Key utilizada)

Client Libraries

Emisión de comprobantes

​

Emitir los ocho tipos de comprobante, consultar su estado y descargar XML, PDF y la respuesta de Hacienda.

Emitir es lo que su sistema va a hacer mil veces al día. Esta sección cubre el flujo completo: qué se envía, qué se recibe, cuál comprobante corresponde a cada situación y en qué se tropieza cada tipo.

Todos los ejemplos de esta página fueron emitidos contra el sandbox oficial de Hacienda y aceptados. Cada endpoint trae al menos uno sencillo y uno completo.

Los endpoints están abajo. Cada uno trae ejemplos listos para copiar: uno sencillo para el caso corriente, uno completo con varias líneas e impuestos, y una referencia con todos los campos que acepta.

La explicación de cada tipo de comprobante —cuál corresponde a cada situación y en qué se tropieza— está arriba, en la sección «Emisión de comprobantes» de la guía.