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.

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.



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": "6a12352db0dcdddbf99c91f9", "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=Panadería Undamo (producción)" \
  -F "environment=production" \
  -F 'emisor={"tipoIdentificacion":"02","numeroIdentificacion":"3101678166","nombre":"UNDAMO DE ALAJUELA SOCIEDAD ANONIMA","codigoActividad":["1071.9"],"correoElectronico":"facturacion@undamo.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 plataformaemisorLegalId: "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": "3101678166",
    "condicionVenta": "01",
    "medioPago": ["01"],
    "receptor": {
      "tipoIdentificacion": "01",
      "numeroIdentificacion": "112340567",
      "nombre": "Juan Pérez",
      "correoElectronico": "juan@cliente.com"
    },
    "detalle": [
      {
        "cantidad": 1,
        "unidadMedida": "Unid",
        "codigoCabys": "2349002011500",
        "detalle": "Pan pita 400g",
        "precioUnitario": 1000,
        "impuesto": [ { "codigo": "01", "codigoTarifa": "08", "tarifa": 13 } ]
      }
    ]
  }'
{
  "documentId": "6a640c68a06e822633e9db71",
  "clave": "50624072600310167816600100001010000000866142351111",
  "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 de 8 caracteres 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": "6a640c68a06e822633e9db71",
  "clave": "50624072600310167816600100001010000000866142351111",
  "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éricodetalle.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 emisor está en consecutivoMode: "integrator", 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.

// 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": "3101678166",   // cédula del comercio (recomendado)
  // "certificateId": "6a4d7b00899e08b9a0d4011f",

  // ── Condiciones comerciales ──
  "condicionVenta": "01",           // 01 = contado
  "medioPago": ["01"],              // 01 = efectivo
  "currency": "CRC",

  // ── A quién ──
  "receptor": { "tipoIdentificacion": "01", "numeroIdentificacion": "112340567", "nombre": "Juan Pérez" },

  // ── Qué ──
  "detalle": [ /* líneas */ ],

  // ── Opcionales ──
  "otrosCargos": [],
  "observaciones": "Pedido #4471"
}

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 Consecutivos).
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": "3101678166",   // cédula del comercio (recomendado)
  "certificateId": "6a4d7b00…",    // o el id de un certificado concreto
  "environment": "production",     // opcional: fuerza el ambiente del certificado

  // ── Numeración — opcional; la plataforma la administra ────────────────────
  "clave": "506…",                 // clave pre-reservada (POST /clave/reserve)
  "branchCode": "001",             // sucursal  (default: la del emisor)
  "terminalCode": "00001",         // caja      (default: la del emisor)
  "consecutivoNumero": "0000000866", // sólo en consecutivoMode = 'integrator'

  // ── 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": "3101456789",
    "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": "2341000000100",      // 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": "3101456789",
    "nombreTercero": "Transportes Unidos 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",
  "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 (0104, 0820); 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 Crédito 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
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.

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
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 No — asumido
04 alcohol No — asumido
05 bebidas envasadas No — asumido
06 tabaco Sí — se cobra
12 cemento 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)
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.

Ejemplos resueltos

1 · Servicio gravado al 13 %
{
  "cantidad": 1,
  "unidadMedida": "Sp",
  "codigoCabys": "8511100000000",
  "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": "2341000000100",
  "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": "2349002011500",
  "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": "2349002020800",
  "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": "2341000000100",
  "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": "2431001000000",
  "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 mLmonto = 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": "9310100010100",     // 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.

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.

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 9001 321,60      = 51 578,40
efectivo   = 51 578,4041 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_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.08690.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/3101912877/regimen" \
  -H "X-API-Key: sk_live_..." -H "X-API-Secret: ..."
{
  "identificacion": "3101912877",
  "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 CR05015202001026284066 (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á:

  • receiverMRStatuspending, 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.

Numeración propia, si ya la tiene

Por defecto la plataforma numera de forma atómica y usted se olvida del tema. Pero si viene de un sistema con su propia numeración y necesita preservarla, active el modo integrator:

PATCH /emisores/{legalId}/config     { "consecutivoMode": "integrator" }

A partir de ahí el contador es suyo: nosotros dejamos de administrarlo, y repetir un número devuelve 409.

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. Hay tres caminos:

Cómo Cuándo Qué envía
La API numera Caso normal Nada: omita clave y claveIntegrador
claveIntegrador El POS ya compuso e imprimió la clave La clave completa de 50
clave Reservó por adelantado con POST /clave/reserve La clave reservada

clave y claveIntegrador son excluyentes: mandar los dos es ambiguo, no redundante.

claveIntegrador: la clave que usted imprimió

Los dos últimos caminos ya existían en piezas (consecutivoNumero + codigoSeguridad + branchCode + terminalCode + situacion), y nosotros re-componíamos la clave a partir de ellas. El problema es que 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ó.

{
  "emisorLegalId": "3101737993",
  "codigoActividad": "4711.2",

  // La clave que el POS compuso e imprimió en el tiquete
  "claveIntegrador": "50627082600310173799300100009040000000015342240125",

  "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": "112340567",
    "nombre": "Juan Pérez Mora",
    "correoElectronico": "juan@ejemplo.cr"
  },

  "detalle": [
    { "numeroLinea": 1, "codigoCabys": "0125300020500",
      "cantidad": 0.5, "unidadMedida": "Kg",
      "detalle": "CEBOLLA BLANCA A GRANEL", "precioUnitario": 346.55,
      "impuesto": [{ "codigo": "01", "codigoTarifa": "02", "tarifa": 1 }] },

    { "numeroLinea": 2, "codigoCabys": "2316100000200",
      "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). fechaEmision sí, porque la clave sólo lleva el día y el XML necesita la hora.

Y el mismo tiquete por el otro camino, mandando las piezas en vez de la clave. Aquí la compone la API — sirve cuando su numeración es propia pero no necesita imprimir la clave antes de transmitir:

{
  "emisorLegalId": "3101737993",
  "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": "112340567",
    "nombre": "Juan Pérez Mora",
    "correoElectronico": "juan@ejemplo.cr"
  },
  "detalle": [
    { "numeroLinea": 1, "codigoCabys": "0125300020500",
      "cantidad": 0.5, "unidadMedida": "Kg",
      "detalle": "CEBOLLA BLANCA A GRANEL", "precioUnitario": 346.55,
      "impuesto": [{ "codigo": "01", "codigoTarifa": "02", "tarifa": 1 }] },
    { "numeroLinea": 2, "codigoCabys": "2316100000200",
      "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.

Cómo se arma la clave:

506 + ddmmaa + cédula(12) + consecutivo(20) + situación(1) + códigoSeguridad(8)
506   270826   003101737993  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. 3101737993 son 10 dígitos y en la clave va 003101737993.

Ambas se validan antes de firmar. La API rechaza con 400 si la cédula de la clave no es la del emisor que firma (clave_integrador_cedula_mismatch), si el día no cuadra con fechaEmision (clave_integrador_fecha_mismatch), si el tipo de documento no corresponde al endpoint (clave_tipo_documento_mismatch) o si el emisor no está en modo integrator (clave_integrador_requiere_modo_integrator). Ninguno de esos gasta consecutivo.

La situación se toma de la clave (dígito 42), no del payload: es la que ya se imprimió. Si el payload dice otra cosa, se emite con la de la clave y la respuesta trae un aviso.

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.



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) 0108, 10, 1215, 99
Tiquete (04), Compra (08), Exportación (09) igual, pero sin el 12
Notas de crédito y débito (03, 02) todos, 0115 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
02 Jurídica 10
03 DIMEX 11–12
04 NITE 10
05 Extranjero no domiciliado 1–20, admite letras No
06 No contribuyente No Solo en REP

Para un receptor extranjero: tipoIdentificacion: "05", el identificador (pasaporte, tax ID) en numeroIdentificacion, y la dirección en otrasSenasExtranjero en lugar de ubicacion.

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-00460853-20?cabys=2341000000100"

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": "3521100000000",
  "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": "2349002011500", "descripcion": "Pan pita o pan árabe, sin congelar", "impuesto": 1 },
    { "codigo": "2349002020800", "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. Hacienda rechaza el comprobante, así que la API lo corta antes con 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.



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

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: 310123A45600310123A456.



Consecutivos y numeración

El consecutivo tiene 20 dígitos y lo genera la plataforma:

001 00001 01 0000000866
     │    │      └── número secuencial (10)
     │    └───────── tipo de comprobante (2)
     └────────────── terminal / caja (5)
 └─────────────────── sucursal (3)

Dos modos, configurables por emisor:

Modo Quién numera Cuándo usarlo
platform (por defecto) La plataforma, de forma atómica Casi siempre
integrator Usted, enviando consecutivoNumero Solo si ya tiene numeración propia que debe preservar

En modo platform, branchCode y terminalCode son opcionales: se usan los del emisor. Enviar consecutivoNumero en modo platform devuelve 400.

En modo integrator usted es responsable de que no haya saltos ni repeticiones, incluso con concurrencia. Un consecutivo repetido se rechaza; uno saltado hay que justificarlo.

Reserva previa de clave

Si necesita imprimir o mostrar la clave antes de emitir:

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
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 o consecutivoNumero, la respuesta es 400 shared_cert_numbering_is_automatic — se rechaza en vez de ignorarse en silencio, para que no crea que se aplicó.

  • 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 003101678166 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.



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, o descarga de XML con el emisor de pruebas Revisar scopes y estado
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 Reintentar con backoff

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



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.