MENU navbar-image

Introduction

API RESTful para emisión, consulta, descarga y gestión completa de comprobantes electrónicos ante el Ministerio de Hacienda de Costa Rica. Compatible al 100% con la normativa vigente del esquema XML v4.4 y la firma digital XAdES-EPES. Soporta los 7 tipos de comprobante: Factura Electrónica (01), Nota de Débito (02), Nota de Crédito (03), Tiquete Electrónico (04), Factura de Compra (08), Factura de Exportación (09) y Recibo Electrónico de Pago (10). Diseñada para e-commerce, ERP, PMS, CRM y POS.

Bienvenido a la API de Almendro Factura Electrónica

Almendro Factura Electrónica le permite integrar facturación electrónica costarricense en cualquier sistema en minutos. Emita comprobantes, consulte estados, descargue XML y PDF, gestione clientes y productos, reciba notificaciones en tiempo real y administre múltiples contribuyentes desde una sola API.

La API cumple al 100% con la normativa version 4.4, vigente del Ministerio de Hacienda:


Primeros pasos

Integrar facturación electrónica con Almendro requiere solo cuatro pasos. No necesita conocer el esquema XML ni la especificación de firma digital: Almendro se encarga de todo lo técnico.

Paso 1 | Cree su cuenta. Regístrese en fe.almendro.cr. Su cuenta queda activa inmediatamente. Recibirá un correo de verificación para confirmar su dirección de email.

Paso 2 | Obtenga su token API. Desde el portal, vaya a Configuración y genere su token API. Este token es su credencial para autenticarse en todos los endpoints. Guárdelo en un lugar seguro: por razones de seguridad, el token solo se muestra una vez al momento de generarlo. Si lo pierde, puede generar uno nuevo (el anterior se revoca automáticamente).

Paso 3 | Suba su certificado digital. Envíe su archivo .p12 de firma digital (emitido por una entidad certificadora registrada ante el BCCR) con POST /api/v1/public/certificates. Almendro valida el certificado, extrae su información y lo almacena de forma segura. La llave privada nunca se expone en ningún endpoint. Necesitará también el PIN de Hacienda que usa para acceder al sistema de comprobantes electrónicos del Ministerio.

Paso 4 | Emita su primer comprobante. Envíe los datos de la factura con POST /api/v1/public/vouchers. Almendro genera el XML conforme al esquema v4.4 de Hacienda, lo valida contra los esquemas oficiales, lo firma digitalmente con XAdES-EPES y lo envía automáticamente al Ministerio de Hacienda. Usted recibe la clave de 50 dígitos del comprobante y puede consultar el resultado con GET /api/v1/public/vouchers/{key} o recibir una notificación automática por Webhook cuando Hacienda responda.

Recomendación: antes de emitir en producción, pruebe su integración usando el ambiente sandbox. Los comprobantes de sandbox se envían al sandbox oficial de Hacienda, no tienen valor fiscal y no consumen su cuota mensual. Vea la sección "Ambiente Sandbox" más adelante.


Autenticación

Todas las peticiones a la API requieren un Bearer Token en el header Authorization:

Authorization: Bearer {YOUR_AUTH_KEY}

Obtenga su token desde el portal en Configuración. Cada contribuyente genera y administra su propio token de forma independiente.

Aislamiento de datos: cada token está vinculado a un contribuyente específico. Todas sus consultas retornan exclusivamente los comprobantes, clientes, productos y configuración de su propio contribuyente. Es imposible acceder a datos de otro contribuyente, incluso si conociera su token. Este aislamiento se aplica automáticamente en todos los endpoints sin excepción.

Gestión del token: puede consultar los metadatos de su token activo (fecha de creación, último uso) con GET /api/v1/public/tokens/current. Para generar un nuevo token y revocar el anterior, use POST /api/v1/public/tokens.


Formato de respuesta

Todas las respuestas de la API siguen un formato JSON consistente:

{
    "success": true,
    "data": {},
    "message": "Descripción legible en español",
    "errors": null
}

Cuando ocurre un error de validación (422), el campo errors detalla cada campo que falló con mensajes específicos en español:

{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "voucher_type": ["El tipo de comprobante (voucher_type) es obligatorio."],
        "line_items.0.cabys_code": ["El código CABYS no existe en el catálogo."]
    }
}

En las respuestas paginadas (listados), se incluyen los campos meta y links para facilitar la navegación entre páginas:

{
    "success": true,
    "data": [...],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 5,
        "per_page": 15,
        "total": 73,
        "from": 1,
        "to": 15
    },
    "links": {
        "first": "...?page=1",
        "last": "...?page=5",
        "prev": null,
        "next": "...?page=2"
    }
}

Emisión de comprobantes

La emisión es el corazón de la API. El proceso es asíncrono: el endpoint retorna 202 Accepted en milisegundos y el envío a Hacienda ocurre en segundo plano.

sequenceDiagram
    participant I as Su sistema
    participant A as Almendro
    participant H as Hacienda

    I->>+A: POST /vouchers
    Note right of A: Almendro genera clave, XML v4.4 y firma XAdES-EPES
    A-->>-I: 202 Accepted (clave de 50 dígitos)

    rect rgba(128, 128, 128, 0.08)
        Note over A,H: Procesamiento asíncrono (5 a 60 segundos)
        A->>+H: XML firmado
        H-->>-A: Respuesta fiscal
    end

    alt
        A-->>I: Webhook voucher.accepted
    else
        A-->>I: Webhook voucher.rejected
    end

El endpoint retorna 202 Accepted inmediatamente. Esto confirma que el comprobante fue generado, validado contra los esquemas XSD oficiales de Hacienda y firmado digitalmente. El envío a Hacienda ocurre automáticamente en segundo plano.

Importante: el código 202 Accepted no significa "aceptado por Hacienda". Es la confirmación de que Almendro recibió y procesó su solicitud correctamente. La respuesta fiscal de Hacienda se obtiene consultando el estado del comprobante o configurando Webhooks.

Atajos para emisión rápida: si tiene clientes y productos registrados en su catálogo de Almendro, puede simplificar el payload de emisión. Envíe client_id para resolver automáticamente los datos del receptor, y item_id en cada línea para resolver código CABYS, descripción, unidad de medida, precio e impuesto. Si incluye campos explícitos junto con el client_id o item_id, los valores explícitos siempre tienen prioridad.

Anulación: para anular un comprobante previamente aceptado por Hacienda, use POST /api/v1/public/vouchers/{key}/cancel. Almendro genera automáticamente una Nota de Crédito (tipo 03) que referencia al comprobante original.

Estados del comprobante:

Estado Significado
draft Generado, pendiente de firma (ocurre solo si hay un error temporal con el certificado)
pending Firmado, pendiente de envío a Hacienda
sent Enviado a Hacienda, esperando respuesta
accepted Aceptado por Hacienda
rejected Rechazado por Hacienda
error Error técnico al comunicarse con Hacienda
cancelled Anulado mediante Nota de Crédito

Impuestos y cálculo de totales

Almendro calcula automáticamente todos los totales del ResumenFactura a partir de las líneas de detalle. Usted solo necesita enviar los impuestos correctamente en cada línea.

Estructura del impuesto por línea

Cada línea (line_items[]) puede tener un array taxes[] con uno o más impuestos:

{
    "taxes": [
        {
            "codigo": "01",
            "codigoTarifa": "08",
            "tarifa": "13.00",
            "monto": "1300.00000"
        }
    ]
}
Campo Descripción Valores comunes
codigo Código del impuesto 01=IVA, 02=Selectivo consumo, 07=IVA cálculo especial, 08=IVA bienes usados
codigoTarifa Tarifa del IVA (obligatorio si codigo es 01 o 07) 01=0%, 02=1%, 03=2%, 04=4%, 08=13%, 10=Exenta
tarifa Porcentaje de la tarifa "13.00", "4.00", "0.00"
monto Monto del impuesto: base_imponible × tarifa / 100 "1300.00000"
exoneracion Objeto con datos de exoneración (opcional) Ver sección Exonerados
factor Factor para IVA cálculo especial (solo código 07) "0.5000"

Clasificación automática de cada línea

Almendro clasifica cada línea en dos dimensiones para calcular los 8 subtotales del ResumenFactura:

Dimensión 1 | Tipo de bien (primer dígito del código CABYS):

Primer dígito CABYS Clasificación Ejemplo
0, 1, 2, 3, 4 Mercancía 1234500000000 → mercancía
5, 6, 7, 8, 9 Servicio 7331100000000 → servicio

Dimensión 2 | Condición del IVA (determinado por el array taxes[]):

Condición Cuándo aplica
Gravado Tiene IVA (código 01 o 07) con tarifa distinta de 10 y sin exoneración
Exento Tiene IVA con tarifa 10 (exenta), O no tiene ningún impuesto IVA
Exonerado Tiene IVA con objeto exoneracion presente
No sujeto Casos especiales del Art. 9 LIVA (prácticamente nunca en facturación normal)

Estas dos dimensiones generan los 8 subtotales del XML:

                    Servicio              Mercancía
                    ────────              ─────────
Gravado         TotalServGravados     TotalMercanciasGravadas
Exento          TotalServExentos      TotalMercanciasExentas
Exonerado       TotalServExonerado    TotalMercExonerada
No sujeto       TotalServNoSujeto     TotalMercNoSujeta

Ejemplo 1 | Servicio gravado con IVA 13%

{
    "line_items": [{
        "line_number": 1,
        "cabys_code": "7331100000000",
        "detail": "Servicio de consultoría",
        "quantity": "1.000",
        "unit_of_measure": "Sp",
        "unit_price": "10000.00000",
        "total_amount": "10000.00000",
        "sub_total": "10000.00000",
        "base_imponible": "10000.00000",
        "taxes": [{"codigo": "01", "codigoTarifa": "08", "tarifa": "13.00", "monto": "1300.00000"}],
        "impuesto_neto": "1300.00000",
        "total_line_amount": "11300.00000"
    }]
}

CABYS 7 → servicio. IVA tarifa 08 → gravado. Aporta ₡10,000 a TotalServGravados.

Ejemplo 2 | Mercancía gravada con IVA 13%

{
    "line_items": [{
        "line_number": 1,
        "cabys_code": "1234500000000",
        "detail": "Producto de limpieza",
        "quantity": "2.000",
        "unit_of_measure": "Unid",
        "unit_price": "5000.00000",
        "total_amount": "10000.00000",
        "sub_total": "10000.00000",
        "base_imponible": "10000.00000",
        "taxes": [{"codigo": "01", "codigoTarifa": "08", "tarifa": "13.00", "monto": "1300.00000"}],
        "impuesto_neto": "1300.00000",
        "total_line_amount": "11300.00000"
    }]
}

CABYS 1 → mercancía. IVA tarifa 08 → gravado. Aporta ₡10,000 a TotalMercanciasGravadas.

Ejemplo 3 | Servicio exento de IVA

Opción A | Tarifa exenta (10):

{"taxes": [{"codigo": "01", "codigoTarifa": "10", "tarifa": "0.00", "monto": "0.00000"}], "impuesto_neto": "0.00000"}

Opción B | Sin taxes:

{"taxes": [], "impuesto_neto": "0.00000"}

Ambas clasifican como exentoTotalServExentos o TotalMercanciasExentas.

Ejemplo 4 | Factura mixta (servicio + mercancía gravados)

{
    "line_items": [
        {
            "line_number": 1,
            "cabys_code": "7331100000000",
            "detail": "Consultoría técnica",
            "quantity": "1.000", "unit_of_measure": "Sp",
            "unit_price": "50000.00000", "total_amount": "50000.00000",
            "sub_total": "50000.00000", "base_imponible": "50000.00000",
            "taxes": [{"codigo": "01", "codigoTarifa": "08", "tarifa": "13.00", "monto": "6500.00000"}],
            "impuesto_neto": "6500.00000", "total_line_amount": "56500.00000"
        },
        {
            "line_number": 2,
            "cabys_code": "1234500000000",
            "detail": "Material de oficina",
            "quantity": "10.000", "unit_of_measure": "Unid",
            "unit_price": "1000.00000", "total_amount": "10000.00000",
            "sub_total": "10000.00000", "base_imponible": "10000.00000",
            "taxes": [{"codigo": "01", "codigoTarifa": "08", "tarifa": "13.00", "monto": "1300.00000"}],
            "impuesto_neto": "1300.00000", "total_line_amount": "11300.00000"
        }
    ]
}

ResumenFactura: TotalServGravados=₡50,000 · TotalMercanciasGravadas=₡10,000 · TotalGravado=₡60,000 · TotalImpuesto=₡7,800 · TotalComprobante=₡67,800.

Ejemplo 5 | Payload completo funcional (servicio gravado 13%)

{
    "voucher_type": "01",
    "situation": "1",
    "issued_at": "2026-04-20T10:00:00-06:00",
    "issuer_activity_code": "6201.0",
    "sale_condition": "01",
    "currency_code": "CRC",
    "exchange_rate": "1.00000",
    "payment_methods": [{"tipo": "01"}],
    "receiver": {
        "id_type": "02",
        "id_number": "3101234567",
        "name": "Empresa Receptora S.A.",
        "emails": ["facturacion@receptor.com"]
    },
    "line_items": [{
        "line_number": 1,
        "cabys_code": "7331100000000",
        "detail": "Servicio de desarrollo de software",
        "quantity": "1.000",
        "unit_of_measure": "Sp",
        "unit_price": "100000.00000",
        "total_amount": "100000.00000",
        "sub_total": "100000.00000",
        "base_imponible": "100000.00000",
        "taxes": [{"codigo": "01", "codigoTarifa": "08", "tarifa": "13.00", "monto": "13000.00000"}],
        "impuesto_neto": "13000.00000",
        "total_line_amount": "113000.00000"
    }]
}

Ejemplo 6 | IVA reducido 4% (canasta básica)

{"taxes": [{"codigo": "01", "codigoTarifa": "04", "tarifa": "4.00", "monto": "400.00000"}], "impuesto_neto": "400.00000"}

Fórmulas de cada línea

total_amount     = unit_price × quantity
sub_total        = total_amount - descuentos (si aplica)
base_imponible   = sub_total (normalmente igual, salvo exoneraciones parciales)
monto (impuesto) = base_imponible × tarifa / 100
impuesto_neto    = monto - monto_exonerado (si aplica)
total_line_amount = sub_total + impuesto_neto

Todos los montos: exactamente 5 decimales ("10000.00000").

Errores comunes de Hacienda

Código Error Causa Solución
-111 "Monto total de mercancías gravadas no coincide" CABYS de mercancía (dígito 0-4) con IVA pero total_amount/sub_total inconsistente Verifique sub_total = unit_price × quantity - descuentos. Use 5 decimales.
-481 "Carece del monto Total mercancías No Sujetas" CABYS de mercancía sin taxes válidos o CABYS inexistente en catálogo Verifique cabys_code con GET /catalogs/cabys. Use 13 dígitos exactos.
-485 "Carece del monto Total No Sujeto" Línea sin clasificar por CABYS inválido o taxes mal formados Toda línea gravada: codigo + codigoTarifa + tarifa + monto completos.

Regla de oro

  1. cabys_code: 13 dígitos válidos. Verifique con GET /catalogs/cabys?search={código}.
  2. Gravado (IVA > 0%): taxes con codigo + codigoTarifa + tarifa + monto, más impuesto_neto y base_imponible.
  3. Exento: tarifa 10 con monto 0.00000, O taxes: [].
  4. monto = base_imponible × tarifa / 100 (5 decimales).
  5. total_line_amount = sub_total + impuesto_neto.
  6. 5 decimales siempre: "10000.00000".

Almendro calcula automáticamente los 23 campos del ResumenFactura | no envíe totales.


Consulta y descarga de comprobantes

Una vez emitido un comprobante, dispone de varios endpoints para consultarlo y descargar sus archivos:


Gestión de clientes y productos

Almendro le permite mantener un catálogo de clientes (receptores frecuentes) y productos o servicios (líneas de detalle reutilizables). Esto simplifica la emisión de comprobantes al permitir referenciar un client_id o item_id en lugar de repetir todos los datos en cada factura.

Clientes (/clients): registre la razón social, cédula, correos, dirección y teléfono de sus receptores frecuentes. Al emitir un comprobante con client_id, los datos del receptor se completan automáticamente.

Productos/Servicios (/items): registre código CABYS, descripción, unidad de medida, precio unitario e impuesto de cada producto o servicio. Al emitir con item_id en una línea, esos campos se resuelven automáticamente. Puede asignar un código interno (SKU, PLU) único por contribuyente para localizar rápidamente sus productos.

Ambos catálogos ofrecen los cinco endpoints estándar: listar, crear, consultar, actualizar y eliminar.


Plantillas PDF y personalización visual

Cada contribuyente puede personalizar cómo lucen sus comprobantes en formato PDF. El sistema de plantillas permite configurar colores, fuentes, márgenes, qué secciones mostrar u ocultar, y subir su propio logotipo.

Cinco estilos predefinidos: Predeterminado, Moderno, Clásico, Minimalista y Dividido. Cada uno ofrece un diseño distinto que se adapta a diferentes tipos de negocio.

Configuración visual: a través del campo config_json puede ajustar colores (primario, secundario, texto, acento en formato hexadecimal), familias tipográficas, tamaños de fuente para encabezado, cuerpo y pie de página, y márgenes en milímetros.

Logotipo: suba el logo de su empresa con POST /pdf-templates/{id}/logo (formatos JPG, PNG o SVG, máximo 2 MB). El logo aparecerá en la posición configurada (izquierda, centro o derecha) de todos los comprobantes generados con esa plantilla.

Restricciones normativas: el código QR debe medir al menos 2.5 cm de alto por 2.5 cm de ancho y ubicarse en la parte inferior derecha del PDF, conforme al artículo 5 de la Resolución. Estas restricciones se validan automáticamente y no pueden modificarse.

Puede tener varias plantillas y marcar una como predeterminada. La cantidad máxima de plantillas depende de su plan.


Configuración de email transaccional

Almendro envía automáticamente los comprobantes por correo electrónico al receptor, adjuntando el XML firmado y el PDF con código QR, conforme al artículo 18 del Reglamento que obliga la entrega del comprobante electrónico y su representación gráfica.

Desde PUT /email-settings puede configurar:


Webhooks | notificaciones en tiempo real

En lugar de consultar repetidamente el estado de un comprobante, puede configurar Webhooks para que Almendro le notifique automáticamente cuando ocurran eventos relevantes. Al recibir la notificación, su sistema puede actualizar sus registros, notificar al usuario final o ejecutar cualquier lógica de negocio.

Eventos disponibles:

Evento Se dispara cuando...
voucher.accepted Hacienda acepta un comprobante
voucher.rejected Hacienda rechaza un comprobante
voucher.sent Un comprobante se envía a Hacienda
receiver.confirmed Un receptor confirma o rechaza un comprobante recibido
receiver.deadline Se acerca el vencimiento del plazo de 8 días hábiles para confirmar
certificate.expiring Un certificado digital está próximo a vencer

Seguridad: cada notificación incluye una firma HMAC-SHA256 en el header para que su servidor pueda verificar que la notificación proviene de Almendro y no fue alterada en tránsito. La URL de su endpoint debe usar HTTPS.

Tolerancia a fallos: si su servidor no responde o retorna un error, Almendro reintenta con intervalos crecientes. Tras 10 fallos consecutivos, el endpoint se desactiva automáticamente. Puede reactivarlo en cualquier momento con PUT /webhooks/{id}.

Historial de entregas: consulte el registro de entregas con GET /webhooks/{id}/logs para verificar qué notificaciones se enviaron, cuáles fallaron y cuáles se reintentaron.

La cantidad de endpoints webhook disponibles depende de su plan (desde 1 en el plan Pyme hasta 15 en el plan Integrador).


Reportes y análisis financiero

La API ofrece siete endpoints de reportes diseñados para alimentar tableros de control, preparar la declaración D-104 y analizar el desempeño del negocio. Todos comparten los mismos filtros: rango de fechas, tipo de comprobante, estado, moneda, ambiente, cédula del receptor, condición de venta y medio de pago.

Valores por defecto: si no envía filtros, los reportes cubren el mes actual, solo comprobantes de producción aceptados por Hacienda, en colones costarricenses. Estos valores corresponden al período y criterios más comunes para la declaración tributaria mensual.


Catálogos oficiales

La API expone tres catálogos oficiales de Hacienda necesarios para construir comprobantes electrónicos válidos:


Consulta de contribuyentes

El endpoint GET /taxpayer/{id_number} permite verificar los datos de una cédula ante el registro de Hacienda antes de emitir un comprobante. Retorna el nombre oficial, tipo de identificación, régimen tributario, situación (activo/inactivo) y actividades económicas inscritas.

Casos de uso frecuentes:

Para cédulas físicas de 9 dígitos y planes con la funcionalidad habilitada, el endpoint enriquece la respuesta con datos del Padrón Electoral del TSE (nombre completo, vigencia de la cédula, ubicación). Si la persona no está inscrita como contribuyente ante Hacienda pero sí aparece en el Padrón TSE, el endpoint la identifica como consumidor final, indicando que puede recibir Tiquetes Electrónicos (tipo 04) pero no Facturas Electrónicas.

Los resultados se almacenan en caché durante 24 horas para respuestas rápidas en consultas posteriores.


Confirmación del Receptor (MensajeReceptor)

Conforme al artículo 15 del Reglamento, los receptores de comprobantes tienen 8 días hábiles (excluyendo fines de semana y feriados nacionales de Costa Rica) para responder a un comprobante recibido de un proveedor. Vencido el plazo, se considera aceptación tácita.

El endpoint POST /receiver/confirm le permite enviar la respuesta a Hacienda con tres opciones:

Código Tipo de respuesta Consecuencia
1 Aceptado Genera crédito fiscal para el receptor
2 Aceptado Parcialmente Genera crédito fiscal parcial
3 Rechazado No genera crédito fiscal

Al aceptar (total o parcialmente), debe indicar la condición del impuesto (crédito IVA general, crédito parcial, bienes de capital, gasto sin crédito, o proporcionalidad), el monto del impuesto acreditable y la actividad económica asociada.

La respuesta incluye información detallada del plazo: días hábiles transcurridos, días restantes, fecha límite y alertas de vencimiento próximo.


Modo Integrador | gestión de múltiples contribuyentes

Si usted es integrador (plan Integrador), puede administrar la facturación de múltiples clientes desde su propia cuenta. Esto es ideal para empresas que desarrollan software de punto de venta, e-commerce, hotelería o cualquier sistema que facture en nombre de terceros.

Cómo funciona:

Vincular un cliente nuevo: créelo con POST /my-contributors indicando su cédula, razón social, correos y actividades económicas. Almendro crea la cuenta del cliente con un usuario propietario y una contraseña temporal. El cliente recibe un email de bienvenida con instrucciones para acceder a su portal. Automáticamente recibe el plan Gratis (5 comprobantes por mes) para que pueda probar el servicio.

Vincular un cliente que ya tiene cuenta: si el cliente ya está registrado en Almendro, solicite acceso con POST /access-requests. El cliente recibe una notificación por email y en su portal, y puede aceptar (total o parcialmente, eligiendo qué ambientes autoriza) o rechazar la solicitud. Al aceptar, usted recibe acceso al certificado digital del cliente con una terminal exclusiva que evita colisión de consecutivos.

Emitir en nombre del cliente: incluya managed_contributor_id en el payload de emisión. El comprobante se registra bajo la cédula del cliente ante Hacienda (el cliente aparece como emisor, nunca el integrador), se firma con el certificado digital del cliente y se envía desde su cuenta. El comprobante cuenta contra el límite mensual de su plan de integrador, no del plan del cliente.

Administrar certificados: suba el certificado .p12 del cliente con POST /my-contributors/{id}/certificates. El certificado queda vinculado a la cuenta del cliente, no a la suya. Puede listar los certificados activos y su estado de vigencia.

Revocar acceso: el cliente puede revocar su acceso en cualquier momento desde DELETE /certificate-grants/{id}. Usted también puede renunciar voluntariamente a un acceso desde DELETE /my-access/{grantId}.

Ejemplo completo | emitir una factura en nombre de un cliente

A continuación se muestra el flujo completo desde cero. Todos los pasos usan el token del integrador | no se necesita el token del cliente en ningún momento.

Paso 1 | Buscar al cliente por cédula (opcional, para autocompletar datos)

curl -X GET "https://fe.almendro.cr/api/v1/public/taxpayer/3101234567" \
  -H "Authorization: Bearer {TOKEN_DEL_INTEGRADOR}"

Retorna nombre oficial, tipo de identificación, régimen tributario y actividades económicas del contribuyente según el registro de Hacienda. Use estos datos para prellenar el paso 2.

Paso 2 | Crear al cliente como contribuyente gestionado

curl -X POST "https://fe.almendro.cr/api/v1/public/my-contributors" \
  -H "Authorization: Bearer {TOKEN_DEL_INTEGRADOR}" \
  -H "Content-Type: application/json" \
  -d '{
    "legal_name": "Empresa del Cliente S.A.",
    "id_type": "02",
    "id_number": "3101234567",
    "email": "cliente@ejemplo.com",
    "password": "ContraseñaSegura123!"
  }'

La respuesta incluye el UUID del cliente en el campo data.id. Guarde este UUID: lo necesita para todos los pasos siguientes. El objeto managed_relationship describe el vínculo activo entre usted (integrador) y el cliente | incluye la terminal exclusiva asignada (5 dígitos) para evitar colisión de consecutivos.

{
    "success": true,
    "data": {
        "id": "019d867d-0241-7288-8ece-fd64da75616d",
        "legal_name": "Empresa del Cliente S.A.",
        "id_type": "02",
        "id_type_label": "Cédula Jurídica",
        "id_number": "3101234567",
        "is_active": true,
        "production_enabled": false,
        "plan_id": "019d0001-0000-0000-0000-000000000001",
        "can_emit_from_portal": true,
        "managed_relationship": {
            "id": "019d870a-0001-7288-8ece-fd64da75a001",
            "assigned_terminal": "00002",
            "retention_months_override": null,
            "default_pdf_template_id": null,
            "linked_at": "2026-04-18T10:00:00-06:00"
        }
    },
    "message": "Cliente creado correctamente. Se envió email de bienvenida.",
    "errors": null
}

Paso 3 | Subir el certificado digital .p12 del cliente

El certificado debe pertenecer al cliente (emitido por una CA registrada ante el BCCR para la cédula del cliente). Almendro lo almacena cifrado y lo vincula a la cuenta del cliente, no a la del integrador.

curl -X POST "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-0241-7288-8ece-fd64da75616d/certificates" \
  -H "Authorization: Bearer {TOKEN_DEL_INTEGRADOR}" \
  -F "p12_file=@/ruta/al/certificado-cliente.p12" \
  -F "p12_password=ContraseñaDelP12" \
  -F "hacienda_pin=PinDeHaciendaDelCliente" \
  -F "environment=sandbox"

Paso 4 | Emitir un comprobante en nombre del cliente

Use POST /vouchers con su token de integrador y agregue el campo managed_contributor_id con el UUID del cliente obtenido en el paso 2. El resto del payload es idéntico a una emisión normal:

curl -X POST "https://fe.almendro.cr/api/v1/public/vouchers" \
  -H "Authorization: Bearer {TOKEN_DEL_INTEGRADOR}" \
  -H "Content-Type: application/json" \
  -d '{
    "managed_contributor_id": "019d867d-0241-7288-8ece-fd64da75616d",
    "voucher_type": "01",
    "situation": "1",
    "issued_at": "2026-04-18T10:00:00-06:00",
    "sale_condition": "01",
    "payment_methods": [{"tipo": "01"}],
    "receiver": {
        "name": "Juan Pérez Solís",
        "id_type": "01",
        "id_number": "101230456"
    },
    "line_items": [{
        "line_number": 1,
"cabys_code": "4321500000100",
        "detail": "Servicio de consultoría",
        "unit_of_measure": "Sp",
        "quantity": "1.000",
        "unit_price": "10000.00000",
        "sub_total": "10000.00000",
        "total_amount": "10000.00000",
        "taxes": [{
            "codigo": "01",
            "codigoTarifa": "08",
            "tarifa": "13.00",
            "monto": "1300.00000"
        }],
        "impuesto_neto": "1300.00000",
        "total_line_amount": "11300.00000"
    }]
  }'

¿Qué sucede internamente?

Cuando envía managed_contributor_id, Almendro ejecuta automáticamente lo siguiente:

  1. Verifica que el UUID pertenece a un cliente vinculado a su cuenta de integrador
  2. Genera la clave de 50 dígitos usando la cédula del cliente (no la del integrador)
  3. Construye el XML v4.4 con el cliente como emisor (EmisorType) ante Hacienda
  4. Firma digitalmente con el certificado .p12 del cliente
  5. Asigna el consecutivo en los contadores del cliente, usando la terminal exclusiva del integrador
  6. Descuenta el comprobante del límite mensual del plan del integrador

Hacienda siempre ve al cliente como el emisor del comprobante. El integrador nunca aparece en el XML ni en la clave de 50 dígitos.

Sin managed_contributor_id

Si omite el campo managed_contributor_id, el comprobante se emite bajo su propia cédula de integrador, como si fuera un contribuyente normal (emisión directa).

Consultar comprobantes de un cliente gestionado

Use el filtro managed_contributor_id en GET /vouchers para ver solo los comprobantes emitidos en nombre de un cliente específico:

curl -X GET "https://fe.almendro.cr/api/v1/public/vouchers?managed_contributor_id=019d867d-0241-7288-8ece-fd64da75616d" \
  -H "Authorization: Bearer {TOKEN_DEL_INTEGRADOR}"

Reportes agregados por cliente

Los endpoints de reportes (/reports/*) agregan automáticamente los datos de todos sus clientes gestionados. Para filtrar los datos de un solo cliente, use el parámetro ?managed_contributor_id={UUID} en cualquier endpoint de reportes.


Control de acceso | solicitudes y grants

El sistema de control de acceso regula la relación entre integradores y clientes, garantizando que el cliente siempre tiene soberanía sobre sus certificados digitales conforme al artículo 16 del Reglamento.

Solicitudes de acceso (/access-requests): el integrador solicita, el cliente decide. Una solicitud puede estar en estado pendiente, aceptada (total o parcialmente), rechazada o cancelada. El integrador puede ver sus solicitudes enviadas; el cliente, las recibidas.

Grants de certificados (/certificate-grants): cuando el cliente acepta una solicitud, se crean grants que autorizan al integrador a firmar con el certificado del cliente en los ambientes aprobados. Cada grant tiene una terminal exclusiva asignada para evitar duplicación de consecutivos.

El cliente puede consultar qué integradores tienen acceso a sus certificados con GET /certificate-grants, y revocar cualquier acceso en cualquier momento.


Notificaciones

El sistema de notificaciones mantiene informado al usuario sobre eventos importantes que requieren su atención:

Use GET /notifications para obtener la lista paginada de notificaciones con un conteo de no leídas para actualizar badges en su interfaz. Las notificaciones no leídas aparecen primero. Puede marcar una notificación como leída con POST /notifications/{id}/read o marcar todas de una vez con POST /notifications/read-all.


Planes y límites

El volumen de comprobantes, la cantidad de peticiones por minuto y las funcionalidades disponibles dependen del plan contratado:

Plan Precio Comprobantes/mes Excedente Peticiones/min Sandbox Webhooks Clientes gestionados
Gratis $0 5 Bloquea 5 No No 1
Emprendedor $36/año 100 $0.05/comp 10 No No 1
Pyme $96/año 500 $0.03/comp 20 1 endpoint 1
Profesional $19/mes o $182/año 2,000 $0.02/comp 40 3 endpoints 5
Empresa $39/mes o $351/año 8,000 $0.012/comp 80 5 endpoints 1
Integrador $79/mes o $711/año 50,000 $0.008/comp 150 15 endpoints 100

Límite de peticiones por minuto: cuando se excede, la API retorna 429 Too Many Requests con el header Retry-After indicando cuántos segundos esperar. Las operaciones de lectura (GET) están exentas del límite; solo las operaciones transaccionales (POST, PUT, DELETE) cuentan contra la cuota.

Límite mensual de comprobantes: solo cuentan comprobantes de producción aceptados por Hacienda. Los comprobantes de sandbox no consumen cuota. El plan Gratis bloquea la emisión al alcanzar el límite. Desde el plan Emprendedor, se permite emitir por encima del límite con un cargo automático por comprobante adicional.

Otros límites por plan: cada plan define también la cantidad máxima de clientes en catálogo, productos en catálogo, plantillas PDF, tokens API, emails diarios, sucursales y días de anticipación para alertas de vencimiento de certificado. Consulte los detalles completos en fe.almendro.cr.


Ambiente Sandbox

Almendro ofrece un ambiente sandbox que replica los endpoints de emisión contra el sandbox oficial del Ministerio de Hacienda. Los comprobantes emitidos en sandbox no tienen valor fiscal y no cuentan contra su límite mensual.

Para usar el sandbox, reemplace /api/v1/public/ por /api/v1/public/sandbox/ en la URL. El token, los headers y el payload son exactamente iguales:

Producción:  https://fe.almendro.cr/api/v1/public/vouchers
Sandbox:     https://fe.almendro.cr/api/v1/public/sandbox/vouchers
Aspecto Producción Sandbox
Destino API oficial de Hacienda API sandbox de Hacienda
Valor fiscal No
Cuenta contra límite mensual No
Envío de email al receptor No (solo registro interno)
Marca de agua en PDF Sin marca "SANDBOX
Token y payload El mismo El mismo
Webhooks

Endpoints sandbox disponibles: emisión de los 7 tipos de comprobante, consulta, descarga XML/PDF, anulación, confirmación del receptor y consulta de contribuyentes. Los demás endpoints (clientes, productos, webhooks, reportes, plantillas, certificados, configuración) no necesitan URL sandbox porque operan sobre los mismos datos en ambos ambientes.

El acceso al sandbox requiere plan Pyme o superior.


Retención y disponibilidad de XML

Almendro retiene el XML firmado y la respuesta de Hacienda de cada comprobante durante 3 meses en todos los planes. Transcurrido ese período, el contenido XML se elimina automáticamente, pero los metadatos del comprobante (totales, estado, receptor, líneas de detalle) y la generación de PDF permanecen disponibles de forma indefinida.

Cuando el XML ya no está disponible, los endpoints GET /vouchers/{key}/xml y GET /vouchers/{key}/xml-response retornan 410 Gone con la fecha en que se eliminó el archivo.

Retención extendida: puede ampliar la retención de XML hasta 5 años por $12/año (disponible desde el plan Emprendedor). Al vencer la retención extendida, se otorga un período de gracia de 7 días para que descargue sus documentos antes de la eliminación definitiva.


Seguridad y tratamiento de sus datos

Esta es la respuesta técnica a la pregunta que más hacen las empresas antes de integrarse: quién puede ver la información que pasa por la API.

Credenciales de firma cifradas. El certificado .p12, su contraseña y el PIN de Hacienda se guardan cifrados con AES-256-GCM, con una llave dedicada que no forma parte del código de la aplicación. Estos valores no se devuelven en ninguna respuesta de la API, ni siquiera al dueño del certificado: se usan para firmar y nunca salen del servidor.

Aislamiento entre contribuyentes. Cada token pertenece a un contribuyente y toda consulta se filtra por él automáticamente. Además, los endpoints que leen o escriben comprobantes exigen que el token esté vinculado a un contribuyente activo: una petición sin contribuyente asociado se rechaza con 403 antes de tocar la base de datos. No existe ningún parámetro que permita salir de ese ámbito.

Un integrador solo alcanza los comprobantes de un cliente gestionado cuando existe una relación activa, y el alcance depende del rol de esa relación: en rol integrador ve únicamente los comprobantes que él mismo emitió por el cliente; en rol contador ve la facturación completa del cliente. Lo que el cliente emita por su cuenta o con otro proveedor no aparece en el primer caso. El permiso lo otorga el cliente sobre su propio certificado y lo puede revocar cuando quiera.

Qué ve el personal de Almendro. El panel administrativo no tiene ninguna pantalla que liste, abra o exporte comprobantes de un cliente. Lo que el equipo ve son cifras de operación de la cuenta: cantidad de comprobantes emitidos, usuarios, plan y estado de pago. Las acciones administrativas sensibles quedan registradas con autor y fecha.

Respaldos. Se genera un respaldo diario que se cifra en el servidor con criptografía asimétrica: el servidor solo tiene la llave pública, de modo que produce respaldos que él mismo no puede leer. La llave privada vive fuera del servidor.

El límite honesto. Como en cualquier plataforma en la nube, el personal técnico con acceso al servidor tiene acceso material a la base de datos. Eso no lo resuelve el software sino el contrato: firmamos acuerdo de confidencialidad y de tratamiento de datos conforme a la Ley 8968 de Costa Rica. Los respaldos se almacenan en Estados Unidos, siempre cifrados, lo que constituye una transferencia internacional declarada en dicho acuerdo.

El detalle completo, con la referencia al código que sostiene cada afirmación, está en el documento de tratamiento de datos que entregamos a solicitud.


Clave de 50 dígitos

Cada comprobante se identifica con una Clave numérica de exactamente 50 dígitos, definida por los Anexos y Estructuras v4.4 de la DGT. Esta clave se usa en todos los endpoints que reciben el parámetro {key}:

[3 país][6 fecha DDMMAA][12 cédula emisor][20 consecutivo][1 situación][8 seguridad]

Ejemplo: 50613032600206270652001000010100000000011 12345678


Tipos de comprobante soportados

Código Tipo Descripción Receptor
01 Factura Electrónica Venta de bienes o servicios con receptor identificado Obligatorio
02 Nota de Débito Corrección que incrementa el monto de un comprobante previo Obligatorio
03 Nota de Crédito Corrección que disminuye el monto, o anulación total Obligatorio
04 Tiquete Electrónico Venta al consumidor final sin receptor obligatorio Opcional
08 Factura de Compra Compra a proveedores no inscritos en el régimen Obligatorio
09 Factura de Exportación Venta de bienes o servicios fuera del territorio nacional Opcional
10 Recibo Electrónico de Pago Constancia de recepción de pago en ventas a crédito fiscal o al Estado Obligatorio

Las Notas de Débito (02), Notas de Crédito (03), Facturas de Compra (08) y Recibos de Pago (10) requieren al menos una referencia al comprobante original en el campo references.


Códigos de estado HTTP

Código Significado
200 Consulta, actualización o eliminación exitosa
201 Recurso creado correctamente (clientes, productos, webhooks, plantillas, certificados)
202 Comprobante recibido, validado, firmado y en proceso de envío a Hacienda
401 Token de autenticación inválido, expirado o ausente
403 Su plan no permite esta operación o no tiene permisos suficientes
404 Recurso no encontrado o no pertenece a su contribuyente
410 El XML de este comprobante fue eliminado por vencimiento de retención. Los metadatos y el PDF siguen disponibles
422 Error de validación. Revise el campo errors en la respuesta para detalles por campo
429 Límite de peticiones por minuto excedido. Espere los segundos indicados en el header Retry-After
500 Error interno del servidor
502 La API del Ministerio de Hacienda no está disponible temporalmente
504 Tiempo de espera agotado al contactar la API del Ministerio de Hacienda

Soporte y contacto

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {YOUR_AUTH_KEY}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Obtenga su token desde el portal en Configuración → Token API. Cada token está vinculado a un contribuyente específico y hereda los límites de su plan. El aislamiento de datos se aplica automáticamente | un token solo puede acceder a los comprobantes y datos de su propio contribuyente.

Comprobantes Electrónicos

Listar comprobantes con filtros y paginación

requires authentication

Devuelve todos los comprobantes emitidos por el contribuyente, con paginación y filtros por estado, tipo, rango de fechas, receptor, ambiente y moneda. Los filtros status y voucher_type aceptan múltiples valores separados por coma.

El listado retorna campos resumidos (sin XML ni totales detallados) para máxima eficiencia. Para el detalle completo de un comprobante, use GET /vouchers/{key}.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/vouchers?status=accepted%2Crejected&voucher_type=01%2C04&date_from=2026-04-01&date_to=2026-04-30&receiver_id_number=3101234567&external_reference=ORD-12345&environment=production&currency_code=CRC&contributor_id=019d867d-0241-7288-8ece-fd64da75616d&scope=own&sort_by=issued_at&sort_dir=desc&per_page=15&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers"
);

const params = {
    "status": "accepted,rejected",
    "voucher_type": "01,04",
    "date_from": "2026-04-01",
    "date_to": "2026-04-30",
    "receiver_id_number": "3101234567",
    "external_reference": "ORD-12345",
    "environment": "production",
    "currency_code": "CRC",
    "contributor_id": "019d867d-0241-7288-8ece-fd64da75616d",
    "scope": "own",
    "sort_by": "issued_at",
    "sort_dir": "desc",
    "per_page": "15",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'status' => 'accepted,rejected',
            'voucher_type' => '01,04',
            'date_from' => '2026-04-01',
            'date_to' => '2026-04-30',
            'receiver_id_number' => '3101234567',
            'external_reference' => 'ORD-12345',
            'environment' => 'production',
            'currency_code' => 'CRC',
            'contributor_id' => '019d867d-0241-7288-8ece-fd64da75616d',
            'scope' => 'own',
            'sort_by' => 'issued_at',
            'sort_dir' => 'desc',
            'per_page' => '15',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado paginado):


{
    "success": true,
    "data": [
        {
            "voucher_key": "50620032600206270652001000010100000000011XXXXXXXX",
            "voucher_type": "01",
            "voucher_type_label": "Factura Electrónica",
            "consecutive_number": "00100001010000000001",
            "issued_at": "2026-03-20T10:30:00-06:00",
            "situation": "1",
            "sale_condition": "01",
            "sale_condition_label": "Contado",
            "receiver": {
                "id_type": "02",
                "id_number": "3102345678",
                "name": "Empresa S.A."
            },
            "currency_code": "CRC",
            "total_comprobante": "56500.00000",
            "status": "accepted",
            "hacienda": {
                "status": "aceptado",
                "sent_at": "2026-03-20T10:31:00-06:00",
                "processed_at": "2026-03-20T10:32:00-06:00"
            },
            "environment": "sandbox",
            "is_xml_available": true,
            "line_items_count": 3,
            "created_at": "2026-03-20T10:30:00-06:00",
            "updated_at": "2026-03-20T10:32:00-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 5,
        "per_page": 15,
        "total": 72,
        "from": 1,
        "to": 15
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": "..."
    }
}
 

Request      

GET api/v1/public/vouchers

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Estado(s) separados por coma. draft, pending, sent, accepted, rejected, error, cancelled. Example: accepted,rejected

voucher_type   string  optional    

Tipo(s) separados por coma. 01=FE, 02=ND, 03=NC, 04=TE, 08=FEC, 09=FEE, 10=REP. Example: 01,04

date_from   string  optional    

Fecha de emisión desde (YYYY-MM-DD). Example: 2026-04-01

date_to   string  optional    

Fecha de emisión hasta (YYYY-MM-DD). Example: 2026-04-30

receiver_id_number   string  optional    

Cédula del receptor (9-12 dígitos). Example: 3101234567

external_reference   string  optional    

Su identificador del documento, tal como lo envió al emitir. Coincidencia exacta. Es la forma de averiguar si una emisión se completó cuando la respuesta no le llegó: consulte por la referencia de su orden antes de reintentar. Example: ORD-12345

environment   string  optional    

Ambiente: sandbox o production. Example: production

currency_code   string  optional    

Código ISO 4217. Example: CRC

contributor_id   string  optional    

UUID del cliente gestionado cuyos comprobantes quiere ver. Solo para integradores con relación activa. Sin él ve los suyos. Example: 019d867d-0241-7288-8ece-fd64da75616d

scope   string  optional    

all (default) o own para excluir los emitidos a nombre de clientes gestionados. Example: own

sort_by   string  optional    

Campo de ordenamiento. Default: issued_at. Example: issued_at

sort_dir   string  optional    

Dirección: asc o desc. Default: desc. Example: desc

per_page   integer  optional    

Resultados por página (1-100). Default: 15. Example: 15

page   integer  optional    

Número de página. Example: 1

GET /api/v1/public/vouchers/issuers

requires authentication

Opciones disponibles para los filtros del listado, calculadas a partir de los comprobantes que realmente existen (acotados al ambiente). Evita ofrecer en los desplegables valores que no devolverían nada.

Retorna: has_own → true si la cuenta tiene comprobantes propios clients → clientes gestionados con al menos un comprobante voucher_types → tipos de comprobante presentes (01, 03, …) statuses → estados presentes (accepted, rejected, …)

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/vouchers/issuers" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/issuers"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/issuers';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/vouchers/issuers

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Emitir un comprobante electrónico

requires authentication

Genera, valida contra el XSD oficial v4.4, firma digitalmente con XAdES-EPES y envía automáticamente a Hacienda cualquiera de los 7 tipos de comprobante soportados.

La respuesta es HTTP 202 Accepted | el envío a Hacienda es asíncrono. Para obtener el resultado final (aceptado/rechazado), suscríbase a los eventos voucher.accepted y voucher.rejected vía webhooks (ver grupo Webhooks), o consulte periódicamente GET /vouchers/{key}.

HTTP 202 no significa "aceptado por Hacienda". Solo confirma que el comprobante fue generado, firmado y encolado exitosamente. El estado inicial en la respuesta es pending.

Para ejemplos completos de payload por cada tipo de comprobante, revise la sección "Ejemplos completos de payload por tipo" de la guía de integración al inicio de este grupo.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/vouchers" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: Identificador único de ESTA emisión, generado por usted (recomendado: un" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"voucher_type\": \"01\",
    \"situation\": \"1\",
    \"local_code\": \"564\",
    \"terminal_code\": \"56425\",
    \"voucher_key\": \"56425593142326822820492192294472586671521743076933\",
    \"consecutive_number\": \"56425593142326822820\",
    \"issued_at\": \"2026-04-20T10:30:00-06:00\",
    \"issuer_activity_code\": \"6201.0\",
    \"receiver_activity_code\": \"4711.0\",
    \"sale_condition\": \"01\",
    \"sale_condition_other\": \"Acuerdo especial de intercambio\",
    \"credit_term\": \"30\",
    \"currency_code\": \"CRC\",
    \"exchange_rate\": \"1.00000\",
    \"external_reference\": \"ORD-12345\",
    \"xml_otros\": [
        {
            \"tipo\": \"texto\",
            \"codigo\": \"orden\",
            \"valor\": \"ORD-0001\"
        }
    ],
    \"client_id\": \"019d1234-0000-0000-0000-000000000001\",
    \"managed_contributor_id\": \"019d867d-0241-7288-8ece-fd64da75616d\",
    \"receiver\": {
        \"name\": \"Empresa Ejemplo S.A.\",
        \"id_type\": \"02\",
        \"id_number\": \"3101000001\",
        \"commercial_name\": \"p\",
        \"emails\": [
            \"architecto\"
        ],
        \"province\": 1,
        \"canton\": \"56\",
        \"district\": \"56\",
        \"address\": \"i\",
        \"phone_country_code\": 8,
        \"phone\": \"564255931\"
    },
    \"line_items\": [
        {
            \"item_id\": \"019d1234-0000-0000-0000-000000000002\",
            \"line_number\": 1,
            \"cabys_code\": \"5311100000000\",
            \"arancelary_partition\": \"564255931423\",
            \"quantity\": \"1.000\",
            \"unit_of_measure\": \"Unid\",
            \"detail\": \"Servicio de consultoría\",
            \"unit_price\": \"10000.00000\",
            \"total_amount\": \"10000.00000\",
            \"sub_total\": \"10000.00000\",
            \"base_imponible\": \"10000.00000\",
            \"discounts\": [
                {
                    \"monto\": 39,
                    \"codigo\": 6
                }
            ],
            \"taxes\": [
                {
                    \"codigo\": \"01\",
                    \"codigoTarifa\": \"08\",
                    \"tarifa\": \"13.00\",
                    \"factorCalculo\": 4326.41688,
                    \"monto\": \"1300.00000\",
                    \"monto_exportacion\": 77,
                    \"exoneracion\": {
                        \"tipoDocumento\": \"iy\",
                        \"numeroDocumento\": \"v\",
                        \"nombreInstitucion\": \"d\",
                        \"fechaEmision\": \"2026-08-11T21:37:46\",
                        \"tarifaExonerada\": 37,
                        \"montoExoneracion\": 9
                    }
                }
            ],
            \"impuesto_neto\": \"1300.00000\",
            \"total_line_amount\": \"11300.00000\",
            \"medicine_registration\": \"n\",
            \"pharma_form_code\": \"564\",
            \"commercial_codes\": [
                {
                    \"tipo\": 4,
                    \"codigo\": \"yvdljnikhwaykcmy\"
                }
            ],
            \"vin_numbers\": [
                \"u\"
            ]
        }
    ],
    \"references\": [
        {
            \"doc_type\": \"01\",
            \"number\": \"50613032600206270652001000010100000000001XXXXXXXX\",
            \"issued_at\": \"2026-03-01T10:00:00-06:00\",
            \"code\": \"01\",
            \"reason\": \"Anulación por error en monto\"
        }
    ],
    \"payment_methods\": [
        {
            \"tipo\": \"01\"
        }
    ],
    \"other_charges\": [
        {
            \"tipo\": \"06\",
            \"description\": \"Quidem nostrum qui commodi incidunt iure odit.\",
            \"percentage\": 10,
            \"amount\": 5000
        }
    ]
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "Identificador único de ESTA emisión, generado por usted (recomendado: un",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "voucher_type": "01",
    "situation": "1",
    "local_code": "564",
    "terminal_code": "56425",
    "voucher_key": "56425593142326822820492192294472586671521743076933",
    "consecutive_number": "56425593142326822820",
    "issued_at": "2026-04-20T10:30:00-06:00",
    "issuer_activity_code": "6201.0",
    "receiver_activity_code": "4711.0",
    "sale_condition": "01",
    "sale_condition_other": "Acuerdo especial de intercambio",
    "credit_term": "30",
    "currency_code": "CRC",
    "exchange_rate": "1.00000",
    "external_reference": "ORD-12345",
    "xml_otros": [
        {
            "tipo": "texto",
            "codigo": "orden",
            "valor": "ORD-0001"
        }
    ],
    "client_id": "019d1234-0000-0000-0000-000000000001",
    "managed_contributor_id": "019d867d-0241-7288-8ece-fd64da75616d",
    "receiver": {
        "name": "Empresa Ejemplo S.A.",
        "id_type": "02",
        "id_number": "3101000001",
        "commercial_name": "p",
        "emails": [
            "architecto"
        ],
        "province": 1,
        "canton": "56",
        "district": "56",
        "address": "i",
        "phone_country_code": 8,
        "phone": "564255931"
    },
    "line_items": [
        {
            "item_id": "019d1234-0000-0000-0000-000000000002",
            "line_number": 1,
            "cabys_code": "5311100000000",
            "arancelary_partition": "564255931423",
            "quantity": "1.000",
            "unit_of_measure": "Unid",
            "detail": "Servicio de consultoría",
            "unit_price": "10000.00000",
            "total_amount": "10000.00000",
            "sub_total": "10000.00000",
            "base_imponible": "10000.00000",
            "discounts": [
                {
                    "monto": 39,
                    "codigo": 6
                }
            ],
            "taxes": [
                {
                    "codigo": "01",
                    "codigoTarifa": "08",
                    "tarifa": "13.00",
                    "factorCalculo": 4326.41688,
                    "monto": "1300.00000",
                    "monto_exportacion": 77,
                    "exoneracion": {
                        "tipoDocumento": "iy",
                        "numeroDocumento": "v",
                        "nombreInstitucion": "d",
                        "fechaEmision": "2026-08-11T21:37:46",
                        "tarifaExonerada": 37,
                        "montoExoneracion": 9
                    }
                }
            ],
            "impuesto_neto": "1300.00000",
            "total_line_amount": "11300.00000",
            "medicine_registration": "n",
            "pharma_form_code": "564",
            "commercial_codes": [
                {
                    "tipo": 4,
                    "codigo": "yvdljnikhwaykcmy"
                }
            ],
            "vin_numbers": [
                "u"
            ]
        }
    ],
    "references": [
        {
            "doc_type": "01",
            "number": "50613032600206270652001000010100000000001XXXXXXXX",
            "issued_at": "2026-03-01T10:00:00-06:00",
            "code": "01",
            "reason": "Anulación por error en monto"
        }
    ],
    "payment_methods": [
        {
            "tipo": "01"
        }
    ],
    "other_charges": [
        {
            "tipo": "06",
            "description": "Quidem nostrum qui commodi incidunt iure odit.",
            "percentage": 10,
            "amount": 5000
        }
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Idempotency-Key' => 'Identificador único de ESTA emisión, generado por usted (recomendado: un',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'voucher_type' => '01',
            'situation' => '1',
            'local_code' => '564',
            'terminal_code' => '56425',
            'voucher_key' => '56425593142326822820492192294472586671521743076933',
            'consecutive_number' => '56425593142326822820',
            'issued_at' => '2026-04-20T10:30:00-06:00',
            'issuer_activity_code' => '6201.0',
            'receiver_activity_code' => '4711.0',
            'sale_condition' => '01',
            'sale_condition_other' => 'Acuerdo especial de intercambio',
            'credit_term' => '30',
            'currency_code' => 'CRC',
            'exchange_rate' => '1.00000',
            'external_reference' => 'ORD-12345',
            'xml_otros' => [
                [
                    'tipo' => 'texto',
                    'codigo' => 'orden',
                    'valor' => 'ORD-0001',
                ],
            ],
            'client_id' => '019d1234-0000-0000-0000-000000000001',
            'managed_contributor_id' => '019d867d-0241-7288-8ece-fd64da75616d',
            'receiver' => [
                'name' => 'Empresa Ejemplo S.A.',
                'id_type' => '02',
                'id_number' => '3101000001',
                'commercial_name' => 'p',
                'emails' => [
                    'architecto',
                ],
                'province' => 1,
                'canton' => '56',
                'district' => '56',
                'address' => 'i',
                'phone_country_code' => 8,
                'phone' => '564255931',
            ],
            'line_items' => [
                [
                    'item_id' => '019d1234-0000-0000-0000-000000000002',
                    'line_number' => 1,
                    'cabys_code' => '5311100000000',
                    'arancelary_partition' => '564255931423',
                    'quantity' => '1.000',
                    'unit_of_measure' => 'Unid',
                    'detail' => 'Servicio de consultoría',
                    'unit_price' => '10000.00000',
                    'total_amount' => '10000.00000',
                    'sub_total' => '10000.00000',
                    'base_imponible' => '10000.00000',
                    'discounts' => [
                        [
                            'monto' => 39,
                            'codigo' => 6,
                        ],
                    ],
                    'taxes' => [
                        [
                            'codigo' => '01',
                            'codigoTarifa' => '08',
                            'tarifa' => '13.00',
                            'factorCalculo' => 4326.41688,
                            'monto' => '1300.00000',
                            'monto_exportacion' => 77,
                            'exoneracion' => [
                                'tipoDocumento' => 'iy',
                                'numeroDocumento' => 'v',
                                'nombreInstitucion' => 'd',
                                'fechaEmision' => '2026-08-11T21:37:46',
                                'tarifaExonerada' => 37,
                                'montoExoneracion' => 9,
                            ],
                        ],
                    ],
                    'impuesto_neto' => '1300.00000',
                    'total_line_amount' => '11300.00000',
                    'medicine_registration' => 'n',
                    'pharma_form_code' => '564',
                    'commercial_codes' => [
                        [
                            'tipo' => 4,
                            'codigo' => 'yvdljnikhwaykcmy',
                        ],
                    ],
                    'vin_numbers' => [
                        'u',
                    ],
                ],
            ],
            'references' => [
                [
                    'doc_type' => '01',
                    'number' => '50613032600206270652001000010100000000001XXXXXXXX',
                    'issued_at' => '2026-03-01T10:00:00-06:00',
                    'code' => '01',
                    'reason' => 'Anulación por error en monto',
                ],
            ],
            'payment_methods' => [
                [
                    'tipo' => '01',
                ],
            ],
            'other_charges' => [
                [
                    'tipo' => '06',
                    'description' => 'Quidem nostrum qui commodi incidunt iure odit.',
                    'percentage' => 10,
                    'amount' => 5000.0,
                ],
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (202, Comprobante emitido correctamente):


{
    "success": true,
    "data": {
        "voucher_key": "50620042600206270652001000010100000000011XXXXXXXX",
        "voucher_type": "01",
        "voucher_type_label": "Factura Electrónica",
        "consecutive_number": "00100001010000000001",
        "issued_at": "2026-04-20T10:30:00-06:00",
        "situation": "1",
        "situation_label": "Normal",
        "sale_condition": "01",
        "sale_condition_label": "Contado",
        "sale_condition_other": null,
        "credit_term": null,
        "receiver": {
            "id_type": "02",
            "id_number": "3101000001",
            "name": "EMPRESA EJEMPLO S.A.",
            "commercial_name": null,
            "emails": [
                "facturacion@ejemplo.cr"
            ]
        },
        "currency_code": "CRC",
        "exchange_rate": "1.00000",
        "totals": {
            "serv_gravados": "10000.00000",
            "serv_exentos": "0.00000",
            "serv_exonerado": "0.00000",
            "serv_no_sujeto": "0.00000",
            "merc_gravadas": "0.00000",
            "merc_exentas": "0.00000",
            "merc_exonerada": "0.00000",
            "merc_no_sujeta": "0.00000",
            "total_gravado": "10000.00000",
            "total_exento": "0.00000",
            "total_exonerado": "0.00000",
            "total_no_sujeto": "0.00000",
            "total_venta": "10000.00000",
            "total_descuentos": "0.00000",
            "total_venta_neta": "10000.00000",
            "total_impuesto": "1300.00000",
            "total_imp_asum_emisor_fab": "0.00000",
            "total_iva_devuelto": "0.00000",
            "total_otros_cargos": "0.00000",
            "total_comprobante": "11300.00000"
        },
        "payment_methods": [
            {
                "tipo": "01"
            }
        ],
        "status": "pending",
        "hacienda": {
            "status": null,
            "message": null,
            "sent_at": null,
            "processed_at": null
        },
        "environment": "production",
        "is_xml_available": true,
        "line_items_count": 1,
        "references_count": 0,
        "created_at": "2026-04-20T10:30:00-06:00",
        "updated_at": "2026-04-20T10:30:00-06:00"
    },
    "message": "Comprobante [50620042600206270652001000010100000000011XXXXXXXX] generado y encolado para envío a Hacienda.",
    "errors": null
}
 

Example response (422, Error de validación del payload):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "voucher_type": [
            "El campo voucher_type es obligatorio."
        ]
    }
}
 

Example response (422, Error de validación XSD (no consume consecutivo)):


{
    "success": false,
    "data": null,
    "message": "Validación XSD fallida para Factura Electrónica (01) | 1 error. Primer error: [45:12] Element 'CodigoCABYS': [facet 'length'] The value has a length of '12'; this differs from the allowed length of '13'.",
    "errors": {
        "voucher_type": [
            "01"
        ],
        "xsd": [
            {
                "nivel": "error",
                "codigo": 1824,
                "linea": 45,
                "columna": 12,
                "mensaje": "Element 'CodigoCABYS': [facet 'length'] The value has a length of '12'; this differs from the allowed length of '13'."
            }
        ]
    }
}
 

Example response (422, Error de firma digital (consume consecutivo)):


{
    "success": false,
    "data": null,
    "message": "No se pudo firmar el comprobante. El certificado digital está vencido. Renueve su certificado con el BCCR y vuelva a subirlo.",
    "errors": {
        "signature": [
            "..."
        ],
        "code": [
            "2002"
        ]
    }
}
 

Example response (429, Cupo mensual agotado (plan sin overage)):


{
    "success": false,
    "data": null,
    "message": "Límite mensual de comprobantes alcanzado (50/50). Actualice a PyME para emitir más. Período: junio 2026. Precio: $29.99/mes ≈ ₡15,745.",
    "errors": null
}
 

Request      

POST api/v1/public/vouchers

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: Identificador único de ESTA emisión, generado por usted (recomendado: un

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

voucher_type   string     

Tipo de comprobante. 01=FE, 02=ND, 03=NC, 04=TE, 08=FEC, 09=FEE, 10=REP. Example: 01

situation   string     

Situación del comprobante. 1=Normal, 2=Contingencia, 3=Sin internet. Example: 1

local_code   string  optional    

Must match the regex /^\d{3}$/. Example: 564

terminal_code   string  optional    

Must match the regex /^\d{5}$/. Example: 56425

voucher_key   string  optional    

Must match the regex /^\d{50}$/. Example: 56425593142326822820492192294472586671521743076933

consecutive_number   string  optional    

Must match the regex /^\d{20}$/. Example: 56425593142326822820

issued_at   string     

Fecha y hora de emisión ISO 8601 con offset de Costa Rica (-06:00). No puede ser futura. Example: 2026-04-20T10:30:00-06:00

issuer_activity_code   string  optional    

Código CIIU del emisor en formato Hacienda (XXXX.X). Example: 6201.0

receiver_activity_code   string  optional    

Código CIIU del receptor (XXXX.X). Solo para FEC (tipo 08). Example: 4711.0

sale_condition   string     

Condición de venta. 01=Contado, 02=Crédito, etc. Example: 01

sale_condition_other   string  optional    

Descripción si sale_condition=99. Obligatorio para 99 (5-100 chars). Example: Acuerdo especial de intercambio

credit_term   string  optional    

Plazo del crédito en días. Obligatorio si sale_condition es 02, 08 o 10. Example: 30

currency_code   string  optional    

Código ISO 4217. Default: CRC. Example: CRC

exchange_rate   string  optional    

Tipo de cambio respecto al CRC (5 decimales). Requerido si currency_code != CRC. Example: 1.00000

observations   string  optional    

Observaciones opcionales (máx 250 chars). El campo value no puede tener más de 250 caracteres.

external_reference   string  optional    

Su identificador del documento que origina este comprobante (número de orden, ticket, folio). No se valida ni se interpreta: se guarda tal cual y se puede filtrar con GET /vouchers?external_reference=.... Envíelo siempre: es lo que le permite averiguar si una emisión llegó a completarse cuando la conexión se corta. No se exige único, porque contra una misma orden puede emitir una factura y después su nota de crédito. Máx. 150 caracteres. Example: ORD-12345

xml_otros   object[]  optional    

Contenido del nodo <Otros> del XML. A diferencia de external_reference, esto SÍ queda dentro del documento fiscal firmado ante Hacienda. Máx. 10 entradas. Use external_reference si solo necesita poder consultar después; use este si la referencia debe constar en el comprobante.

tipo   string     

texto (genera <OtroTexto>) o contenido (genera <OtroContenido>). Example: texto

codigo   string  optional    

Atributo codigo del nodo. Máx. 255. Example: orden

valor   string     

Contenido del nodo. 1 a 500 caracteres. Example: ORD-0001

client_id   string  optional    

UUID de un cliente del catálogo (GET /clients). Sobrescribe los datos del receptor. Example: 019d1234-0000-0000-0000-000000000001

managed_contributor_id   string  optional    

UUID del cliente gestionado. Solo para integradores. Si se envía, el comprobante se emite bajo la cédula de ese cliente. Example: 019d867d-0241-7288-8ece-fd64da75616d

receiver   object  optional    

Datos del receptor. Requerido para FE/ND/NC/FEC. Opcional si se envía client_id.

name   string     

Nombre o razón social (3–100 chars). Example: Empresa Ejemplo S.A.

id_type   string     

Tipo de identificación. 01=Física, 02=Jurídica, 03=DIMEX, 04=NITE, 05=Extranjero. Example: 02

id_number   string     

Número de identificación. Example: 3101000001

commercial_name   string  optional    

El campo value no puede tener más de 80 caracteres. Example: p

emails   string[]  optional    

Correos del receptor para envío automático del comprobante.

province   integer  optional    

El campo value debe estar entre 1 y 7. Example: 1

canton   string  optional    

Must match the regex /^\d{2}$/. Example: 56

district   string  optional    

Must match the regex /^\d{2}$/. Example: 56

address   string  optional    

El campo value debe tener al menos 5 caracteres. El campo value no puede tener más de 250 caracteres. Example: i

phone_country_code   integer  optional    

El campo value debe ser al menos 1. El campo value no puede ser mayor a 999. Example: 8

phone   string  optional    

Must match the regex /^\d{4,20}$/. Example: 564255931

line_items   object[]     

Líneas de detalle (máximo 1000).

item_id   string  optional    

UUID de un producto del catálogo (GET /items). Resuelve CABYS, descripción, unidad, precio e impuesto automáticamente. Los campos explícitos tienen prioridad. Example: 019d1234-0000-0000-0000-000000000002

line_number   integer     

Número de línea (1-based). Example: 1

cabys_code   string     

Código CABYS de 13 dígitos. Example: 5311100000000

arancelary_partition   string  optional    

Must match the regex /^\d{12}$/. Example: 564255931423

quantity   string     

Cantidad con 3 decimales. Example: 1.000

unit_of_measure   string     

Unidad de medida (Unid, kg, Sp, etc.). Example: Unid

detail   string     

Descripción (3–200 chars). Example: Servicio de consultoría

unit_price   string     

Precio unitario (5 decimales). Example: 10000.00000

total_amount   string     

Monto total antes de descuentos. Example: 10000.00000

sub_total   string     

Subtotal de la línea. Example: 10000.00000

base_imponible   string  optional    

Base imponible para el impuesto. Example: 10000.00000

discounts   object[]  optional    

El campo value no puede tener más de 5 elementos.

monto   number     

El campo value debe ser al menos 0. Example: 39

codigo   string     

Example: 6

Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 99
taxes   object[]  optional    

Lista de impuestos de la línea.

codigo   string     

Código impuesto. 01=IVA. Example: 01

codigoTarifa   string     

Tarifa IVA. 08=13%, 10=Exenta. Example: 08

tarifa   string     

Porcentaje de tarifa. Example: 13.00

factorCalculo   number  optional    

Example: 4326.41688

monto   string     

Monto del impuesto. Example: 1300.00000

monto_exportacion   number  optional    

El campo value debe ser al menos 0. Example: 77

exoneracion   object  optional    
tipoDocumento   string  optional    

This field is required when line_items..taxes..exoneracion is present. El campo value no puede tener más de 2 caracteres. Example: iy

numeroDocumento   string  optional    

This field is required when line_items..taxes..exoneracion is present. El campo value no puede tener más de 40 caracteres. Example: v

nombreInstitucion   string  optional    

This field is required when line_items..taxes..exoneracion is present. El campo value no puede tener más de 160 caracteres. Example: d

fechaEmision   string  optional    

This field is required when line_items..taxes..exoneracion is present. El campo value no es una fecha válida. Example: 2026-08-11T21:37:46

tarifaExonerada   number  optional    

This field is required when line_items..taxes..exoneracion is present. El campo value debe ser al menos 0. Example: 37

montoExoneracion   number  optional    

This field is required when line_items..taxes..exoneracion is present. El campo value debe ser al menos 0. Example: 9

impuesto_neto   string  optional    

Monto neto del impuesto (obligatorio si hay taxes). Example: 1300.00000

total_line_amount   string     

Total de la línea incluyendo impuesto. Example: 11300.00000

medicine_registration   string  optional    

El campo value no puede tener más de 100 caracteres. Example: n

pharma_form_code   string  optional    

Must match the regex /^\d{3}$/. Example: 564

commercial_codes   object[]  optional    

El campo value no puede tener más de 5 elementos.

tipo   string     

Example: 4

Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 99
codigo   string     

El campo value no puede tener más de 20 caracteres. Example: yvdljnikhwaykcmy

vin_numbers   string[]  optional    

El campo value no puede tener más de 50 caracteres.

references   object[]  optional    

Referencias a otros comprobantes. Obligatorio para ND/NC y REP.

doc_type   string     

Tipo de documento referenciado. 01=FE, 03=NC, etc. Example: 01

number   string     

Clave de 50 dígitos del comprobante referenciado. Example: 50613032600206270652001000010100000000001XXXXXXXX

issued_at   string     

Fecha de emisión del comprobante referenciado. Example: 2026-03-01T10:00:00-06:00

code   string     

Código de referencia. 01=Anula, 04=Referencia, 06=Devolución, 10=ND financiera. Example: 01

reason   string     

Razón de la referencia (máx 180 chars). Example: Anulación por error en monto

payment_methods   object[]     

Lista de medios de pago.

tipo   string     

Código medio pago. 01=Efectivo, 02=Tarjeta, 03=Cheque, 04=Transferencia. Example: 01

monto   number  optional    

Monto pagado con este medio. Obligatorio cuando hay 2+ medios de pago. El campo value debe ser al menos 0.

other_charges   object[]     
tipo   string     

Tipo de cargo. 01=Contribución parafiscal, 04=Cobro terceros, 06=Garantía. Example: 06

Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 99
description   string  optional    

El campo value no puede tener más de 80 caracteres. Example: Quidem nostrum qui commodi incidunt iure odit.

percentage   number  optional    

El campo value debe ser al menos 0. El campo value no puede ser mayor a 100. Example: 10

amount   number     

Monto del cargo (Decimal 18,5). El campo value debe ser al menos 0. Example: 5000

Listar comprobantes de contingencia pendientes

requires authentication

Devuelve los comprobantes emitidos en situación de contingencia (caída del sistema) o sin internet, que aún no fueron aceptados ni rechazados por Hacienda.

La normativa establece un plazo máximo de 2 días hábiles para remitir los comprobantes de contingencia. Este endpoint le muestra cuántos están pendientes y cuántos ya superaron el plazo, para ayudarle a cumplir con el monitoreo requerido antes de incurrir en infracciones tributarias.

Estados incluidos: draft, pending, sent, error.

Los comprobantes ya resueltos (accepted, rejected, cancelled) no aparecen aquí.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/vouchers/pending-contingency?situation=3&expired_only=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/pending-contingency"
);

const params = {
    "situation": "3",
    "expired_only": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/pending-contingency';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'situation' => '3',
            'expired_only' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Con comprobantes pendientes):


{
    "success": true,
    "data": {
        "items": [
            {
                "voucher_key": "50617042026...",
                "voucher_type": "01",
                "voucher_type_label": "Factura Electrónica",
                "consecutive_number": "00100001010000000001",
                "status": "error",
                "situation": "3",
                "situation_label": "Sin Internet",
                "issued_at": "2026-04-10T14:30:00-06:00",
                "deadline": "2026-04-14",
                "elapsed_business_days": 3,
                "is_expired": true,
                "receiver": {
                    "id_type": "02",
                    "id_number": "3101000001",
                    "name": "Empresa S.A."
                },
                "currency_code": "CRC",
                "total_comprobante": "56500.00000"
            }
        ],
        "total": 5,
        "expired_count": 2
    },
    "message": "5 comprobante(s) de contingencia sin resolver. 2 superan el plazo de 2 días hábiles.",
    "errors": null
}
 

Example response (200, Sin pendientes):


{
    "success": true,
    "data": {
        "items": [],
        "total": 0,
        "expired_count": 0
    },
    "message": "Sin comprobantes de contingencia pendientes.",
    "errors": null
}
 

Request      

GET api/v1/public/vouchers/pending-contingency

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

situation   string  optional    

Filtrar por situación. 2=Contingencia, 3=Sin internet. Sin valor muestra ambas. Example: 3

expired_only   boolean  optional    

Mostrar solo los que superaron el plazo de 2 días hábiles. Default: false. Example: true

Consultar un comprobante por clave

requires authentication

Devuelve el detalle completo de un comprobante identificado por su clave de 50 dígitos: datos del emisor y receptor, líneas, totales, estado actual y respuesta de Hacienda (si ya fue procesado).

La clave debe tener exactamente 50 dígitos numéricos | formatos inválidos retornan 404 sin procesar la consulta.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Comprobante encontrado):


{
    "success": true,
    "data": {
        "voucher_key": "50619032600310199999900100001010000000001112345678",
        "voucher_type": "01",
        "voucher_type_label": "Factura Electrónica",
        "consecutive_number": "00100001010000000001",
        "issued_at": "2026-04-13T10:00:00-06:00",
        "situation": "1",
        "situation_label": "Normal",
        "sale_condition": "01",
        "sale_condition_label": "Contado",
        "sale_condition_other": null,
        "credit_term": null,
        "receiver": {
            "id_type": "02",
            "id_number": "3101000001",
            "name": "EMPRESA EJEMPLO S.A.",
            "commercial_name": "Ejemplo Comercial",
            "emails": [
                "facturacion@ejemplo.cr"
            ]
        },
        "currency_code": "CRC",
        "exchange_rate": "1.00000",
        "totals": {
            "serv_gravados": "10000.00000",
            "serv_exentos": "0.00000",
            "serv_exonerado": "0.00000",
            "serv_no_sujeto": "0.00000",
            "merc_gravadas": "0.00000",
            "merc_exentas": "0.00000",
            "merc_exonerada": "0.00000",
            "merc_no_sujeta": "0.00000",
            "total_gravado": "10000.00000",
            "total_exento": "0.00000",
            "total_exonerado": "0.00000",
            "total_no_sujeto": "0.00000",
            "total_venta": "10000.00000",
            "total_descuentos": "0.00000",
            "total_venta_neta": "10000.00000",
            "total_impuesto": "1300.00000",
            "total_imp_asum_emisor_fab": "0.00000",
            "total_iva_devuelto": "0.00000",
            "total_otros_cargos": "0.00000",
            "total_comprobante": "11300.00000"
        },
        "payment_methods": [
            {
                "tipo": "01"
            }
        ],
        "status": "accepted",
        "hacienda": {
            "status": "aceptado",
            "message": null,
            "sent_at": "2026-04-13T10:01:00-06:00",
            "processed_at": "2026-04-13T10:02:00-06:00"
        },
        "environment": "production",
        "is_xml_available": true,
        "line_items_count": 1,
        "references_count": 0,
        "created_at": "2026-04-13T10:00:00-06:00",
        "updated_at": "2026-04-13T10:02:00-06:00"
    },
    "message": "Comprobante obtenido correctamente.",
    "errors": null
}
 

Example response (404, Comprobante no encontrado):


{
    "success": false,
    "data": null,
    "message": "El recurso solicitado no existe.",
    "errors": null
}
 

Request      

GET api/v1/public/vouchers/{key}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

key   string     

Clave de 50 dígitos del comprobante. Example: 50619032600310199999900100001010000000001112345678

Response

Response Fields

data   object     
voucher_key   string     

Clave numérica de 50 dígitos. Identificador único del comprobante ante Hacienda. Estructura: [3 país][6 fecha DDMMYY][12 cédula][20 consecutivo][1 situación][8 seguridad].

voucher_type   string     

Código del tipo de comprobante: 01=FE, 02=ND, 03=NC, 04=TE, 08=FEC, 09=FEE, 10=REP.

voucher_type_label   string     

Nombre legible del tipo de comprobante en español.

consecutive_number   string     

Número consecutivo interno de 20 dígitos asignado al emitir.

issued_at   string     

Fecha y hora de emisión en formato ISO 8601 con offset CR (-06:00).

situation   string     

Situación del comprobante: 1=Normal, 2=Contingencia, 3=Sin internet.

situation_label   string     

Nombre legible de la situación en español.

sale_condition   string     

Código de condición de venta: 01=Contado, 02=Crédito, 08=Crédito al Estado, 10=Crédito IVA 90d, 99=Otros.

sale_condition_label   string     

Nombre legible de la condición de venta.

sale_condition_other   string     

Descripción personalizada cuando sale_condition=99. Null en cualquier otro caso.

credit_term   string     

Plazo del crédito en días. Presente cuando sale_condition es 02, 08 o 10.

receiver   object     

Datos del receptor del comprobante. null cuando el tipo no requiere receptor (TE sin receptor, FEE sin receptor).

id_type   string     

Tipo de identificación del receptor: 0106.

id_number   string     

Número de identificación del receptor.

name   string     

Nombre o razón social del receptor.

commercial_name   string     

Nombre comercial del receptor. Puede ser null.

emails   string[]     

Correos electrónicos del receptor usados para el envío transaccional.

currency_code   string     

Código de moneda ISO 4217 (CRC, USD, EUR, etc.).

exchange_rate   string     

Tipo de cambio respecto al CRC. 1.00000 si la moneda es CRC.

totals   object     

Totales del ResumenFactura desglosados (Decimal 18,5).

serv_gravados   string     

Total servicios gravados con IVA.

serv_exentos   string     

Total servicios exentos.

serv_exonerado   string     

Total servicios exonerados.

serv_no_sujeto   string     

Total servicios no sujetos a IVA.

merc_gravadas   string     

Total mercancías gravadas con IVA.

merc_exentas   string     

Total mercancías exentas.

merc_exonerada   string     

Total mercancías exoneradas.

merc_no_sujeta   string     

Total mercancías no sujetas a IVA.

total_gravado   string     

Suma de servicios + mercancías gravados.

total_exento   string     

Suma de servicios + mercancías exentos.

total_exonerado   string     

Suma de servicios + mercancías exonerados.

total_no_sujeto   string     

Suma de servicios + mercancías no sujetos.

total_venta   string     

TotalVenta = suma de MontoTotalLinea de todas las líneas.

total_descuentos   string     

Suma de todos los descuentos aplicados.

total_venta_neta   string     

TotalVenta − TotalDescuentos.

total_impuesto   string     

Suma de todos los impuestos (IVA, selectivo, etc.).

total_imp_asum_emisor_fab   string     

Impuesto asumido por el emisor/fabricante. Normalmente 0.00000.

total_iva_devuelto   string     

IVA devuelto (aplicable en devoluciones). Normalmente 0.00000.

total_otros_cargos   string     

Suma de otros cargos adicionales al comprobante.

total_comprobante   string     

Monto final: TotalVentaNeta + TotalImpuesto − TotalIVADevuelto + TotalOtrosCargos.

payment_methods   object[]     

Medios de pago del comprobante. Cada objeto tiene tipo: 01=Efectivo, 02=Tarjeta, 03=Cheque, 04=Transferencia, 99=Otros.

status   string     

Estado actual del comprobante: draft, pending, sent, accepted, rejected, error, cancelled.

hacienda   object     

Información de la respuesta de Hacienda (MensajeHacienda).

status   string     

Estado según Hacienda: aceptado, rechazado, o null si aún no procesado.

message   string     

Detalle del mensaje de Hacienda. Contiene la descripción del error si fue rechazado. null si fue aceptado.

sent_at   string     

Fecha/hora en que se envió a Hacienda (ISO 8601). null si no enviado aún.

processed_at   string     

Fecha/hora en que Hacienda procesó el comprobante (ISO 8601). null si pendiente.

environment   string     

Ambiente del comprobante: production (valor fiscal) o sandbox (pruebas, sin valor fiscal).

is_xml_available   boolean     

true si los XML (firmado + respuesta) están disponibles para descarga. false si la retención expiró y fueron purgados.

line_items_count   integer     

Cantidad de líneas de detalle (LineaDetalle) del comprobante.

references_count   integer     

Cantidad de referencias a otros comprobantes (InformacionReferencia).

created_at   string     

Fecha de creación del registro (ISO 8601).

updated_at   string     

Fecha de última actualización del registro (ISO 8601).

Anular un comprobante (emite una Nota de Crédito)

requires authentication

En Costa Rica, la anulación de un comprobante electrónico NO es una operación directa. El mecanismo normativo es emitir una Nota de Crédito que referencia al comprobante original con el código 01 (Anula documento).

Este endpoint automatiza ese proceso:

  1. Lee el comprobante original por su clave.
  2. Construye automáticamente el payload de la NC (mismas líneas, mismo receptor) con la referencia correspondiente.
  3. La emite por el mismo pipeline que POST /vouchers (valida XSD, firma, envía a Hacienda).
  4. Retorna la NC generada con HTTP 202.

Restricciones:

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/cancel" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: Identificador único de ESTA anulación, generado por usted. Es OPCIONAL," \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/cancel"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "Identificador único de ESTA anulación, generado por usted. Es OPCIONAL,",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/cancel';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Idempotency-Key' => 'Identificador único de ESTA anulación, generado por usted. Es OPCIONAL,',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (202, NC de anulación generada):


{
    "success": true,
    "data": {
        "voucher_key": "50621042600310199999900100001010000000002212345679",
        "voucher_type": "03",
        "voucher_type_label": "Nota de Crédito",
        "consecutive_number": "00100001030000000001",
        "status": "pending",
        "environment": "production",
        "created_at": "2026-04-21T11:00:00-06:00"
    },
    "message": "NC de anulación generada. Encolada para envío a Hacienda.",
    "errors": null
}
 

Example response (409, El estado no permite anulación):


{
    "success": false,
    "data": null,
    "message": "Solo comprobantes aceptados por Hacienda pueden anularse.",
    "errors": null
}
 

Example response (422, Error de validación XSD de la NC):


{
    "success": false,
    "data": null,
    "message": "Validación XSD fallida para Nota de Crédito (03) | ...",
    "errors": {
        "voucher_type": [
            "03"
        ],
        "xsd": [
            {
                "linea": 45,
                "columna": 12,
                "mensaje": "..."
            }
        ]
    }
}
 

Example response (422, Error de firma de la NC):


{
    "success": false,
    "data": null,
    "message": "No se pudo firmar el comprobante. El certificado digital está vencido.",
    "errors": {
        "signature": [
            "..."
        ],
        "code": [
            "2002"
        ]
    }
}
 

Request      

POST api/v1/public/vouchers/{key}/cancel

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: Identificador único de ESTA anulación, generado por usted. Es OPCIONAL,

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

key   string     

Clave de 50 dígitos del comprobante a anular. Example: 50619032600310199999900100001010000000001112345678

Reenviar a Hacienda un comprobante que quedó sin enviarse

requires authentication

Reintenta la emisión de un comprobante que NO llegó a Hacienda y por lo tanto NO consumió consecutivo allá: típicamente quedó en draft con su XML generado pero sin firmar (falló la firma por falta de certificado). Una vez subido el .p12, este endpoint lo firma y lo encola | reusando el mismo comprobante y consecutivo, sin generar huecos de numeración.

También cubre el estado error (Hacienda no lo procesó por un fallo temporal; ya está firmado y solo se reencola).

El ambiente del reenvío es el del comprobante original (un comprobante sandbox se firma con el certificado sandbox y se envía a sandbox).

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/vouchers/50617062600020627065200100001010000000101173247335/resend" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/50617062600020627065200100001010000000101173247335/resend"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/50617062600020627065200100001010000000101173247335/resend';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/vouchers/{key}/resend

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

key   string     

Clave de 50 dígitos. Example: 50617062600020627065200100001010000000101173247335

Descargar el XML firmado del comprobante

requires authentication

Devuelve el XML completo con firma digital XAdES-EPES, tal como fue enviado a la API de recepción de Hacienda. Este XML es el documento fiscal con validez legal.

Obligación de archivo: la normativa exige conservar este XML por 5 años. Descárguelo y archívelo de su lado dentro del período de retención de la plataforma (3 meses por defecto, hasta 5 años con el add-on de retención extendida).

Disponibilidad:

Después de la retención, los metadatos del comprobante y el PDF siguen disponibles | solo el XML deja de estar accesible.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/xml" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/xml"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/xml';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, XML disponible):


[archivo binario application/xml]
 

Example response (404, XML aún no generado):


{
    "success": false,
    "data": null,
    "message": "El XML firmado aún no está disponible. El comprobante está en estado 'draft'.",
    "errors": null
}
 

Example response (410, XML purgado | retención expirada):


{
    "success": false,
    "data": null,
    "message": "XML purgado. Retención expirada el 2026-06-15.",
    "errors": null
}
 

Request      

GET api/v1/public/vouchers/{key}/xml

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

key   string     

Clave de 50 dígitos. Example: 50619032600310199999900100001010000000001112345678

Descargar el XML de respuesta de Hacienda (MensajeHacienda)

requires authentication

Devuelve el MensajeHacienda XML firmado por Hacienda que representa el resultado oficial del procesamiento del comprobante. Incluye:

Este XML es evidencia oficial de la validación ante Hacienda. Su obligación legal de archivo es de 5 años, igual que el XML firmado.

Disponibilidad:

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/xml-response" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/xml-response"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/xml-response';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, XML respuesta disponible):


[archivo binario application/xml]
 

Example response (404, Respuesta aún no disponible):


{
    "success": false,
    "data": null,
    "message": "La respuesta de Hacienda aún no está disponible. El comprobante está en estado 'sent'.",
    "errors": null
}
 

Example response (410, XML purgado | retención expirada):


{
    "success": false,
    "data": null,
    "message": "XML purgado. Retención expirada el 2026-06-15.",
    "errors": null
}
 

Request      

GET api/v1/public/vouchers/{key}/xml-response

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

key   string     

Clave de 50 dígitos. Example: 50619032600310199999900100001010000000001112345678

Descargar la representación gráfica (PDF) del comprobante

requires authentication

Genera y devuelve el PDF del comprobante usando la plantilla por defecto del contribuyente (o una específica si se indica vía template_id). El PDF incluye obligatoriamente un código QR con la clave de 50 dígitos en la esquina inferior derecha, con tamaño mínimo de 2.5 cm conforme a la normativa.

Disponibilidad:

Para gestionar plantillas PDF personalizadas, vea el grupo Plantillas PDF de esta documentación.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/pdf?template_id=16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/pdf"
);

const params = {
    "template_id": "16",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/50619032600310199999900100001010000000001112345678/pdf';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'template_id' => '16',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, PDF generado):


[archivo binario application/pdf]
 

Example response (404, Comprobante en draft):


{
    "success": false,
    "data": null,
    "message": "El PDF no está disponible. El comprobante está en estado 'draft'.",
    "errors": null
}
 

Example response (404, Sin plantilla PDF default activa):


{
    "success": false,
    "data": null,
    "message": "El contribuyente no tiene plantilla PDF default activa.",
    "errors": null
}
 

Request      

GET api/v1/public/vouchers/{key}/pdf

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

key   string     

Clave de 50 dígitos. Example: 50619032600310199999900100001010000000001112345678

Query Parameters

template_id   integer  optional    

ID de una plantilla específica. Si no se envía, usa la plantilla default del contribuyente. Example: 16

Comprobantes / Borradores

Listar borradores del contribuyente

requires authentication

Devuelve los borradores del wizard de emisión guardados por el contribuyente, ordenados del más reciente al más antiguo (por fecha de última actualización). Un borrador es el estado parcial de un comprobante en construcción; no es un comprobante fiscal ni consume consecutivo.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/vouchers/drafts?per_page=15&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/drafts"
);

const params = {
    "per_page": "15",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/drafts';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'per_page' => '15',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado paginado):


{
    "success": true,
    "data": [
        {
            "id": "019d867d-aaaa-7288-8ece-fd64da756001",
            "voucher_type": "01",
            "payload": {
                "client_id": null,
                "line_items": [],
                "currency_code": "CRC"
            },
            "totals_preview": {
                "currency_code": "CRC",
                "line_count": 0,
                "total_venta": "0.00000",
                "total_descuento": "0.00000",
                "total_gravado": "0.00000",
                "total_exento": "0.00000",
                "total_venta_neta": "0.00000",
                "total_impuesto": "0.00000",
                "total_otros_cargos": "0.00000",
                "total_comprobante": "0.00000"
            },
            "comments": "Esperando datos del cliente.",
            "validation": null,
            "created_at": "2026-04-16T10:00:00-06:00",
            "updated_at": "2026-04-16T10:30:00-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 1,
        "from": 1,
        "to": 1
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Example response (200, Sin borradores):


{
    "success": true,
    "data": [],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 0,
        "from": null,
        "to": null
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Request      

GET api/v1/public/vouchers/drafts

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

per_page   integer  optional    

Resultados por página (1-100). Default: 15. Example: 15

page   integer  optional    

Número de página. Example: 1

Crear un borrador

requires authentication

Guarda el estado parcial del wizard. NO consume consecutivo. Devuelve la validación en vivo (errores condicionales + preview de totales).

El payload es el cuerpo PARCIAL del comprobante (mismo contrato que POST /vouchers, pero puede estar incompleto entre pasos del wizard).

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/vouchers/drafts" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"voucher_type\": \"01\",
    \"payload\": {
        \"client_id\": null,
        \"line_items\": [],
        \"currency_code\": \"CRC\"
    },
    \"comments\": \"Esperando datos del cliente.\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/drafts"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "voucher_type": "01",
    "payload": {
        "client_id": null,
        "line_items": [],
        "currency_code": "CRC"
    },
    "comments": "Esperando datos del cliente."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/drafts';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'voucher_type' => '01',
            'payload' => [
                'client_id' => null,
                'line_items' => [],
                'currency_code' => 'CRC',
            ],
            'comments' => 'Esperando datos del cliente.',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Borrador guardado):


{
    "success": true,
    "data": {
        "id": "019d867d-aaaa-7288-8ece-fd64da756001",
        "voucher_type": "01",
        "payload": {
            "client_id": null,
            "line_items": [],
            "currency_code": "CRC"
        },
        "totals_preview": {
            "currency_code": "CRC",
            "line_count": 0,
            "total_venta": "0.00000",
            "total_descuento": "0.00000",
            "total_gravado": "0.00000",
            "total_exento": "0.00000",
            "total_venta_neta": "0.00000",
            "total_impuesto": "0.00000",
            "total_otros_cargos": "0.00000",
            "total_comprobante": "0.00000"
        },
        "comments": "Esperando datos del cliente.",
        "validation": {
            "valid": false,
            "errors": {
                "line_items": [
                    "Agregá al menos una línea de detalle."
                ],
                "sale_condition": [
                    "Indicá la condición de venta."
                ]
            },
            "totals": {
                "currency_code": "CRC",
                "line_count": 0,
                "total_comprobante": "0.00000"
            }
        },
        "created_at": "2026-04-16T10:00:00-06:00",
        "updated_at": "2026-04-16T10:00:00-06:00"
    },
    "message": "Borrador guardado.",
    "errors": null
}
 

Example response (422, Payload ausente o inválido):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "payload": [
            "El cuerpo del borrador (payload) es obligatorio."
        ]
    }
}
 

Request      

POST api/v1/public/vouchers/drafts

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

voucher_type   string  optional    

Tipo de comprobante en construcción: 01 (FE), 02 (ND), 03 (NC), 04 (TE), 08 (FEC), 09 (FEE), 10 (REP). null mientras el usuario no elige. Example: 01

payload   object     

Cuerpo PARCIAL del comprobante (mismo contrato que POST /vouchers, pero puede estar incompleto). El borrador no se valida ni firma hasta emitir.

comments   string  optional    

Notas internas del borrador (máx. 1000 caracteres). No viajan al XML. Example: Esperando datos del cliente.

Consultar un borrador (con validación en vivo)

requires authentication

Devuelve el detalle del borrador indicado, recalculando en el momento la validación en vivo (errores condicionales + preview de totales) sin persistirla. Solo se retornan borradores que pertenecen a su contribuyente. Un id que no sea un UUID válido retorna 404.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Borrador encontrado):


{
    "success": true,
    "data": {
        "id": "019d867d-aaaa-7288-8ece-fd64da756001",
        "voucher_type": "01",
        "payload": {
            "client_id": null,
            "line_items": [],
            "currency_code": "CRC"
        },
        "totals_preview": {
            "currency_code": "CRC",
            "line_count": 0,
            "total_comprobante": "0.00000"
        },
        "comments": "Esperando datos del cliente.",
        "validation": {
            "valid": false,
            "errors": {
                "line_items": [
                    "Agregá al menos una línea de detalle."
                ]
            },
            "totals": {
                "currency_code": "CRC",
                "line_count": 0,
                "total_comprobante": "0.00000"
            }
        },
        "created_at": "2026-04-16T10:00:00-06:00",
        "updated_at": "2026-04-16T10:30:00-06:00"
    },
    "message": "",
    "errors": null
}
 

Example response (404, No encontrado):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Request      

GET api/v1/public/vouchers/drafts/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del borrador. Example: 019d867d-aaaa-7288-8ece-fd64da756001

Response

Response Fields

data   object     
id   string     

UUID del borrador. Úselo en las rutas /{id}, /{id}/issue y DELETE /{id}.

voucher_type   string     

Tipo de comprobante en construcción (01, 02, 03, 04, 08, 09, 10). null mientras el usuario no elige.

payload   object     

Cuerpo PARCIAL del comprobante (mismo contrato que POST /vouchers, posiblemente incompleto).

totals_preview   object     

Preview de totales persistido en el último store/update. null si nunca se calculó.

comments   string     

Notas internas del borrador. No viajan al XML. Puede ser null.

validation   object     

Validación en vivo (dry-run): valid (bool), errors (mapa campo→mensajes condicionales) y totals (preview recalculado). null si no se adjuntó.

created_at   string     

Fecha de creación del borrador (ISO 8601).

updated_at   string     

Fecha de última actualización del borrador (ISO 8601).

Guardar un paso del wizard (actualización del borrador)

requires authentication

Reemplaza el estado del borrador con lo enviado (voucher_type, payload y comments) y vuelve a calcular la validación en vivo (errores condicionales + preview de totales), persistiendo el preview. NO consume consecutivo ni firma. Un id que no sea un UUID válido retorna 404.

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"voucher_type\": \"01\",
    \"payload\": {
        \"client_id\": \"019d867d-0001-7288-8ece-fd64da756001\",
        \"sale_condition\": \"01\",
        \"line_items\": [
            {
                \"quantity\": 1,
                \"unit_price\": 1000
            }
        ]
    },
    \"comments\": \"Listo para emitir.\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "voucher_type": "01",
    "payload": {
        "client_id": "019d867d-0001-7288-8ece-fd64da756001",
        "sale_condition": "01",
        "line_items": [
            {
                "quantity": 1,
                "unit_price": 1000
            }
        ]
    },
    "comments": "Listo para emitir."
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'voucher_type' => '01',
            'payload' => [
                'client_id' => '019d867d-0001-7288-8ece-fd64da756001',
                'sale_condition' => '01',
                'line_items' => [
                    [
                        'quantity' => 1,
                        'unit_price' => 1000,
                    ],
                ],
            ],
            'comments' => 'Listo para emitir.',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Borrador actualizado):


{
    "success": true,
    "data": {
        "id": "019d867d-aaaa-7288-8ece-fd64da756001",
        "voucher_type": "01",
        "payload": {
            "client_id": "019d867d-0001-7288-8ece-fd64da756001",
            "sale_condition": "01",
            "line_items": [
                {
                    "quantity": 1,
                    "unit_price": 1000
                }
            ]
        },
        "totals_preview": {
            "currency_code": "CRC",
            "line_count": 1,
            "total_venta": "1000.00000",
            "total_descuento": "0.00000",
            "total_gravado": "1000.00000",
            "total_exento": "0.00000",
            "total_venta_neta": "1000.00000",
            "total_impuesto": "130.00000",
            "total_otros_cargos": "0.00000",
            "total_comprobante": "1130.00000"
        },
        "comments": "Listo para emitir.",
        "validation": {
            "valid": true,
            "errors": [],
            "totals": {
                "currency_code": "CRC",
                "line_count": 1,
                "total_comprobante": "1130.00000"
            }
        },
        "created_at": "2026-04-16T10:00:00-06:00",
        "updated_at": "2026-04-16T11:00:00-06:00"
    },
    "message": "Borrador actualizado.",
    "errors": null
}
 

Example response (404, No encontrado):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Example response (422, Payload ausente o inválido):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "payload": [
            "El cuerpo del borrador (payload) es obligatorio."
        ]
    }
}
 

Request      

PUT api/v1/public/vouchers/drafts/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del borrador. Example: 019d867d-aaaa-7288-8ece-fd64da756001

Body Parameters

voucher_type   string  optional    

Tipo de comprobante en construcción: 01 (FE), 02 (ND), 03 (NC), 04 (TE), 08 (FEC), 09 (FEE), 10 (REP). Example: 01

payload   object     

Cuerpo PARCIAL del comprobante (mismo contrato que POST /vouchers, posiblemente incompleto).

comments   string  optional    

Notas internas del borrador (máx. 1000 caracteres). No viajan al XML. Example: Listo para emitir.

Emitir el comprobante a partir del borrador

requires authentication

Valida en seco (reglas condicionales); si pasa, reproduce el pipeline real de POST /vouchers (consume consecutivo, firma, valida XSD, encola el envío a Hacienda) y ELIMINA el borrador al tener éxito.

No recibe cuerpo: emite a partir del payload ya guardado en el borrador. La respuesta es liviana (no serializa el comprobante completo); el detalle se consulta en GET /vouchers/{key}. Un id que no sea un UUID válido retorna 404.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001/issue" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001/issue"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001/issue';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Comprobante emitido):


{
    "success": true,
    "data": {
        "id": "019d867d-bbbb-7288-8ece-fd64da756999",
        "voucher_key": "50624062600310112345600100001010000000001100000001",
        "voucher_type": "01",
        "consecutive_number": "00100001010000000001",
        "status": "pending"
    },
    "message": "Comprobante emitido.",
    "errors": null
}
 

Example response (404, No encontrado):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Example response (422, Borrador incompleto):


{
    "success": false,
    "data": null,
    "message": "El borrador tiene datos incompletos para emitir.",
    "errors": {
        "line_items": [
            "Agregá al menos una línea de detalle."
        ],
        "sale_condition": [
            "Indicá la condición de venta."
        ]
    }
}
 

Example response (422, Falla del esquema oficial (XSD)):


{
    "success": false,
    "data": null,
    "message": "El comprobante no cumple el esquema oficial (XSD).",
    "errors": {
        "xsd": [
            "El elemento 'CodigoMoneda' no es válido."
        ]
    }
}
 

Example response (422, Sucursal/terminal inexistente):


{
    "success": false,
    "data": null,
    "message": "La sucursal/terminal indicada no existe.",
    "errors": {
        "terminal_code": [
            "Créela primero en Sucursales y terminales."
        ]
    }
}
 

Request      

POST api/v1/public/vouchers/drafts/{id}/issue

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del borrador a emitir. Example: 019d867d-aaaa-7288-8ece-fd64da756001

Descartar un borrador

requires authentication

Elimina el borrador indicado. No afecta a comprobantes ya emitidos (el borrador no es un comprobante fiscal). Un id que no sea un UUID válido retorna 404.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/vouchers/drafts/019d867d-aaaa-7288-8ece-fd64da756001';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Borrador descartado):


{
    "success": true,
    "data": null,
    "message": "Borrador descartado.",
    "errors": null
}
 

Example response (404, No encontrado):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Request      

DELETE api/v1/public/vouchers/drafts/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del borrador. Example: 019d867d-aaaa-7288-8ece-fd64da756001

Perfil

Estadísticas de uso mensual.

requires authentication

Retorna los conteos de comprobantes emitidos y emails enviados durante el período mensual en curso, junto con los límites de su plan.

Solo cuenta comprobantes de producción aceptados por Hacienda. Los comprobantes emitidos en sandbox no consumen el límite del plan.

Si es integrador, el conteo agrega automáticamente los comprobantes propios y los de todos sus clientes gestionados, permitiendo monitorear el consumo total del plan en una sola consulta.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/profile/usage" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/profile/usage"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/profile/usage';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Contribuyente normal):


{
    "success": true,
    "data": {
        "vouchers_this_month": 87,
        "vouchers_limit": 500,
        "vouchers_remaining": 413,
        "vouchers_percentage": 17.4,
        "emails_this_month": 340,
        "emails_limit": 2250,
        "period_start": "2026-04-01",
        "period_end": "2026-04-30"
    },
    "message": "Estadísticas de uso mensual.",
    "errors": null
}
 

Example response (200, Integrador (consumo agregado con todos sus clientes)):


{
    "success": true,
    "data": {
        "vouchers_this_month": 12450,
        "vouchers_limit": 50000,
        "vouchers_remaining": 37550,
        "vouchers_percentage": 24.9,
        "emails_this_month": 0,
        "emails_limit": 5000,
        "period_start": "2026-04-01",
        "period_end": "2026-04-30"
    },
    "message": "Estadísticas de uso mensual.",
    "errors": null
}
 

Request      

GET api/v1/public/profile/usage

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
vouchers_this_month   integer     

Comprobantes de producción aceptados emitidos en el mes actual. Si es integrador, suma propios + todos los clientes gestionados.

vouchers_limit   integer     

Límite mensual del plan contratado.

vouchers_remaining   integer     

Comprobantes restantes antes de alcanzar el límite: max(0, limit - this_month).

vouchers_percentage   number     

Porcentaje de uso del cupo mensual (0-100, 2 decimales).

emails_this_month   integer     

Correos transaccionales enviados en el mes en curso.

emails_limit   integer     

Límite MENSUAL de correos del plan. null = sin tope.

period_start   string     

Primer día del mes actual (YYYY-MM-DD). Zona horaria: America/Costa_Rica.

period_end   string     

Último día del mes actual (YYYY-MM-DD).

Consultar perfil completo del contribuyente autenticado.

requires authentication

Retorna todos los datos del contribuyente incluyendo el plan activo con sus limites y features, las actividades economicas registradas, la ubicacion y la configuracion de contacto.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/profile" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/profile"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/profile';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Perfil completo):


{
    "success": true,
    "data": {
        "id": "019d867d-0241-7288-8ece-fd64da75616d",
        "legal_name": "Empresa Ejemplo S.A.",
        "trade_name": "Ejemplo Shop",
        "id_type": "02",
        "id_type_label": "Cédula Jurídica",
        "id_number": "3101999999",
        "emails": [
            "facturacion@empresa.cr"
        ],
        "economic_activities": [
            "6201.0"
        ],
        "location": {
            "province": 1,
            "canton": 1,
            "district": 1
        },
        "neighborhood": "San Pedro",
        "address": "200 metros norte del parque central",
        "phone": {
            "country_code": 506,
            "number": "22001234"
        },
        "is_active": true,
        "production_enabled": false,
        "hacienda_environment": "sandbox",
        "plan_id": "019d0001-0000-0000-0000-000000000001",
        "is_managed": false,
        "is_integrator": false,
        "plan": {
            "id": "019d0001-0000-0000-0000-000000000001",
            "code": "pyme",
            "name": "Pyme",
            "description": "Automatice su facturación con hasta 500 comprobantes/mes.",
            "monthly_price_usd": null,
            "annual_price_usd": 96,
            "billing_mode": "annual_only",
            "annual_discount_percent": 0,
            "vouchers_per_month": 500,
            "overage_price_per_voucher": 0.03,
            "max_users": 3,
            "max_api_tokens": 2,
            "max_clients": 300,
            "max_items": 750,
            "max_pdf_templates": 1,
            "max_monthly_emails": 2250,
            "max_webhook_endpoints": 1,
            "max_branch_terminals": 2,
            "max_managed_contributors": 1,
            "api_rate_limit_per_minute": 20,
            "retention_months": 3,
            "extended_retention_eligible": true,
            "pdf_branding": "platform",
            "api_write_enabled": true,
            "api_readonly_enabled": false,
            "sandbox_access": true,
            "webhooks_enabled": false,
            "multi_contributor": false,
            "receiver_module": true,
            "bulk_emission": false,
            "sla_support": false
        },
        "created_at": "2026-04-01T10:00:00-06:00",
        "updated_at": "2026-04-13T14:30:00-06:00"
    },
    "message": "Perfil del contribuyente.",
    "errors": null
}
 

Request      

GET api/v1/public/profile

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
id   string     

UUID del contribuyente.

legal_name   string     

Razón social o nombre legal. Mapea a EmisorType/Nombre del XSD (min 5, max 100).

trade_name   string     

Nombre comercial. Mapea a EmisorType/NombreComercial del XSD. null si no se configuró.

id_type   string     

Tipo de identificación: 01=Física, 02=Jurídica, 03=DIMEX, 04=NITE. Inmutable.

id_type_label   string     

Nombre legible del tipo de identificación.

id_number   string     

Número de identificación del contribuyente.

emails   string[]     

Correos electrónicos del emisor (máximo 4). El primero mapea a CorreoElectronico del XSD.

economic_activities   string[]     

Códigos CIIU en formato Hacienda XXXX.X. Mapean a CodigoActividadEmisor del XSD.

location   object     

Ubicación del emisor (UbicacionType del XSD). null si no tiene ubicación configurada.

province   integer     

Código de provincia (1-7).

canton   integer     

Código de cantón.

district   integer     

Código de distrito.

neighborhood   string     

Barrio del emisor. null si no se configuró.

address   string     

Señas exactas del emisor. Mapea a OtrasSenas del XSD (max 250). null si no se configuró.

phone   object     

Teléfono del emisor (TelefonoType del XSD). null si no tiene teléfono configurado.

country_code   integer     

Código de país (1-3 dígitos). Default: 506.

number   string     

Número de teléfono.

is_active   boolean     

true si la cuenta está activa. Solo un admin de plataforma puede suspenderla.

production_enabled   boolean     

true si la cuenta está autorizada por DGT (BCCR + cumplimiento normativo) para emitir comprobantes con valor fiscal real ante Hacienda. Fuente de verdad del estado fiscal del contribuyente.

hacienda_environment   string     

@deprecated Derivado de production_enabled. Para nuevas integraciones use production_enabled directamente. Valores: sandbox (production_enabled=false, modo pruebas) o production (production_enabled=true, autorizado para emisión real). El ambiente del voucher individual se determina por la URL del request (/sandbox/ vs producción), NO por este campo.

plan_id   string     

UUID del plan contratado.

is_managed   boolean     

true si el contribuyente es gestionado por al menos un integrador.

is_integrator   boolean     

true si el contribuyente tiene plan Integrador y gestiona otros clientes.

plan   object     

Detalle completo del plan contratado con todos los límites y features.

code   string     

Código del plan: free, starter, pyme, professional, business, integrator.

name   string     

Nombre comercial del plan.

vouchers_per_month   integer     

Límite mensual de comprobantes.

max_clients   integer     

Máximo de clientes en el catálogo.

max_items   integer     

Máximo de items en el catálogo.

max_webhook_endpoints   integer     

Máximo de webhook endpoints.

max_pdf_templates   integer     

Máximo de plantillas PDF.

max_monthly_emails   integer     

Máximo de correos por mes.

api_rate_limit_per_minute   integer     

Requests por minuto permitidos.

sandbox_access   boolean     

true si el plan incluye acceso a la API sandbox.

pdf_branding   string     

Nivel de branding: platform, minimal o white_label.

created_at   string     

Fecha de creación de la cuenta (ISO 8601).

updated_at   string     

Fecha de última actualización (ISO 8601).

Actualizar datos editables del perfil del contribuyente.

requires authentication

Soporta actualizacion parcial: envie unicamente los campos que desea modificar. Los campos no incluidos en el payload conservan su valor actual.

Campos editables: id_number, legal_name, trade_name, emails, economic_activities, phone, phone_country_code, province, canton, district, neighborhood, address.

Campos inmutables (no se pueden cambiar desde este endpoint): id_type (tipo de identificacion), el plan activo, production_enabled (autorización DGT | gestionada por super_admin tras verificar firma BCCR y cumplimiento normativo) y el estado activo/inactivo de la cuenta.

La configuracion de email transaccional se gestiona desde su propio endpoint: PUT /email-settings.

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/profile" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"id_number\": \"3101999999\",
    \"id_type\": 2,
    \"hacienda_confirmed\": true,
    \"legal_name\": \"Empresa Ejemplo S.A.\",
    \"trade_name\": \"Ejemplo Tienda\",
    \"emails\": [
        \"facturacion@empresa.cr\"
    ],
    \"economic_activities\": [
        \"6201.0\"
    ],
    \"phone\": \"22001234\",
    \"phone_country_code\": 506,
    \"province\": 1,
    \"canton\": 1,
    \"district\": 1,
    \"neighborhood\": \"San Pedro\",
    \"address\": \"200 metros norte del parque central\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/profile"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "id_number": "3101999999",
    "id_type": 2,
    "hacienda_confirmed": true,
    "legal_name": "Empresa Ejemplo S.A.",
    "trade_name": "Ejemplo Tienda",
    "emails": [
        "facturacion@empresa.cr"
    ],
    "economic_activities": [
        "6201.0"
    ],
    "phone": "22001234",
    "phone_country_code": 506,
    "province": 1,
    "canton": 1,
    "district": 1,
    "neighborhood": "San Pedro",
    "address": "200 metros norte del parque central"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/profile';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'id_number' => '3101999999',
            'id_type' => 2,
            'hacienda_confirmed' => true,
            'legal_name' => 'Empresa Ejemplo S.A.',
            'trade_name' => 'Ejemplo Tienda',
            'emails' => [
                'facturacion@empresa.cr',
            ],
            'economic_activities' => [
                '6201.0',
            ],
            'phone' => '22001234',
            'phone_country_code' => 506,
            'province' => 1,
            'canton' => 1,
            'district' => 1,
            'neighborhood' => 'San Pedro',
            'address' => '200 metros norte del parque central',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Perfil actualizado):


{
    "success": true,
    "data": {
        "id": "019d867d-0241-7288-8ece-fd64da75616d",
        "legal_name": "Empresa Ejemplo S.A. Actualizada",
        "trade_name": "Ejemplo Shop Nuevo",
        "id_type": "02",
        "id_type_label": "Cédula Jurídica",
        "id_number": "3101999999",
        "emails": [
            "facturacion@empresa.cr",
            "contabilidad@empresa.cr"
        ],
        "economic_activities": [
            "6201.0",
            "4711.0"
        ],
        "location": {
            "province": 1,
            "canton": 1,
            "district": 1
        },
        "neighborhood": "San Pedro",
        "address": "200 metros norte del parque central",
        "phone": {
            "country_code": 506,
            "number": "22001234"
        },
        "is_active": true,
        "production_enabled": false,
        "hacienda_environment": "sandbox",
        "plan_id": "019d0001-0000-0000-0000-000000000001",
        "is_managed": false,
        "is_integrator": false,
        "plan": {
            "id": "019d0001-0000-0000-0000-000000000001",
            "code": "pyme",
            "name": "Pyme",
            "vouchers_per_month": 500,
            "max_clients": 300,
            "sandbox_access": true
        },
        "created_at": "2026-04-01T10:00:00-06:00",
        "updated_at": "2026-04-16T09:15:00-06:00"
    },
    "message": "Perfil actualizado correctamente.",
    "errors": null
}
 

Example response (422, Error de validación):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "emails.0": [
            "El campo emails.0 debe ser un correo electrónico válido."
        ],
        "economic_activities.0": [
            "El formato del código de actividad económica debe ser XXXX.X (ejemplo: 6201.0)."
        ]
    }
}
 

Request      

PUT api/v1/public/profile

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

id_number   string  optional    

Numero de identificacion. Solo digitos, 5-12 caracteres. Cambiar este valor afecta la clave de 50 digitos en comprobantes futuros. Must match the regex /^\d{5,12}$/. Example: 3101999999

id_type   string  optional    

Example: 2

Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
hacienda_confirmed   boolean  optional    

Example: true

legal_name   string  optional    

Razon social o nombre completo. Min 5, max 100 caracteres. El campo value debe tener al menos 5 caracteres. El campo value no puede tener más de 100 caracteres. Example: Empresa Ejemplo S.A.

trade_name   string  optional    

Nombre comercial. Opcional, min 3, max 80 caracteres. El campo value debe tener al menos 3 caracteres. El campo value no puede tener más de 80 caracteres. Example: Ejemplo Tienda

emails   string[]  optional    

Correo electrónico individual. Max 160 chars. El campo value no es un correo electrónico válido. El campo value no puede tener más de 160 caracteres.

economic_activities   string[]     

Código CIIU individual en formato XXXX.X. Must match the regex /^\d{4}.\d$/.

phone   string  optional    

Número de teléfono sin código de país. Solo dígitos, 4-20 chars. Must match the regex /^\d{4,20}$/. Example: 22001234

phone_country_code   integer  optional    

Código de país telefónico (1-999). Default 506 (Costa Rica). El campo value debe estar entre 1 y 999. Example: 506

province   integer  optional    

Código de provincia (1-7). Si se envía, canton y district son obligatorios. El campo value debe estar entre 1 y 7. Example: 1

canton   integer  optional    

Código de cantón (1-99). Obligatorio si province presente. El campo value debe estar entre 1 y 99. Example: 1

district   integer  optional    

Código de distrito (1-99). Obligatorio si province presente. El campo value debe estar entre 1 y 99. Example: 1

neighborhood   string  optional    

Barrio. Opcional, min 5, max 50 chars. El campo value debe tener al menos 5 caracteres. El campo value no puede tener más de 50 caracteres. Example: San Pedro

address   string  optional    

Senas exactas de la direccion. Min 5, max 250 caracteres. El campo value debe tener al menos 5 caracteres. El campo value no puede tener más de 250 caracteres. Example: 200 metros norte del parque central

Usuarios de la Cuenta

Listar usuarios del contribuyente

requires authentication

Devuelve todos los usuarios (cuentas de login) asociados al contribuyente activo: los activos, los que están pendientes de establecer su contraseña y los desactivados. Los usuarios cuyo acceso fue revocado no aparecen.

Cada usuario es una cuenta de login (nombre + correo + rol), sin cédula | la cédula vive en el contribuyente, no en el usuario.

Solo el owner o un admin del contribuyente pueden consultar esta lista. Otros roles reciben HTTP 403.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/users" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/users"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/users';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado de usuarios):


{
    "success": true,
    "data": [
        {
            "id": "019d867d-0001-7288-8ece-fd64da756001",
            "name": "Ana Propietaria",
            "email": "ana@empresa.cr",
            "role": "owner",
            "is_active": true,
            "is_owner": true,
            "status": "active",
            "created_at": "2026-04-01T10:00:00-06:00"
        },
        {
            "id": "019d867d-0002-7288-8ece-fd64da756002",
            "name": "María Pérez",
            "email": "maria@empresa.cr",
            "role": "cashier",
            "is_active": true,
            "is_owner": false,
            "status": "pending",
            "created_at": "2026-04-10T14:30:00-06:00"
        }
    ],
    "message": "",
    "errors": null
}
 

Example response (403, Sin permisos):


{
    "success": false,
    "data": null,
    "message": "No tenés permisos para gestionar usuarios.",
    "errors": null
}
 

Request      

GET api/v1/public/users

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Invitar un usuario

requires authentication

Crea una cuenta de login (sin cédula) y la asocia al contribuyente activo. El usuario recibe un correo con un enlace para establecer su contraseña; mientras tanto queda en estado pending.

Reglas:

Solo el owner o un admin pueden invitar usuarios. Otros roles reciben HTTP 403.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/users" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"María Pérez\",
    \"email\": \"maria@empresa.cr\",
    \"role\": \"cashier\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/users"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "María Pérez",
    "email": "maria@empresa.cr",
    "role": "cashier"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/users';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'María Pérez',
            'email' => 'maria@empresa.cr',
            'role' => 'cashier',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Usuario invitado):


{
    "success": true,
    "data": {
        "id": "019d867d-0002-7288-8ece-fd64da756002",
        "name": "María Pérez",
        "email": "maria@empresa.cr",
        "role": "cashier",
        "is_active": true,
        "is_owner": false,
        "status": "pending",
        "created_at": "2026-04-16T10:00:00-06:00"
    },
    "message": "Usuario invitado. Recibirá un correo para establecer su contraseña.",
    "errors": null
}
 

Example response (403, Sin permisos):


{
    "success": false,
    "data": null,
    "message": "No tenés permisos para gestionar usuarios.",
    "errors": null
}
 

Example response (422, Correo duplicado):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "email": [
            "Ya existe un usuario con ese correo."
        ]
    }
}
 

Example response (422, Rol inválido):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "role": [
            "El rol seleccionado no es válido."
        ]
    }
}
 

Request      

POST api/v1/public/users

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Nombre del usuario a invitar (máx. 150 chars). Example: María Pérez

email   string     

Correo del usuario (único). Recibe la invitación / magic link. Example: maria@empresa.cr

role   string     

Rol del usuario: admin, accountant, cashier, viewer o api_user. Example: cashier

Editar un usuario

requires authentication

Permite cambiar el rol y/o el estado (activar / desactivar) de un usuario del contribuyente. Admite actualización parcial | envíe solo los campos que desea modificar.

Acción sensible: cambiar el estado is_active requiere enviar el campo password con la contraseña del usuario que opera. Si falta o es incorrecta, responde HTTP 401.

Al activar un usuario se respeta el max_users del plan: si ya se alcanzó el límite de usuarios activos, responde HTTP 422.

El propietario de la cuenta no puede modificarse (HTTP 403), y el rol owner no puede asignarse desde aquí. Solo el owner o un admin pueden editar usuarios.

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/users/019d867d-0002-7288-8ece-fd64da756002" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"role\": \"viewer\",
    \"is_active\": true,
    \"password\": \"MiClaveSegura123\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/users/019d867d-0002-7288-8ece-fd64da756002"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "role": "viewer",
    "is_active": true,
    "password": "MiClaveSegura123"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/users/019d867d-0002-7288-8ece-fd64da756002';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'role' => 'viewer',
            'is_active' => true,
            'password' => 'MiClaveSegura123',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Usuario actualizado):


{
    "success": true,
    "data": {
        "id": "019d867d-0002-7288-8ece-fd64da756002",
        "name": "María Pérez",
        "email": "maria@empresa.cr",
        "role": "viewer",
        "is_active": true,
        "is_owner": false,
        "status": "active",
        "created_at": "2026-04-10T14:30:00-06:00"
    },
    "message": "Usuario actualizado.",
    "errors": null
}
 

Example response (401, Contraseña incorrecta):


{
    "success": false,
    "data": null,
    "message": "Contraseña incorrecta.",
    "errors": null
}
 

Example response (403, Sin permisos):


{
    "success": false,
    "data": null,
    "message": "No tenés permisos para gestionar usuarios.",
    "errors": null
}
 

Example response (403, Es el propietario):


{
    "success": false,
    "data": null,
    "message": "No se puede modificar al propietario de la cuenta.",
    "errors": null
}
 

Example response (404, Usuario no encontrado):


{
    "success": false,
    "data": null,
    "message": "Usuario no encontrado en este contribuyente.",
    "errors": null
}
 

Example response (422, Límite del plan alcanzado):


{
    "success": false,
    "data": null,
    "message": "Alcanzaste el límite de usuarios activos de tu plan (3).",
    "errors": {
        "is_active": [
            "Desactivá otro usuario o cambiá de plan."
        ]
    }
}
 

Request      

PUT api/v1/public/users/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del usuario a editar. Example: 019d867d-0002-7288-8ece-fd64da756002

Body Parameters

role   string  optional    

Nuevo rol del usuario: admin, accountant, cashier, viewer o api_user. Example: viewer

is_active   boolean  optional    

Activar (true) o desactivar (false) el usuario. Example: true

password   string  optional    

Contraseña del usuario que opera. Requerida solo cuando cambia is_active. Example: MiClaveSegura123

Revocar el acceso de un usuario

requires authentication

Desvincula al usuario del contribuyente activo, liberando su asiento del plan. La cuenta de login global del usuario no se elimina: sigue viva para que pueda asociarse a otro contribuyente o facturar por su cuenta. Simplemente deja de pertenecer a esta cuenta y desaparece del listado.

Acción sensible: requiere enviar el campo password con la contraseña del usuario que opera. Si falta o es incorrecta, responde HTTP 401.

No puede revocar su propio acceso ni el del propietario de la cuenta (ambos casos responden HTTP 403). Solo el owner o un admin pueden revocar usuarios.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/users/019d867d-0002-7288-8ece-fd64da756002" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"password\": \"MiClaveSegura123\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/users/019d867d-0002-7288-8ece-fd64da756002"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "password": "MiClaveSegura123"
};

fetch(url, {
    method: "DELETE",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/users/019d867d-0002-7288-8ece-fd64da756002';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'password' => 'MiClaveSegura123',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Acceso revocado):


{
    "success": true,
    "data": null,
    "message": "Usuario desvinculado de esta cuenta.",
    "errors": null
}
 

Example response (401, Contraseña incorrecta):


{
    "success": false,
    "data": null,
    "message": "Contraseña incorrecta.",
    "errors": null
}
 

Example response (403, Sin permisos):


{
    "success": false,
    "data": null,
    "message": "No tenés permisos para gestionar usuarios.",
    "errors": null
}
 

Example response (403, No puede revocarse a sí mismo):


{
    "success": false,
    "data": null,
    "message": "No podés revocar tu propio acceso.",
    "errors": null
}
 

Example response (403, Es el propietario):


{
    "success": false,
    "data": null,
    "message": "No se puede revocar al propietario de la cuenta.",
    "errors": null
}
 

Example response (404, Usuario no encontrado):


{
    "success": false,
    "data": null,
    "message": "Usuario no encontrado en este contribuyente.",
    "errors": null
}
 

Request      

DELETE api/v1/public/users/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del usuario a revocar. Example: 019d867d-0002-7288-8ece-fd64da756002

Body Parameters

password   string     

Contraseña del usuario que opera (confirmación de la acción sensible). Example: MiClaveSegura123

Cambio de Contribuyente

Listar contribuyentes accesibles

requires authentication

Devuelve los contribuyentes a los que el usuario autenticado tiene acceso operativo (pivote user_contributors activo y aceptado), por ejemplo como dueño de su propia empresa o como contador de varios clientes. Solo se incluyen los contribuyentes que estén activos.

El campo is_current indica cuál de ellos es el contribuyente activo de la sesión (el que lee el middleware multi-tenant). Use el endpoint POST /switch-contributor para cambiarlo.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/my-access-contributors" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-access-contributors"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-access-contributors';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Contribuyentes accesibles):


{
    "success": true,
    "data": [
        {
            "contributor_id": "019d867d-0001-7288-8ece-fd64da756001",
            "legal_name": "EMPRESA EJEMPLO S.A.",
            "id_number": "3101123456",
            "role": "owner",
            "is_current": true
        },
        {
            "contributor_id": "019d867d-0002-7288-8ece-fd64da756002",
            "legal_name": "CLIENTE CONTABLE S.A.",
            "id_number": "3101987654",
            "role": "accountant",
            "is_current": false
        }
    ],
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/my-access-contributors

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
contributor_id   string     

UUID del contribuyente. Úselo como contributor_id en POST /switch-contributor.

legal_name   string     

Nombre legal o razón social del contribuyente.

id_number   string     

Número de identificación (cédula) del contribuyente.

role   string     

Rol del usuario sobre ese contribuyente (por ejemplo owner o accountant).

is_current   boolean     

true si es el contribuyente activo de la sesión actual.

Cambiar el contribuyente activo

requires authentication

Cambia el contribuyente activo de la sesión del usuario autenticado. El usuario debe tener acceso operativo (pivote user_contributors activo y aceptado) al contribuyente destino, y este debe estar activo. El mismo token sigue siendo válido tras el cambio.

Si el usuario no tiene acceso al contribuyente indicado, responde HTTP 403. Si el contribuyente no existe o está inactivo, responde HTTP 404.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/switch-contributor" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"contributor_id\": \"019d867d-0002-7288-8ece-fd64da756002\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/switch-contributor"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "contributor_id": "019d867d-0002-7288-8ece-fd64da756002"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/switch-contributor';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'contributor_id' => '019d867d-0002-7288-8ece-fd64da756002',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Contribuyente cambiado):


{
    "success": true,
    "data": {
        "contributor_id": "019d867d-0002-7288-8ece-fd64da756002",
        "legal_name": "CLIENTE CONTABLE S.A.",
        "role": "accountant"
    },
    "message": "Contribuyente activo cambiado.",
    "errors": null
}
 

Example response (403, Sin acceso al contribuyente):


{
    "success": false,
    "data": null,
    "message": "No tenés acceso a ese contribuyente.",
    "errors": null
}
 

Example response (404, Contribuyente inexistente o inactivo):


{
    "success": false,
    "data": null,
    "message": "El contribuyente no existe o está inactivo.",
    "errors": null
}
 

Example response (422, Validación fallida):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "contributor_id": [
            "El campo contributor id es obligatorio."
        ]
    }
}
 

Request      

POST api/v1/public/switch-contributor

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

contributor_id   string     

UUID del contribuyente al que se quiere cambiar. Example: 019d867d-0002-7288-8ece-fd64da756002

Certificados Digitales

Listar certificados digitales del contribuyente

requires authentication

Devuelve el historial completo de certificados digitales registrados por el contribuyente: el certificado actualmente activo y todos los que fueron desactivados previamente (conservados para auditoría).

Los datos sensibles del certificado (contenido del archivo .p12, contraseña y PIN de Hacienda) nunca se incluyen en la respuesta, incluso si usted mismo los cargó.

Cada registro incluye:

Ordenamiento: el más reciente primero.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/certificates" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/certificates"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/certificates';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado de certificados):


{
    "success": true,
    "data": [],
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/certificates

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Consultar disponibilidad de subida de certificado por ambiente

requires authentication

Indica para cada ambiente (sandbox, production) si está disponible para subir un certificado nuevo o si ya hay uno activo que debe desactivarse primero.

Útil para mostrar el formulario de subida con el selector de ambiente correctamente deshabilitado, evitando que el usuario intente subir un .p12 que sería rechazado con HTTP 422.

Reglas:

Cuando available = false, el campo active_certificate incluye los datos del certificado vigente para que el frontend pueda mostrar al usuario qué cert está bloqueando el upload.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/certificates/availability" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/certificates/availability"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/certificates/availability';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Sandbox disponible, producción ocupada):


{
    "success": true,
    "data": {
        "sandbox": {
            "available": true,
            "active_certificate": null
        },
        "production": {
            "available": false,
            "active_certificate": {
                "id": "019d867d-1111-7288-8ece-fd64da756001",
                "certificate_serial": "019DD6D90F43",
                "certificate_subject": "CN=ALBERT MENDEZ, serialNumber=CPF-02-0627-0652",
                "valid_from": "2025-12-15T10:30:00-06:00",
                "valid_until": "2027-12-15T10:30:00-06:00",
                "days_remaining": 596,
                "is_expired": false,
                "created_at": "2026-04-15T08:00:00-06:00"
            }
        }
    },
    "message": "",
    "errors": null
}
 

Example response (200, Ambos ambientes disponibles):


{
    "success": true,
    "data": {
        "sandbox": {
            "available": true,
            "active_certificate": null
        },
        "production": {
            "available": true,
            "active_certificate": null
        }
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/certificates/availability

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
sandbox   object     
available   boolean     

true si el ambiente sandbox está disponible para subir un cert nuevo, false si ya hay uno activo.

active_certificate   object     

Datos del certificado activo en sandbox. null si no hay activo.

production   object     
available   boolean     

Igual al de sandbox pero para producción.

active_certificate   object     

Datos del certificado activo en producción. null si no hay activo.

Subir y activar un certificado digital `.p12`

requires authentication

Sube un archivo .p12 (PKCS#12) con su contraseña y la contraseña del IdP de Hacienda, y lo activa como certificado para firmar comprobantes en el ambiente indicado.

Este request debe enviarse como multipart/form-data (no JSON), porque incluye un archivo binario.

Validaciones que se ejecutan ANTES de guardar:

  1. Disponibilidad del ambiente: solo puede subir cert si NO hay uno activo en ese ambiente. Si ya hay uno, debe desactivarlo primero con DELETE /certificates/{id} o usar el endpoint GET /certificates/availability para chequear estado.
  2. PIN del .p12: el archivo debe abrirse con la contraseña provista en p12_password.
  3. Llave privada RSA: el .p12 debe contener una llave RSA válida (Hacienda CR exige RSA-SHA256 para XAdES-EPES).
  4. Subject del certificado: el cert debe pertenecer al contribuyente que lo está subiendo (anti-fraude | REGLAMENTO art. 16).
  5. Vigencia del certificado: el X.509 no puede estar vencido ni ser prematuro.
  6. Credenciales IdP de Hacienda: el hacienda_pin debe autenticar correctamente contra el IdP del ambiente solicitado (POST grant_type=password). Si falla, el cert NO se guarda | sería inutilizable sin token OAuth2 válido.

Si CUALQUIERA de las 6 validaciones falla, se retorna HTTP 422 (o 503 si el IdP de Hacienda no responde) y NADA se guarda en DB.

Ejemplo con curl:

curl -X POST https://api.almendro.cr/api/v1/public/certificates \
  -H "Authorization: Bearer {su_token}" \
  -F "p12_file=@/ruta/a/certificado.p12" \
  -F "p12_password=PIN-del-archivo-p12" \
  -F "hacienda_pin=Contraseña-del-IdP-de-Hacienda" \
  -F "environment=sandbox"
Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/certificates" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "p12_password=architecto"\
    --form "hacienda_pin=architecto"\
    --form "environment=architecto"\
    --form "p12_file=@/tmp/phpbon97l1emqj1dgYHhsD" 
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/certificates"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('p12_password', 'architecto');
body.append('hacienda_pin', 'architecto');
body.append('environment', 'architecto');
body.append('p12_file', document.querySelector('input[name="p12_file"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/certificates';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'multipart/form-data',
            'Accept' => 'application/json',
        ],
        'multipart' => [
            [
                'name' => 'p12_password',
                'contents' => 'architecto'
            ],
            [
                'name' => 'hacienda_pin',
                'contents' => 'architecto'
            ],
            [
                'name' => 'environment',
                'contents' => 'architecto'
            ],
            [
                'name' => 'p12_file',
                'contents' => fopen('/tmp/phpbon97l1emqj1dgYHhsD', 'r')
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Certificado registrado exitosamente):


{
    "success": true,
    "data": {
        "id": "019d867d-1111-7288-8ece-fd64da756001",
        "environment": "sandbox",
        "is_active": true,
        "is_expired": false,
        "days_remaining": 596,
        "valid_from": "2025-12-15T10:30:00-06:00",
        "valid_until": "2027-12-15T10:30:00-06:00",
        "certificate_subject": "CN=ALBERT MENDEZ, serialNumber=CPF-02-0627-0652",
        "certificate_serial": "019DD6C62577",
        "created_at": "2026-04-29T08:00:00-06:00"
    },
    "message": "Certificado registrado y activado correctamente.",
    "errors": null
}
 

Example response (422, PIN del archivo .p12 incorrecto (5001)):


{
    "success": false,
    "data": null,
    "message": "El PIN del archivo .p12 es incorrecto, el archivo está corrupto, o no es un PKCS#12 válido emitido por el BCCR. ...",
    "errors": {
        "p12_password": [
            "El PIN del archivo .p12 es incorrecto, ..."
        ]
    }
}
 

Example response (422, Cert no pertenece al contribuyente (5002)):


{
    "success": false,
    "data": null,
    "message": "El certificado .p12 NO pertenece a este contribuyente. ...",
    "errors": {
        "p12_file": [
            "El certificado .p12 NO pertenece a este contribuyente. ..."
        ]
    }
}
 

Example response (422, Credenciales IdP rechazadas (5003)):


{
    "success": false,
    "data": null,
    "message": "El IdP de Hacienda rechazó las credenciales con HTTP 401. ...",
    "errors": {
        "hacienda_pin": [
            "El IdP de Hacienda rechazó las credenciales con HTTP 401. ..."
        ]
    }
}
 

Example response (422, Ya hay cert activo en el ambiente (5004)):


{
    "success": false,
    "data": null,
    "message": "Ya existe un certificado activo en el ambiente 'sandbox'. ...",
    "errors": {
        "environment": [
            "Ya existe un certificado activo en el ambiente 'sandbox'. ..."
        ]
    }
}
 

Example response (422, El .p12 pertenece al otro ambiente (5005)):


{
    "success": false,
    "data": null,
    "message": "El certificado .p12 pertenece al ambiente de sandbox (pruebas) pero usted lo está subiendo como producción. ...",
    "errors": {
        "p12_file": [
            "El certificado .p12 pertenece al ambiente de sandbox (pruebas) pero usted lo está subiendo como producción. ..."
        ]
    }
}
 

Example response (422, Perfil sin identificación registrada (5006)):


{
    "success": false,
    "data": null,
    "message": "No es posible validar el certificado porque el contribuyente todavía no tiene registrada su identificación (tipo y número de cédula) en el perfil. ...",
    "errors": {
        "p12_file": [
            "No es posible validar el certificado porque el contribuyente todavía no tiene registrada su identificación ..."
        ]
    }
}
 

Example response (422, Certificado vencido (2002)):


{
    "success": false,
    "data": null,
    "message": "El certificado del contribuyente '...' no está vigente. ...",
    "errors": {
        "p12_file": [
            "El certificado del contribuyente '...' no está vigente. ..."
        ]
    }
}
 

Example response (503, IdP de Hacienda no responde temporalmente):


{
    "success": false,
    "data": null,
    "message": "El IdP de Hacienda no responde temporalmente. Intente nuevamente en unos minutos.",
    "errors": null
}
 

Request      

POST api/v1/public/certificates

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

Body Parameters

p12_file   file     

Archivo de certificado digital en formato PKCS#12 (extensión .p12 o .pfx). Tamaño máximo: 5 MB. Example: /tmp/phpbon97l1emqj1dgYHhsD

p12_password   string     

Contraseña del archivo .p12 (PIN del archivo). NO confunda con la contraseña del IdP de Hacienda | son distintas. Example: architecto

hacienda_pin   string     

Contraseña del IdP de Hacienda (asignada por TRIBU-CR al registrarse como emisor). NO confunda con el PIN del archivo .p12. Example: architecto

environment   string     

Ambiente: sandbox o production. Example: architecto

Desactivar un certificado digital

requires authentication

Marca el certificado como inactivo. El registro no se elimina: permanece en el histórico para auditoría de todas las firmas realizadas durante su vigencia.

Consecuencias inmediatas:

Solo puede desactivar certificados propios. Si el UUID no corresponde a su contribuyente, retorna 404.

Casos típicos para desactivar:

  • El certificado fue comprometido (pérdida, acceso no autorizado).
  • Va a subir un certificado nuevo (debe desactivar el vigente primero | el sistema no permite dos activos en el mismo ambiente).
  • Migración de un contribuyente a otra plataforma.
Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/certificates/019d867d-1111-7288-8ece-fd64da756001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/certificates/019d867d-1111-7288-8ece-fd64da756001"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/certificates/019d867d-1111-7288-8ece-fd64da756001';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Certificado desactivado (sin accesos asociados)):


{
    "success": true,
    "data": null,
    "message": "Certificado desactivado correctamente.",
    "errors": null
}
 

Example response (200, Certificado desactivado con auto-revocación de accesos):


{
    "success": true,
    "data": null,
    "message": "Certificado desactivado correctamente. Se revocaron 2 accesos de integradores asociados.",
    "errors": null
}
 

Example response (404, Certificado no encontrado o no pertenece al contribuyente):


{
    "success": false,
    "data": null,
    "message": "Certificado no encontrado.",
    "errors": null
}
 

Request      

DELETE api/v1/public/certificates/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del certificado a desactivar. Example: 019d867d-1111-7288-8ece-fd64da756001

Plantillas PDF

Listar plantillas PDF del contribuyente

requires authentication

Devuelve todas las plantillas PDF activas del contribuyente, ordenadas con la plantilla por defecto primero (is_default=true) y luego por nombre alfabéticamente.

Este endpoint no pagina | el número de plantillas por contribuyente es pequeño (límite por plan entre 1 y 15), por lo que siempre se devuelve la lista completa.

Cada plantilla incluye su configuración completa (config_json) con colores, fuentes, layout y parámetros del QR, además de un flag has_logo que indica si tiene un logo cargado.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/pdf-templates" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/pdf-templates"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/pdf-templates';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado de plantillas (default primero)):


{
    "success": true,
    "data": [
        {
            "id": 1,
            "name": "Classic",
            "is_default": true,
            "paper_size": "letter",
            "config_json": {
                "colors": {
                    "primary": "#2d3748",
                    "secondary": "#4a5568",
                    "accent": "#3182ce"
                },
                "fonts": {
                    "family": "Helvetica",
                    "size_header": 14,
                    "size_body": 9
                },
                "layout": {
                    "show_logo": true,
                    "logo_position": "left",
                    "logo_max_height": 60
                },
                "qr": {
                    "enabled": true,
                    "size": 100,
                    "position": "bottom-right"
                }
            },
            "has_logo": true,
            "is_active": true,
            "created_at": "2026-04-01T10:00:00-06:00",
            "updated_at": "2026-04-10T14:30:00-06:00"
        },
        {
            "id": 2,
            "name": "Minimal",
            "is_default": false,
            "paper_size": "A4",
            "config_json": {
                "colors": {
                    "primary": "#000000",
                    "secondary": "#666666",
                    "accent": "#000000"
                },
                "fonts": {
                    "family": "Helvetica",
                    "size_header": 12,
                    "size_body": 8
                },
                "layout": {
                    "show_logo": false,
                    "logo_position": "left",
                    "logo_max_height": 60
                },
                "qr": {
                    "enabled": true,
                    "size": 80,
                    "position": "bottom-right"
                }
            },
            "has_logo": false,
            "is_active": true,
            "created_at": "2026-04-05T08:00:00-06:00",
            "updated_at": "2026-04-05T08:00:00-06:00"
        }
    ],
    "message": "",
    "errors": null
}
 

Example response (401, No autenticado):


{
    "success": false,
    "data": null,
    "message": "Unauthenticated.",
    "errors": null
}
 

Request      

GET api/v1/public/pdf-templates

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Crear una nueva plantilla PDF

requires authentication

Registra una plantilla personalizada en el catálogo del contribuyente. Si envía is_default: true, la plantilla default anterior se desmarca automáticamente.

Si no envía config_json, se aplica una configuración por defecto que cumple con todos los requisitos normativos (QR mínimo 2.5 cm en la esquina inferior derecha).

Si envía config_json, puede enviar un objeto parcial | las claves no enviadas usan los valores del template default.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/pdf-templates" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Mi Plantilla Corporativa\",
    \"is_default\": false,
    \"paper_size\": \"letter\",
    \"config_json\": {
        \"colors\": {
            \"primary\": \"#0a66c2\",
            \"secondary\": \"#333333\",
            \"accent\": \"#0a66c2\"
        },
        \"fonts\": {
            \"family\": \"Helvetica\",
            \"size_header\": 14,
            \"size_body\": 9
        },
        \"layout\": {
            \"show_logo\": true,
            \"logo_position\": \"left\",
            \"logo_max_height\": 60
        },
        \"qr\": {
            \"enabled\": true,
            \"size\": 100,
            \"position\": \"bottom-right\"
        },
        \"margins\": {
            \"top\": 15,
            \"right\": 15,
            \"bottom\": 15,
            \"left\": 15
        },
        \"style\": \"classic\",
        \"footer_text\": \"Documento generado electrónicamente | Almendro Factura Electrónica\"
    }
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/pdf-templates"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Mi Plantilla Corporativa",
    "is_default": false,
    "paper_size": "letter",
    "config_json": {
        "colors": {
            "primary": "#0a66c2",
            "secondary": "#333333",
            "accent": "#0a66c2"
        },
        "fonts": {
            "family": "Helvetica",
            "size_header": 14,
            "size_body": 9
        },
        "layout": {
            "show_logo": true,
            "logo_position": "left",
            "logo_max_height": 60
        },
        "qr": {
            "enabled": true,
            "size": 100,
            "position": "bottom-right"
        },
        "margins": {
            "top": 15,
            "right": 15,
            "bottom": 15,
            "left": 15
        },
        "style": "classic",
        "footer_text": "Documento generado electrónicamente | Almendro Factura Electrónica"
    }
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/pdf-templates';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Mi Plantilla Corporativa',
            'is_default' => false,
            'paper_size' => 'letter',
            'config_json' => [
                'colors' => [
                    'primary' => '#0a66c2',
                    'secondary' => '#333333',
                    'accent' => '#0a66c2',
                ],
                'fonts' => [
                    'family' => 'Helvetica',
                    'size_header' => 14,
                    'size_body' => 9,
                ],
                'layout' => [
                    'show_logo' => true,
                    'logo_position' => 'left',
                    'logo_max_height' => 60,
                ],
                'qr' => [
                    'enabled' => true,
                    'size' => 100,
                    'position' => 'bottom-right',
                ],
                'margins' => [
                    'top' => 15,
                    'right' => 15,
                    'bottom' => 15,
                    'left' => 15,
                ],
                'style' => 'classic',
                'footer_text' => 'Documento generado electrónicamente | Almendro Factura Electrónica',
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Plantilla creada):


{
    "success": true,
    "data": {
        "id": 3,
        "name": "Mi Plantilla Corporativa",
        "is_default": false,
        "paper_size": "letter",
        "config_json": {
            "colors": {
                "primary": "#0a66c2",
                "secondary": "#333333",
                "accent": "#0a66c2"
            },
            "fonts": {
                "family": "Helvetica",
                "size_header": 14,
                "size_body": 9
            },
            "layout": {
                "show_logo": true,
                "logo_position": "left",
                "logo_max_height": 60
            },
            "qr": {
                "enabled": true,
                "size": 100,
                "position": "bottom-right"
            }
        },
        "has_logo": false,
        "is_active": true,
        "created_at": "2026-04-16T10:00:00-06:00",
        "updated_at": "2026-04-16T10:00:00-06:00"
    },
    "message": "Plantilla creada correctamente.",
    "errors": null
}
 

Example response (422, Nombre duplicado):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "name": [
            "Ya existe una plantilla con este nombre para el contribuyente."
        ]
    }
}
 

Example response (422, Color hexadecimal inválido):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "config_json.colors.primary": [
            "El color debe ser un hexadecimal válido (ej. #1a365d)."
        ]
    }
}
 

Example response (422, Tamaño del QR por debajo del mínimo normativo):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "config_json.qr.size": [
            "El tamaño mínimo del QR es 70 pt (≈ 2.5 cm) por normativa."
        ]
    }
}
 

Example response (422, Límite del plan alcanzado):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan": [
            "Ha alcanzado el límite de plantillas PDF de su plan. Actualice a un plan superior."
        ]
    }
}
 

Request      

POST api/v1/public/pdf-templates

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

name   string     

Nombre identificable de la plantilla (3-100 caracteres). Único por contribuyente. Example: Mi Plantilla Corporativa

is_default   boolean  optional    

Si será la plantilla por defecto. Si true, desmarca la anterior automáticamente. Default: false. Example: false

paper_size   string  optional    

Tamaño del papel. Valores: letter o A4. Default: letter. Example: letter

config_json   object  optional    

Configuración visual. Acepta un objeto parcial; las claves no enviadas usan valores por defecto. Ver estructura en la Guía del grupo.

colors   object  optional    
primary   string  optional    

Color hexadecimal principal. Example: #0a66c2

secondary   string  optional    

Color hexadecimal secundario. Example: #333333

text   string  optional    

Must match the regex /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/. Example: #1a202c

accent   string  optional    

Color hexadecimal de acentos. Example: #0a66c2

muted   string  optional    

Must match the regex /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/. Example: #666666

fonts   object  optional    
family   string  optional    

Familia tipográfica. Valores: Helvetica, Times-Roman, Courier. Example: Helvetica

size_header   integer  optional    

Tamaño del encabezado/título (8-24). Example: 14

size_body   integer  optional    

Tamaño del cuerpo (6-14). Example: 9

size_footer   integer  optional    

Tamaño del pie de página (5-10). Example: 7

layout   object  optional    
show_logo   boolean  optional    

Si se muestra el logo. Example: true

logo_position   string  optional    

Posición del logo. Valores: left, center, right. Example: left

logo_max_height   integer  optional    

Alto máximo del logo en pt (20-100). Example: 60

show_commercial_name   boolean  optional    

Example: true

show_address   boolean  optional    

Example: true

show_phone   boolean  optional    

Example: true

show_email   boolean  optional    

Example: true

show_observations   boolean  optional    

Example: true

show_payment_methods   boolean  optional    

Example: true

show_references   boolean  optional    

Example: true

show_other_charges   boolean  optional    

Example: true

qr   object  optional    
size   integer  optional    

Tamaño del QR en pt (70-150, mínimo 70 ≈ 2.5 cm). Example: 100

position   string  optional    

Posición del QR. Valor recomendado por normativa: bottom-right. Example: bottom-right

enabled   boolean  optional    

Incluir QR (normativa obliga true). Example: true

margins   object  optional    
top   integer  optional    

El campo value debe ser al menos 5. El campo value no puede ser mayor a 50. Example: 15

right   integer  optional    

El campo value debe ser al menos 5. El campo value no puede ser mayor a 50. Example: 15

bottom   integer  optional    

El campo value debe ser al menos 5. El campo value no puede ser mayor a 50. Example: 15

left   integer  optional    

El campo value debe ser al menos 5. El campo value no puede ser mayor a 50. Example: 15

style   string  optional    

Example: classic

Must be one of:
  • classic
  • modern
  • minimal
  • bold
  • split
footer_text   string  optional    

El campo value no puede tener más de 200 caracteres. Example: Documento generado electrónicamente | Almendro Factura Electrónica

Response

Response Fields

data   object     
id   integer     

ID numérico de la plantilla. Úselo en PUT, DELETE, /preview y /logo.

name   string     

Nombre identificable de la plantilla (3-100 chars). Único por contribuyente.

is_default   boolean     

true si es la plantilla usada por defecto al generar PDFs. Solo una plantilla puede ser default por contribuyente.

paper_size   string     

Tamaño del papel: letter (Carta US 8.5×11") o A4 (210×297mm).

config_json   object     

Configuración visual completa de la plantilla con 4 bloques: colors, fonts, layout, qr.

colors   object     

Paleta de colores hexadecimales.

primary   string     

Color principal (encabezados, títulos). Ejemplo: #2d3748.

secondary   string     

Color secundario (texto, bordes). Ejemplo: #4a5568.

accent   string     

Color de acentos (líneas, badges). Ejemplo: #3182ce.

fonts   object     

Configuración tipográfica.

family   string     

Familia tipográfica: Helvetica, Times-Roman o Courier.

size_header   integer     

Tamaño del encabezado/título principal en pt (8-24).

size_footer   integer     

Tamaño del pie de página en pt (5-10).

size_body   integer     

Tamaño del texto general en pt (6-14).

layout   object     

Configuración de layout y logo.

show_logo   boolean     

Si se muestra el logo del contribuyente en el encabezado.

logo_position   string     

Posición del logo: left, center o right.

logo_max_height   integer     

Alto máximo del logo en pt (20-100).

qr   object     

Configuración del código QR (obligatorio por normativa).

enabled   boolean     

Si se incluye el QR. Siempre true por normativa | el sistema ignora false.

size   integer     

Tamaño del QR en pt. Mínimo 70 (≈2.5 cm por normativa). Máximo 150.

position   string     

Posición del QR: bottom-right (exigido por normativa) o bottom-left.

has_logo   boolean     

true si la plantilla tiene un archivo de logo cargado. Use POST /pdf-templates/{id}/logo para subirlo.

is_active   boolean     

true si la plantilla está activa y puede usarse para generar PDFs.

created_at   string     

Fecha de creación de la plantilla (ISO 8601).

updated_at   string     

Fecha de última actualización (ISO 8601).

Editar una plantilla PDF existente

requires authentication

Admite actualización parcial | envíe únicamente los campos que desea modificar. Los campos no enviados conservan su valor actual.

El campo config_json se fusiona con la configuración existente (merge profundo): las claves que envíe se actualizan y las que no envíe se mantienen intactas. Esto permite cambiar, por ejemplo, solo los colores sin afectar fuentes, layout o QR.

Si envía is_default: true y la plantilla no era la default, la default anterior se desmarca automáticamente.

Importante: editar una plantilla no afecta a los PDFs ya generados con ella. Los PDFs se congelan con la configuración vigente al momento de la emisión (inmutabilidad post-emisión).

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/pdf-templates/1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Profesional Actualizada\",
    \"is_default\": true,
    \"is_active\": true,
    \"paper_size\": \"letter\",
    \"config_json\": {
        \"colors\": {
            \"primary\": \"#1a365d\",
            \"secondary\": \"#4a5568\",
            \"text\": \"#2815Df\",
            \"accent\": \"#3182ce\",
            \"muted\": \"#2815Df\"
        },
        \"fonts\": {
            \"family\": \"Helvetica\",
            \"size_header\": 14,
            \"size_body\": 9,
            \"size_footer\": 7
        },
        \"layout\": {
            \"show_logo\": true,
            \"logo_position\": \"left\",
            \"logo_max_height\": 60,
            \"show_commercial_name\": true,
            \"show_address\": true,
            \"show_phone\": true,
            \"show_email\": false,
            \"show_observations\": true,
            \"show_payment_methods\": false,
            \"show_references\": false,
            \"show_other_charges\": false
        },
        \"qr\": {
            \"size\": 100,
            \"position\": \"bottom-right\"
        },
        \"margins\": {
            \"top\": 22,
            \"right\": 24,
            \"bottom\": 18,
            \"left\": 8
        },
        \"style\": \"bold\",
        \"footer_text\": \"m\"
    }
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/pdf-templates/1"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Profesional Actualizada",
    "is_default": true,
    "is_active": true,
    "paper_size": "letter",
    "config_json": {
        "colors": {
            "primary": "#1a365d",
            "secondary": "#4a5568",
            "text": "#2815Df",
            "accent": "#3182ce",
            "muted": "#2815Df"
        },
        "fonts": {
            "family": "Helvetica",
            "size_header": 14,
            "size_body": 9,
            "size_footer": 7
        },
        "layout": {
            "show_logo": true,
            "logo_position": "left",
            "logo_max_height": 60,
            "show_commercial_name": true,
            "show_address": true,
            "show_phone": true,
            "show_email": false,
            "show_observations": true,
            "show_payment_methods": false,
            "show_references": false,
            "show_other_charges": false
        },
        "qr": {
            "size": 100,
            "position": "bottom-right"
        },
        "margins": {
            "top": 22,
            "right": 24,
            "bottom": 18,
            "left": 8
        },
        "style": "bold",
        "footer_text": "m"
    }
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/pdf-templates/1';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Profesional Actualizada',
            'is_default' => true,
            'is_active' => true,
            'paper_size' => 'letter',
            'config_json' => [
                'colors' => [
                    'primary' => '#1a365d',
                    'secondary' => '#4a5568',
                    'text' => '#2815Df',
                    'accent' => '#3182ce',
                    'muted' => '#2815Df',
                ],
                'fonts' => [
                    'family' => 'Helvetica',
                    'size_header' => 14,
                    'size_body' => 9,
                    'size_footer' => 7,
                ],
                'layout' => [
                    'show_logo' => true,
                    'logo_position' => 'left',
                    'logo_max_height' => 60,
                    'show_commercial_name' => true,
                    'show_address' => true,
                    'show_phone' => true,
                    'show_email' => false,
                    'show_observations' => true,
                    'show_payment_methods' => false,
                    'show_references' => false,
                    'show_other_charges' => false,
                ],
                'qr' => [
                    'size' => 100,
                    'position' => 'bottom-right',
                ],
                'margins' => [
                    'top' => 22,
                    'right' => 24,
                    'bottom' => 18,
                    'left' => 8,
                ],
                'style' => 'bold',
                'footer_text' => 'm',
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Plantilla actualizada):


{
    "success": true,
    "data": {
        "id": 1,
        "name": "Profesional Actualizada",
        "is_default": true,
        "paper_size": "letter",
        "config_json": {
            "colors": {
                "primary": "#1a365d",
                "secondary": "#4a5568",
                "accent": "#3182ce"
            },
            "fonts": {
                "family": "Helvetica",
                "size_header": 14,
                "size_body": 9
            },
            "layout": {
                "show_logo": true,
                "logo_position": "left",
                "logo_max_height": 60
            },
            "qr": {
                "enabled": true,
                "size": 100,
                "position": "bottom-right"
            }
        },
        "has_logo": true,
        "is_active": true,
        "created_at": "2026-04-01T10:00:00-06:00",
        "updated_at": "2026-04-16T11:00:00-06:00"
    },
    "message": "Plantilla actualizada correctamente.",
    "errors": null
}
 

Example response (404, Plantilla no encontrada):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Example response (422, Nombre duplicado):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "name": [
            "Ya existe una plantilla con este nombre para el contribuyente."
        ]
    }
}
 

Request      

PUT api/v1/public/pdf-templates/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

ID de la plantilla. Example: 1

Body Parameters

name   string  optional    

Nombre de la plantilla (3-100 caracteres, único por contribuyente). Example: Profesional Actualizada

is_default   boolean  optional    

Si será la plantilla default. Si true, desmarca la anterior. Example: true

is_active   boolean  optional    

Estado activo/inactivo de la plantilla. Example: true

paper_size   string  optional    

Tamaño del papel. Valores: letter, A4. Example: letter

config_json   object  optional    

Cambios parciales a la configuración visual (merge profundo con la actual).

colors   object  optional    
primary   string  optional    

Color hexadecimal principal. Example: #1a365d

secondary   string  optional    

Color hexadecimal secundario. Example: #4a5568

text   string  optional    

Must match the regex /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/. Example: #2815Df

accent   string  optional    

Color hexadecimal de acentos. Example: #3182ce

muted   string  optional    

Must match the regex /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/. Example: #2815Df

fonts   object  optional    
family   string  optional    

Familia tipográfica. Example: Helvetica

size_header   integer  optional    

Tamaño del encabezado/título (8-24). Example: 14

size_body   integer  optional    

Tamaño del cuerpo (6-14). Example: 9

size_footer   integer  optional    

Tamaño del pie de página (5-10). Example: 7

layout   object  optional    
show_logo   boolean  optional    

Si se muestra el logo. Example: true

logo_position   string  optional    

Posición del logo (left, center, right). Example: left

logo_max_height   integer  optional    

Alto máximo del logo en pt (20-100). Example: 60

show_commercial_name   boolean  optional    

Example: true

show_address   boolean  optional    

Example: true

show_phone   boolean  optional    

Example: true

show_email   boolean  optional    

Example: false

show_observations   boolean  optional    

Example: true

show_payment_methods   boolean  optional    

Example: false

show_references   boolean  optional    

Example: false

show_other_charges   boolean  optional    

Example: false

qr   object  optional    
size   integer  optional    

Tamaño del QR en pt (70-150). Example: 100

position   string  optional    

Posición del QR (bottom-right recomendado). Example: bottom-right

margins   object  optional    
top   integer  optional    

El campo value debe ser al menos 5. El campo value no puede ser mayor a 50. Example: 22

right   integer  optional    

El campo value debe ser al menos 5. El campo value no puede ser mayor a 50. Example: 24

bottom   integer  optional    

El campo value debe ser al menos 5. El campo value no puede ser mayor a 50. Example: 18

left   integer  optional    

El campo value debe ser al menos 5. El campo value no puede ser mayor a 50. Example: 8

style   string  optional    

Example: bold

Must be one of:
  • classic
  • modern
  • minimal
  • bold
  • split
footer_text   string  optional    

El campo value no puede tener más de 200 caracteres. Example: m

Eliminar una plantilla PDF

requires authentication

Realiza un soft delete de la plantilla. Los PDFs ya generados con esta plantilla no se ven afectados | la configuración se aplica al momento de generar cada PDF y no se modifica retroactivamente.

Protección: no se puede eliminar una plantilla si es la única plantilla activa del contribuyente. En ese caso el sistema responde HTTP 409 y debe:

  1. Crear otra plantilla nueva, o
  2. Reactivar otra plantilla existente,

antes de eliminar la actual.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/pdf-templates/2" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/pdf-templates/2"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/pdf-templates/2';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Plantilla eliminada):


{
    "success": true,
    "data": null,
    "message": "Plantilla eliminada correctamente.",
    "errors": null
}
 

Example response (404, Plantilla no encontrada):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Example response (409, No se puede eliminar la única plantilla activa):


{
    "success": false,
    "data": null,
    "message": "No se puede eliminar la única plantilla activa del contribuyente. Cree otra plantilla o marque otra como default antes de eliminar.",
    "errors": null
}
 

Request      

DELETE api/v1/public/pdf-templates/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

ID de la plantilla. Example: 2

Generar un preview del PDF con datos reales

requires authentication

Renderiza un PDF de ejemplo usando la plantilla indicada y el comprobante más reciente emitido por el contribuyente. Permite verificar la apariencia de la plantilla antes de asignarla como default.

La respuesta es un archivo PDF (Content-Type: application/pdf) inline. Puede guardarlo localmente con -o archivo.pdf en curl o mostrarlo directamente en un iframe del navegador.

Si el contribuyente aún no ha emitido ningún comprobante, retorna HTTP 404 con un mensaje indicando que debe emitir al menos uno primero.

Nota: el comprobante usado para el preview es el más reciente (ORDER BY issued_at DESC LIMIT 1). Si quiere probar la plantilla con un comprobante específico, emita uno nuevo y use este endpoint después | o bien use el endpoint GET /vouchers/{key}/pdf con ?pdf_template_id={id}.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/pdf-templates/1/preview" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/pdf-templates/1/preview"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/pdf-templates/1/preview';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, PDF generado exitosamente):


{
    "Content-Type": "application/pdf",
    "Content-Disposition": "inline; filename=\"preview_Classic.pdf\"",
    "Cache-Control": "no-store",
    "body": "%PDF-1.4 ... (binario) ... %%EOF"
}
 

Example response (404, Sin comprobantes para preview):


{
    "success": false,
    "data": null,
    "message": "No hay comprobantes emitidos para generar un preview. Emita al menos un comprobante antes de usar preview.",
    "errors": null
}
 

Example response (404, Plantilla no encontrada):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Request      

POST api/v1/public/pdf-templates/{id}/preview

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

ID de la plantilla. Example: 1

requires authentication

Devuelve el archivo de imagen del logo como respuesta binaria inline, con el Content-Type correcto según el formato (PNG, JPG/JPEG, SVG).

Si la plantilla no tiene logo configurado, retorna HTTP 404.

El archivo se sirve con cache privado de 1 hora y cabeceras de seguridad (X-Content-Type-Options: nosniff).

requires authentication

Sube un archivo de imagen que se utilizará como logo en el encabezado del PDF generado con esta plantilla. Si la plantilla ya tenía un logo, se reemplaza automáticamente (el archivo anterior se elimina del servidor).

Este request debe enviarse como multipart/form-data (no JSON), porque incluye un archivo binario.

Restricciones del archivo:

Ejemplo con curl:

curl -X POST https://api.almendro.cr/api/v1/public/pdf-templates/1/logo \
  -H "Authorization: Bearer {su_token}" \
  -F "logo_file=@/ruta/a/logo.png"

Ejemplo con Node.js (axios + form-data):

const FormData = require('form-data')
const fs = require('fs')
const axios = require('axios')

const form = new FormData()
form.append('logo_file', fs.createReadStream('logo.png'))

await axios.post(
  'https://api.almendro.cr/api/v1/public/pdf-templates/1/logo',
  form,
  { headers: { ...form.getHeaders(), Authorization: `Bearer ${token}` } },
)

requires authentication

Elimina el archivo de logo asociado a la plantilla. Los PDFs que ya fueron generados con este logo no se ven afectados.

Si la plantilla no tiene logo configurado, retorna HTTP 409.

Configuración Email

Consultar la configuración de email del contribuyente.

requires authentication

Retorna la configuración de envío automático de comprobantes por correo electrónico. Si el contribuyente nunca ha configurado estos valores, se retornan los valores por defecto que cumplen con la obligación de entrega del art. 18 del Reglamento: envío automático activado, XML firmado y PDF adjuntos.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/email-settings" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/email-settings"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/email-settings';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Valores por defecto (nunca configurado)):


{
    "success": true,
    "data": {
        "auto_send": true,
        "send_on_accepted_only": true,
        "attach_xml": true,
        "attach_pdf": true,
        "reply_to": null,
        "bcc": [],
        "custom_subject": null,
        "custom_message": null
    },
    "message": "Configuración de email del contribuyente.",
    "errors": null
}
 

Example response (200, Configuración personalizada):


{
    "success": true,
    "data": {
        "auto_send": true,
        "send_on_accepted_only": true,
        "attach_xml": true,
        "attach_pdf": true,
        "reply_to": "facturas@miempresa.cr",
        "bcc": [
            "contabilidad@miempresa.cr"
        ],
        "custom_subject": "{emisor} | {tipo} {consecutivo}",
        "custom_message": "Adjunto su comprobante electrónico."
    },
    "message": "Configuración de email del contribuyente.",
    "errors": null
}
 

Request      

GET api/v1/public/email-settings

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
auto_send   boolean     

true si el sistema envía automáticamente el comprobante por correo al receptor al emitir. Default true por obligación del art. 18 del Reglamento.

send_on_accepted_only   boolean     

true si el email se envía solo después de que Hacienda acepte el comprobante (práctica estándar CR). false para enviar inmediatamente tras la emisión.

attach_xml   boolean     

true si se adjunta el XML firmado del comprobante al correo. Art. 18 exige entregar "el comprobante electrónico" (= XML).

attach_pdf   boolean     

true si se adjunta la representación gráfica PDF al correo. Resolución art. 5 exige "representación gráfica".

reply_to   string     

Correo al que llegarán las respuestas del receptor. null si no se configuró (usa el default del sistema).

bcc   string[]     

Lista de correos en copia oculta (máximo 4). Útil para enviar copia al contador o sistema interno. Array vacío si no se configuró.

custom_subject   string     

Asunto personalizado del correo. Soporta placeholders: {tipo}, {consecutivo}, {clave}, {receptor}, {total}, {moneda}, {emisor}. null usa el asunto por defecto del sistema.

custom_message   string     

Mensaje personalizado que aparece antes de los datos del comprobante en el cuerpo del correo. null usa el mensaje por defecto.

Actualizar la configuración de email del contribuyente.

requires authentication

Soporta actualización parcial: envíe únicamente los campos que desea modificar. Los campos omitidos conservan su valor actual (o el valor por defecto si nunca fueron configurados).

Advertencia normativa: si desactiva attach_xml o attach_pdf, el contribuyente asume la responsabilidad de entregar los documentos al receptor por otro medio (descarga desde API, portal web, impresión), conforme al art. 18 del Reglamento de Comprobantes Electrónicos.

Placeholders disponibles para custom_subject: {tipo}, {consecutivo}, {clave}, {receptor}, {total}, {moneda}, {emisor}

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/email-settings" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"auto_send\": true,
    \"send_on_accepted_only\": true,
    \"attach_xml\": true,
    \"attach_pdf\": true,
    \"reply_to\": \"facturas@miempresa.cr\",
    \"bcc\": [
        \"b\"
    ],
    \"custom_subject\": \"{emisor} | {tipo} {consecutivo}\",
    \"custom_message\": \"Adjunto encontrará su comprobante electrónico.\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/email-settings"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "auto_send": true,
    "send_on_accepted_only": true,
    "attach_xml": true,
    "attach_pdf": true,
    "reply_to": "facturas@miempresa.cr",
    "bcc": [
        "b"
    ],
    "custom_subject": "{emisor} | {tipo} {consecutivo}",
    "custom_message": "Adjunto encontrará su comprobante electrónico."
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/email-settings';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'auto_send' => true,
            'send_on_accepted_only' => true,
            'attach_xml' => true,
            'attach_pdf' => true,
            'reply_to' => 'facturas@miempresa.cr',
            'bcc' => [
                'b',
            ],
            'custom_subject' => '{emisor} | {tipo} {consecutivo}',
            'custom_message' => 'Adjunto encontrará su comprobante electrónico.',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Configuración actualizada):


{
    "success": true,
    "data": {
        "auto_send": true,
        "send_on_accepted_only": true,
        "attach_xml": true,
        "attach_pdf": true,
        "reply_to": "facturas@miempresa.cr",
        "bcc": [
            "contabilidad@miempresa.cr"
        ],
        "custom_subject": "{emisor} | {tipo} {consecutivo}",
        "custom_message": "Adjunto su comprobante electrónico."
    },
    "message": "Configuración de email actualizada correctamente.",
    "errors": null
}
 

Example response (422, Error de validación):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "reply_to": [
            "El campo reply_to debe ser un correo electrónico válido."
        ],
        "bcc.0": [
            "El campo bcc.0 debe ser un correo electrónico válido."
        ]
    }
}
 

Request      

PUT api/v1/public/email-settings

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

auto_send   boolean  optional    

Envío automático al aceptar comprobante. Default true (art. 18 Reglamento obliga entrega). Example: true

send_on_accepted_only   boolean  optional    

Enviar solo cuando Hacienda acepta (true) o también al emitir (false). Default true. Example: true

attach_xml   boolean  optional    

Adjuntar XML firmado (XAdES-EPES). Default true (art. 18: "comprobante electrónico"). Example: true

attach_pdf   boolean  optional    

Adjuntar PDF con QR. Default true (art. 18 + Resolución art. 5: "representación gráfica"). Example: true

reply_to   string  optional    

Dirección reply-to personalizada. Las respuestas del receptor llegarán a este correo. El campo value no es un correo electrónico válido. El campo value no puede tener más de 160 caracteres. Example: facturas@miempresa.cr

bcc   string[]  optional    

El campo value no es un correo electrónico válido. El campo value no puede tener más de 160 caracteres.

custom_subject   string  optional    

Asunto personalizado. Placeholders: {tipo}, {consecutivo}, {clave}, {receptor}, {total}, {moneda}, {emisor}. El campo value no puede tener más de 200 caracteres. Example: {emisor} | {tipo} {consecutivo}

custom_message   string  optional    

Mensaje personalizado en el cuerpo del email. Se muestra antes de los datos del comprobante. El campo value no puede tener más de 500 caracteres. Example: Adjunto encontrará su comprobante electrónico.

Clientes

Listar clientes del contribuyente

requires authentication

Devuelve los clientes (receptores reutilizables) registrados por el contribuyente, con soporte para búsqueda por texto, filtro por tipo de identificación y filtro por estado.

Por defecto, el listado incluye solo clientes activos ordenados alfabéticamente por nombre. Para ver los inactivos use is_active=false, y para ver todos use is_active=all.

La búsqueda por texto (q) es case-insensitive y busca en: nombre legal, nombre comercial y número de cédula.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/clients?q=Empresa&id_type=02&is_active=true&per_page=15&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/clients"
);

const params = {
    "q": "Empresa",
    "id_type": "02",
    "is_active": "true",
    "per_page": "15",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/clients';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'q' => 'Empresa',
            'id_type' => '02',
            'is_active' => 'true',
            'per_page' => '15',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado paginado):


{
    "success": true,
    "data": [
        {
            "id": "019d867d-0001-7288-8ece-fd64da756001",
            "id_type": "02",
            "id_type_label": "Cédula Jurídica",
            "id_number": "3101123456",
            "name": "EMPRESA EJEMPLO S.A.",
            "commercial_name": "Ejemplo Shop",
            "location": {
                "province": 1,
                "canton": "01",
                "district": "01"
            },
            "neighborhood": "San Pedro",
            "address": "200 metros norte del parque central",
            "phone": {
                "country_code": 506,
                "number": "22001234"
            },
            "emails": [
                "facturacion@ejemplo.cr"
            ],
            "default_activity_code": "6121.0",
            "notes": "Cliente preferencial",
            "is_active": true,
            "has_location": true,
            "has_email": true,
            "created_at": "2026-04-01T10:00:00-06:00",
            "updated_at": "2026-04-10T14:30:00-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 4,
        "per_page": 15,
        "total": 50,
        "from": 1,
        "to": 15
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": "..."
    }
}
 

Example response (200, Sin resultados):


{
    "success": true,
    "data": [],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 0,
        "from": null,
        "to": null
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Request      

GET api/v1/public/clients

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

q   string  optional    

Búsqueda por nombre, nombre comercial o número de cédula (mínimo 2 caracteres). Example: Empresa

id_type   string  optional    

Filtrar por tipo de identificación. Valores: 01, 02, 03, 04, 05, 06. Example: 02

is_active   string  optional    

Filtrar por estado. true = solo activos (default), false = solo inactivos, all = todos. Example: true

per_page   integer  optional    

Resultados por página (1-100). Default: 15. Example: 15

page   integer  optional    

Número de página. Example: 1

Crear un nuevo cliente

requires authentication

Registra un cliente (receptor reutilizable) en el catálogo del contribuyente. Los datos corresponden a los campos del receptor definidos en la normativa de comprobantes electrónicos.

Reglas de validación:

Los campos de ubicación (province, canton, district) son todos opcionales individualmente, pero si envía uno debería enviar los tres para que la ubicación sea coherente.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/clients" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"id_type\": \"02\",
    \"id_number\": \"3101123456\",
    \"name\": \"EMPRESA EJEMPLO S.A.\",
    \"commercial_name\": \"Ejemplo Shop\",
    \"province\": 1,
    \"canton\": 1,
    \"district\": 1,
    \"neighborhood\": \"San Pedro\",
    \"address\": \"200 metros norte del parque central\",
    \"phone_country_code\": 506,
    \"phone\": \"22001234\",
    \"emails\": [
        \"facturacion@ejemplo.cr\"
    ],
    \"default_activity_code\": \"6121.0\",
    \"notes\": \"Cliente preferencial\",
    \"is_active\": true
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/clients"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "id_type": "02",
    "id_number": "3101123456",
    "name": "EMPRESA EJEMPLO S.A.",
    "commercial_name": "Ejemplo Shop",
    "province": 1,
    "canton": 1,
    "district": 1,
    "neighborhood": "San Pedro",
    "address": "200 metros norte del parque central",
    "phone_country_code": 506,
    "phone": "22001234",
    "emails": [
        "facturacion@ejemplo.cr"
    ],
    "default_activity_code": "6121.0",
    "notes": "Cliente preferencial",
    "is_active": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/clients';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'id_type' => '02',
            'id_number' => '3101123456',
            'name' => 'EMPRESA EJEMPLO S.A.',
            'commercial_name' => 'Ejemplo Shop',
            'province' => 1,
            'canton' => 1,
            'district' => 1,
            'neighborhood' => 'San Pedro',
            'address' => '200 metros norte del parque central',
            'phone_country_code' => 506,
            'phone' => '22001234',
            'emails' => [
                'facturacion@ejemplo.cr',
            ],
            'default_activity_code' => '6121.0',
            'notes' => 'Cliente preferencial',
            'is_active' => true,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Cliente creado):


{
    "success": true,
    "data": {
        "id": "019d867d-0002-7288-8ece-fd64da756002",
        "id_type": "02",
        "id_type_label": "Cédula Jurídica",
        "id_number": "3101123456",
        "name": "EMPRESA EJEMPLO S.A.",
        "commercial_name": "Ejemplo Shop",
        "location": {
            "province": 1,
            "canton": "01",
            "district": "01"
        },
        "neighborhood": "San Pedro",
        "address": "200 metros norte del parque central",
        "phone": {
            "country_code": 506,
            "number": "22001234"
        },
        "emails": [
            "facturacion@ejemplo.cr"
        ],
        "default_activity_code": "6121.0",
        "notes": "Cliente preferencial",
        "is_active": true,
        "has_location": true,
        "has_email": true,
        "created_at": "2026-04-16T10:00:00-06:00",
        "updated_at": "2026-04-16T10:00:00-06:00"
    },
    "message": "Cliente creado correctamente.",
    "errors": null
}
 

Example response (422, Identificación duplicada):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "id_number": [
            "Ya existe un cliente con este tipo y número de identificación."
        ]
    }
}
 

Example response (422, Formato de actividad económica inválido):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "default_activity_code": [
            "Debe tener el formato XXXX.X (ejemplo: 6121.0)."
        ]
    }
}
 

Example response (422, Tipo de identificación inválido):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "id_type": [
            "El tipo seleccionado no es válido."
        ]
    }
}
 

Example response (422, Límite del plan alcanzado):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan": [
            "Ha alcanzado el límite de clientes de su plan. Actualice a un plan superior."
        ]
    }
}
 

Request      

POST api/v1/public/clients

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

id_type   string     

Tipo de identificación. Valores permitidos: 01 (Física), 02 (Jurídica), 03 (DIMEX), 04 (NITE), 05 (Extranjero), 06 (No Contribuyente). Example: 02

id_number   string     

Número de identificación (9-20 dígitos, solo números). Example: 3101123456

name   string     

Nombre legal o razón social (3-100 caracteres). Example: EMPRESA EJEMPLO S.A.

commercial_name   string  optional    

Nombre comercial (3-80 caracteres). Example: Ejemplo Shop

province   integer  optional    

Código de provincia (1-7). Example: 1

canton   integer  optional    

Código de cantón (1-99). Example: 1

district   integer  optional    

Código de distrito (1-99). Example: 1

neighborhood   string  optional    

Barrio (5-50 caracteres). Example: San Pedro

address   string  optional    

Señas exactas (5-300 caracteres). Example: 200 metros norte del parque central

phone_country_code   integer  optional    

Código de país del teléfono. Default: 506. Example: 506

phone   string  optional    

Número de teléfono (4-20 dígitos, solo números). Example: 22001234

emails   string[]  optional    

Correos del cliente (máximo 4).

default_activity_code   string  optional    

Código CIIU por defecto en formato Hacienda XXXX.X. Usado como CodigoActividadReceptor al emitir FEC (tipo 08). Example: 6121.0

notes   string  optional    

Notas internas (máximo 1000 caracteres). No aparecen en el comprobante. Example: Cliente preferencial

is_active   boolean  optional    

Si el cliente está activo (puede usarse al emitir). Default: true. Example: true

Consultar un cliente por UUID

requires authentication

Devuelve el detalle completo del cliente indicado. Solo se retornan clientes que pertenecen a su contribuyente. Los clientes eliminados (soft delete) retornan 404 | no se pueden consultar.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/clients/019d867d-0001-7288-8ece-fd64da756001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/clients/019d867d-0001-7288-8ece-fd64da756001"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/clients/019d867d-0001-7288-8ece-fd64da756001';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Cliente encontrado):


{
    "success": true,
    "data": {
        "id": "019d867d-0001-7288-8ece-fd64da756001",
        "id_type": "02",
        "id_type_label": "Cédula Jurídica",
        "id_number": "3101123456",
        "name": "EMPRESA EJEMPLO S.A.",
        "commercial_name": "Ejemplo Shop",
        "location": {
            "province": 1,
            "canton": "01",
            "district": "01"
        },
        "neighborhood": "San Pedro",
        "address": "200 metros norte del parque central",
        "phone": {
            "country_code": 506,
            "number": "22001234"
        },
        "emails": [
            "facturacion@ejemplo.cr"
        ],
        "default_activity_code": "6121.0",
        "notes": "Cliente preferencial",
        "is_active": true,
        "has_location": true,
        "has_email": true,
        "created_at": "2026-04-01T10:00:00-06:00",
        "updated_at": "2026-04-10T14:30:00-06:00"
    },
    "message": "",
    "errors": null
}
 

Example response (404, No encontrado):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Request      

GET api/v1/public/clients/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente. Example: 019d867d-0001-7288-8ece-fd64da756001

Response

Response Fields

data   object     
id   string     

UUID del cliente. Úselo como client_id en POST /vouchers para resolver automáticamente los datos del receptor.

id_type   string     

Tipo de identificación del receptor: 01=Física, 02=Jurídica, 03=DIMEX, 04=NITE, 05=Extranjero, 06=No Contribuyente.

id_type_label   string     

Nombre legible del tipo de identificación en español.

id_number   string     

Número de identificación (9-20 dígitos). Mapea a IdentificacionType/Numero del XSD.

name   string     

Nombre legal o razón social (3-100 chars). Mapea a ReceptorType/Nombre del XSD.

commercial_name   string     

Nombre comercial del receptor. Mapea a NombreComercial del XSD. Puede ser null.

location   object     

Ubicación del receptor (UbicacionType del XSD). null si no tiene ubicación costarricense configurada.

province   integer     

Código de provincia (1-7). Mapea a Provincia del XSD.

canton   string     

Código de cantón (2 dígitos). Mapea a Canton del XSD.

district   string     

Código de distrito (2 dígitos). Mapea a Distrito del XSD.

neighborhood   string     

Barrio (5-50 chars). Campo opcional dentro de la ubicación.

address   string     

Señas exactas. Mapea a OtrasSenas (max 250) si tiene ubicación CR, o OtrasSenasExtranjero (max 300) si no.

phone   object     

Teléfono del receptor (TelefonoType del XSD). null si no tiene teléfono configurado.

country_code   integer     

Código de país del teléfono (1-3 dígitos). Default: 506 (Costa Rica).

number   string     

Número de teléfono (4-20 dígitos).

emails   string[]     

Correos electrónicos del receptor (máximo 4). El primero mapea a CorreoElectronico del XSD. Los demás se usan como destinatarios adicionales del email transaccional.

default_activity_code   string     

Código CIIU en formato Hacienda XXXX.X. Usado como CodigoActividadReceptor al emitir FEC (tipo 08). null si no aplica.

notes   string     

Notas internas libres del integrador. No aparecen en el comprobante electrónico.

is_active   boolean     

true si el cliente está activo y puede usarse al emitir. false si fue desactivado manualmente.

has_location   boolean     

true si tiene provincia, cantón y distrito configurados. Útil para saber si el receptor tendrá nodo Ubicacion en el XML.

has_email   boolean     

true si tiene al menos un correo. Útil para saber si se enviará email transaccional automático al emitir.

created_at   string     

Fecha de creación del registro (ISO 8601).

updated_at   string     

Fecha de última actualización del registro (ISO 8601).

Editar un cliente existente

requires authentication

Admite actualización parcial | envíe únicamente los campos que desea modificar. Los campos no enviados conservan su valor actual.

Importante: editar un cliente no afecta a los comprobantes ya emitidos con él. Los datos del receptor se copian al comprobante en el momento de la emisión y no cambian retroactivamente (inmutabilidad post-emisión por normativa fiscal).

Si cambia el id_type o id_number, el sistema verifica que la nueva combinación no esté en uso por otro cliente del mismo contribuyente. Si ya existe, responde HTTP 422.

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/clients/019d867d-0001-7288-8ece-fd64da756001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"id_type\": \"02\",
    \"id_number\": \"3101123456\",
    \"name\": \"Nombre Actualizado S.A.\",
    \"commercial_name\": \"Nuevo Nombre Comercial\",
    \"province\": 1,
    \"canton\": 1,
    \"district\": 1,
    \"neighborhood\": \"Escalante\",
    \"address\": \"Frente al parque\",
    \"phone_country_code\": 506,
    \"phone\": \"22009999\",
    \"emails\": [
        \"facturacion@ejemplo.cr\",
        \"contabilidad@ejemplo.cr\"
    ],
    \"default_activity_code\": \"6121.0\",
    \"notes\": \"Cliente preferencial | actualizado\",
    \"is_active\": true
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/clients/019d867d-0001-7288-8ece-fd64da756001"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "id_type": "02",
    "id_number": "3101123456",
    "name": "Nombre Actualizado S.A.",
    "commercial_name": "Nuevo Nombre Comercial",
    "province": 1,
    "canton": 1,
    "district": 1,
    "neighborhood": "Escalante",
    "address": "Frente al parque",
    "phone_country_code": 506,
    "phone": "22009999",
    "emails": [
        "facturacion@ejemplo.cr",
        "contabilidad@ejemplo.cr"
    ],
    "default_activity_code": "6121.0",
    "notes": "Cliente preferencial | actualizado",
    "is_active": true
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/clients/019d867d-0001-7288-8ece-fd64da756001';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'id_type' => '02',
            'id_number' => '3101123456',
            'name' => 'Nombre Actualizado S.A.',
            'commercial_name' => 'Nuevo Nombre Comercial',
            'province' => 1,
            'canton' => 1,
            'district' => 1,
            'neighborhood' => 'Escalante',
            'address' => 'Frente al parque',
            'phone_country_code' => 506,
            'phone' => '22009999',
            'emails' => [
                'facturacion@ejemplo.cr',
                'contabilidad@ejemplo.cr',
            ],
            'default_activity_code' => '6121.0',
            'notes' => 'Cliente preferencial | actualizado',
            'is_active' => true,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Cliente actualizado):


{
    "success": true,
    "data": {
        "id": "019d867d-0001-7288-8ece-fd64da756001",
        "id_type": "02",
        "id_type_label": "Cédula Jurídica",
        "id_number": "3101123456",
        "name": "Nombre Actualizado S.A.",
        "commercial_name": "Nuevo Nombre Comercial",
        "location": {
            "province": 1,
            "canton": "01",
            "district": "01"
        },
        "neighborhood": "San Pedro",
        "address": "200 metros norte del parque central",
        "phone": {
            "country_code": 506,
            "number": "22001234"
        },
        "emails": [
            "facturacion@ejemplo.cr",
            "contabilidad@ejemplo.cr"
        ],
        "default_activity_code": "6121.0",
        "notes": "Cliente preferencial | actualizado",
        "is_active": true,
        "has_location": true,
        "has_email": true,
        "created_at": "2026-04-01T10:00:00-06:00",
        "updated_at": "2026-04-16T11:00:00-06:00"
    },
    "message": "Cliente actualizado correctamente.",
    "errors": null
}
 

Example response (404, Cliente no encontrado):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Example response (422, Identificación duplicada):


{
    "success": false,
    "data": null,
    "message": "Ya existe un cliente con este tipo y número de identificación.",
    "errors": {
        "id_number": [
            "Duplicado para este contribuyente."
        ]
    }
}
 

Example response (422, Formato de actividad económica inválido):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "default_activity_code": [
            "Debe tener el formato XXXX.X (ejemplo: 6121.0)."
        ]
    }
}
 

Request      

PUT api/v1/public/clients/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente. Example: 019d867d-0001-7288-8ece-fd64da756001

Body Parameters

id_type   string  optional    

Tipo de identificación. Example: 02

id_number   string  optional    

Número de identificación (9-20 dígitos). Example: 3101123456

name   string  optional    

Nombre legal o razón social (3-100 chars). Example: Nombre Actualizado S.A.

commercial_name   string  optional    

Nombre comercial (3-80 chars). Example: Nuevo Nombre Comercial

province   integer  optional    

Código de provincia (1-7). Example: 1

canton   integer  optional    

Código de cantón (1-99). Example: 1

district   integer  optional    

Código de distrito (1-99). Example: 1

neighborhood   string  optional    

Barrio (5-50 chars). Example: Escalante

address   string  optional    

Señas (5-300 chars). Example: Frente al parque

phone_country_code   integer  optional    

Código de país del teléfono. Example: 506

phone   string  optional    

Número de teléfono (4-20 dígitos). Example: 22009999

emails   string[]  optional    

Correos (máx 4).

default_activity_code   string  optional    

Código CIIU formato XXXX.X. Example: 6121.0

notes   string  optional    

Notas internas (máx 1000 chars). Example: Cliente preferencial | actualizado

is_active   boolean  optional    

Estado del cliente. Example: true

Eliminar un cliente

requires authentication

Realiza un soft delete del cliente. El registro queda marcado como eliminado pero no se borra físicamente | por eso puede:

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/clients/019d867d-0001-7288-8ece-fd64da756001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/clients/019d867d-0001-7288-8ece-fd64da756001"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/clients/019d867d-0001-7288-8ece-fd64da756001';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Cliente eliminado):


{
    "success": true,
    "data": null,
    "message": "Cliente eliminado correctamente.",
    "errors": null
}
 

Example response (404, No encontrado):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Request      

DELETE api/v1/public/clients/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente. Example: 019d867d-0001-7288-8ece-fd64da756001

Items / Productos

Listar productos y servicios del contribuyente

requires authentication

Devuelve los items (productos/servicios reutilizables) registrados por el contribuyente, con soporte para búsqueda por texto, filtro por código CABYS exacto y filtro por estado.

Por defecto, el listado incluye solo items activos ordenados alfabéticamente por descripción. Para ver los inactivos use is_active=false.

La búsqueda por texto (q) es case-insensitive y busca en: descripción, código interno (SKU) y código CABYS.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/items?q=consultor%C3%ADa&cabys_code=4233201000000&for_contributor_id=0b3f1c2a-0000-4000-8000-000000000000&is_active=true&per_page=15&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/items"
);

const params = {
    "q": "consultoría",
    "cabys_code": "4233201000000",
    "for_contributor_id": "0b3f1c2a-0000-4000-8000-000000000000",
    "is_active": "true",
    "per_page": "15",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/items';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'q' => 'consultoría',
            'cabys_code' => '4233201000000',
            'for_contributor_id' => '0b3f1c2a-0000-4000-8000-000000000000',
            'is_active' => 'true',
            'per_page' => '15',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado paginado):


{
    "success": true,
    "data": [
        {
            "id": "019d867d-0010-7288-8ece-fd64da756010",
            "cabys_code": "4233201000000",
            "internal_code": "SKU-001",
            "description": "Servicio de consultoría profesional",
            "unit_of_measure": "Sp",
            "unit_of_measure_label": "Servicios Profesionales",
            "commercial_unit": null,
            "unit_price": "50000.00000",
            "tax": {
                "code": "01",
                "rate_code": "08",
                "rate": "13.00",
                "label": "IVA 13%"
            },
            "is_mercancia": false,
            "is_servicio": true,
            "is_active": true,
            "created_at": "2026-04-01T10:00:00-06:00",
            "updated_at": "2026-04-10T14:30:00-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 2,
        "per_page": 15,
        "total": 30,
        "from": 1,
        "to": 15
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": "..."
    }
}
 

Example response (200, Sin resultados):


{
    "success": true,
    "data": [],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 0,
        "from": null,
        "to": null
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Request      

GET api/v1/public/items

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

q   string  optional    

Búsqueda por descripción, código interno o código CABYS (mínimo 2 caracteres). Example: consultoría

cabys_code   string  optional    

Filtrar por código CABYS exacto (13 dígitos). Example: 4233201000000

for_contributor_id   string  optional    

Selector al facturar: UUID del contribuyente seleccionado. Devuelve los items de su alcance MÁS el pozo general. Sin este parámetro se listan todos los items del catálogo. Example: 0b3f1c2a-0000-4000-8000-000000000000

is_active   string  optional    

Filtrar por estado. true = solo activos (default), false = solo inactivos. Example: true

per_page   integer  optional    

Resultados por página (1-100). Default: 15. Example: 15

page   integer  optional    

Número de página. Example: 1

Crear un nuevo producto o servicio

requires authentication

Registra un item reutilizable en el catálogo del contribuyente. Los datos corresponden a los campos de una línea de detalle del comprobante.

Reglas de validación:

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/items" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"cabys_code\": \"4233201000000\",
    \"internal_code\": \"SKU-001\",
    \"description\": \"Servicio de consultoría profesional\",
    \"unit_of_measure\": \"Sp\",
    \"commercial_unit\": \"caja x 12\",
    \"unit_price\": 50000,
    \"tax_code\": \"01\",
    \"tax_rate_code\": \"08\",
    \"tax_factor\": 4326.41688,
    \"tax_rate\": 13,
    \"is_active\": true
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/items"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "cabys_code": "4233201000000",
    "internal_code": "SKU-001",
    "description": "Servicio de consultoría profesional",
    "unit_of_measure": "Sp",
    "commercial_unit": "caja x 12",
    "unit_price": 50000,
    "tax_code": "01",
    "tax_rate_code": "08",
    "tax_factor": 4326.41688,
    "tax_rate": 13,
    "is_active": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/items';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'cabys_code' => '4233201000000',
            'internal_code' => 'SKU-001',
            'description' => 'Servicio de consultoría profesional',
            'unit_of_measure' => 'Sp',
            'commercial_unit' => 'caja x 12',
            'unit_price' => 50000.0,
            'tax_code' => '01',
            'tax_rate_code' => '08',
            'tax_factor' => 4326.41688,
            'tax_rate' => 13.0,
            'is_active' => true,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Item creado):


{
    "success": true,
    "data": {
        "id": "019d867d-0011-7288-8ece-fd64da756011",
        "cabys_code": "4233201000000",
        "internal_code": "SKU-001",
        "description": "Servicio de consultoría profesional",
        "unit_of_measure": "Sp",
        "unit_of_measure_label": "Servicios Profesionales",
        "commercial_unit": null,
        "unit_price": "50000.00000",
        "tax": {
            "code": "01",
            "rate_code": "08",
            "rate": "13.00",
            "label": "IVA 13%"
        },
        "is_mercancia": false,
        "is_servicio": true,
        "is_active": true,
        "created_at": "2026-04-16T10:00:00-06:00",
        "updated_at": "2026-04-16T10:00:00-06:00"
    },
    "message": "Item creado correctamente.",
    "errors": null
}
 

Example response (422, Código interno duplicado):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "internal_code": [
            "Ya existe un item con este código interno."
        ]
    }
}
 

Example response (422, Código CABYS no existe en el catálogo):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "cabys_code": [
            "El código CABYS no existe en el catálogo vigente."
        ]
    }
}
 

Example response (422, Falta tax_rate_code para IVA):


{
    "success": false,
    "data": null,
    "message": "tax_rate_code es obligatorio cuando tax_code es IVA (01) o IVA cálculo especial (07).",
    "errors": {
        "tax_rate_code": [
            "tax_rate_code es obligatorio cuando tax_code es IVA (01) o IVA cálculo especial (07)."
        ]
    }
}
 

Example response (422, Unit_of_measure 'Otros' sin commercial_unit):


{
    "success": false,
    "data": null,
    "message": "commercial_unit es obligatoria cuando unit_of_measure es 'Otros'.",
    "errors": {
        "commercial_unit": [
            "commercial_unit es obligatoria cuando unit_of_measure es 'Otros'."
        ]
    }
}
 

Example response (422, Límite del plan alcanzado):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan": [
            "Ha alcanzado el límite de items de su plan. Actualice a un plan superior."
        ]
    }
}
 

Request      

POST api/v1/public/items

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

for_contributor_id   string  optional    

Alcance del item en su catálogo. null = pozo general (visible al facturar por cualquier cliente gestionado). UUID de un contribuyente que gestiona (o su propia cuenta) = visible solo al facturar por ese contribuyente. No comparte el item con el contribuyente; solo filtra su selector. El campo value debe ser un UUID válido.

cabys_code   string     

Código CABYS (13 dígitos exactos). Debe existir en el catálogo vigente. Example: 4233201000000

internal_code   string  optional    

Código interno / SKU del producto (máximo 20 caracteres). Único por contribuyente. Example: SKU-001

description   string     

Descripción del producto o servicio (3-200 caracteres). Example: Servicio de consultoría profesional

unit_of_measure   string     

Unidad de medida. Valores frecuentes: Sp (Servicios Profesionales), Unid (Unidades), kg, l, m, , h, Al, Otros. Si usa Otros, debe enviar commercial_unit. Example: Sp

commercial_unit   string  optional    

Unidad de medida comercial, obligatoria solo cuando unit_of_measure es Otros (máximo 20 caracteres). Example: caja x 12

unit_price   number     

Precio unitario (mayor a 0, hasta 5 decimales). Example: 50000

tax_code   string  optional    

Código del impuesto. Valores: 01 (IVA), 02 (ISC), 03 (Único combustibles), 04 (Específico bebidas alcohólicas), 05 (Específico bebidas envasadas), 06 (Específico tabaco), 07 (IVA cálculo especial), 08 (IVA bienes usados), 12 (Específico cemento), 99 (Otros). Example: 01

tax_rate_code   string  optional    

Código de tarifa de IVA (obligatorio si tax_code es 01 o 07). Valores: 01 (0% exento), 02 (1%), 03 (2%), 04 (4%), 05 (0.5%), 06 (4%), 07 (8%), 08 (13%), 09 (14%), 10 (0% exenta), 11 (0% no sujeta). Example: 08

tax_factor   number  optional    

Example: 4326.41688

tax_rate   number  optional    

Tarifa efectiva en porcentaje (0-100, hasta 2 decimales). Example: 13

is_active   boolean  optional    

Si el item está activo. Default: true. Example: true

Consultar un item por UUID

requires authentication

Devuelve el detalle completo del item indicado. Solo se retornan items que pertenecen a su contribuyente. Los items eliminados (soft delete) retornan 404 | no se pueden consultar.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/items/019d867d-0010-7288-8ece-fd64da756010" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/items/019d867d-0010-7288-8ece-fd64da756010"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/items/019d867d-0010-7288-8ece-fd64da756010';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Item encontrado):


{
    "success": true,
    "data": {
        "id": "019d867d-0010-7288-8ece-fd64da756010",
        "cabys_code": "4233201000000",
        "internal_code": "SKU-001",
        "description": "Servicio de consultoría profesional",
        "unit_of_measure": "Sp",
        "unit_of_measure_label": "Servicios Profesionales",
        "commercial_unit": null,
        "unit_price": "50000.00000",
        "tax": {
            "code": "01",
            "rate_code": "08",
            "rate": "13.00",
            "label": "IVA 13%"
        },
        "is_mercancia": false,
        "is_servicio": true,
        "is_active": true,
        "created_at": "2026-04-01T10:00:00-06:00",
        "updated_at": "2026-04-10T14:30:00-06:00"
    },
    "message": "",
    "errors": null
}
 

Example response (404, No encontrado):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Request      

GET api/v1/public/items/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del item. Example: 019d867d-0010-7288-8ece-fd64da756010

Response

Response Fields

data   object     
id   string     

UUID del item. Úselo como item_id en line_items[] de POST /vouchers para resolver automáticamente los datos de la línea.

cabys_code   string     

Código CABYS de 13 dígitos exactos. Mapea a CodigoCABYS del XSD en cada LineaDetalle.

internal_code   string     

Código interno / SKU del producto (máx 20 chars). Si está presente, se usa como CodigoComercial tipo 04 en el XML. null si no se configuró.

description   string     

Descripción del producto o servicio (3-200 chars). Mapea al campo Detalle de la LineaDetalle del XSD.

unit_of_measure   string     

Código de unidad de medida de Hacienda: Sp, Unid, kg, l, m, , h, Al, Otros, etc. Mapea a UnidadMedida del XSD.

unit_of_measure_label   string     

Nombre legible de la unidad de medida en español.

commercial_unit   string     

Unidad de medida comercial personalizada. Solo presente cuando unit_of_measure es Otros (ej. "caja x 12", "paquete"). null en caso contrario.

unit_price   string     

Precio unitario con 5 decimales (Decimal 18,5). Mapea a PrecioUnitario del XSD.

tax   object     

Configuración simplificada de impuesto del item. null si el item no tiene impuesto configurado (exento sin código).

code   string     

Código del impuesto: 01=IVA, 02=ISC, 07=IVA cálculo especial, etc. Mapea a CodigoImpuesto del XSD.

rate_code   string     

Código de tarifa IVA: 01=0%, 02=1%, 03=2%, 04=4%, 08=13%, 10=Exenta. null si code no es 01 ni 07. Mapea a CodigoTarifaIVA del XSD.

rate   string     

Tarifa efectiva en porcentaje con 2 decimales (ej. 13.00). Mapea a Tarifa del XSD.

label   string     

Etiqueta legible del impuesto (ej. "IVA 13%", "IVA Exento", "ISC").

is_mercancia   boolean     

true si el código CABYS corresponde a mercancía. Afecta los totales merc_gravadas/merc_exentas del ResumenFactura.

is_servicio   boolean     

true si el código CABYS corresponde a servicio. Afecta los totales serv_gravados/serv_exentos del ResumenFactura.

is_active   boolean     

true si el item está activo y puede usarse al emitir. false si fue desactivado manualmente.

created_at   string     

Fecha de creación del registro (ISO 8601).

updated_at   string     

Fecha de última actualización del registro (ISO 8601).

Editar un item existente

requires authentication

Admite actualización parcial | envíe únicamente los campos que desea modificar. Los campos no enviados conservan su valor actual.

Importante: editar un item no afecta a los comprobantes ya emitidos con él. Los datos de la línea se copian al comprobante en el momento de la emisión y no cambian retroactivamente (inmutabilidad post-emisión por normativa fiscal).

Validaciones cruzadas que se aplican al editar:

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/items/019d867d-0010-7288-8ece-fd64da756010" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"cabys_code\": \"4233201000000\",
    \"internal_code\": \"SKU-001-V2\",
    \"description\": \"Servicio de consultoría profesional actualizado\",
    \"unit_of_measure\": \"Sp\",
    \"commercial_unit\": \"caja x 12\",
    \"unit_price\": 55000,
    \"tax_code\": \"01\",
    \"tax_rate_code\": \"08\",
    \"tax_rate\": 13,
    \"is_active\": true
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/items/019d867d-0010-7288-8ece-fd64da756010"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "cabys_code": "4233201000000",
    "internal_code": "SKU-001-V2",
    "description": "Servicio de consultoría profesional actualizado",
    "unit_of_measure": "Sp",
    "commercial_unit": "caja x 12",
    "unit_price": 55000,
    "tax_code": "01",
    "tax_rate_code": "08",
    "tax_rate": 13,
    "is_active": true
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/items/019d867d-0010-7288-8ece-fd64da756010';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'cabys_code' => '4233201000000',
            'internal_code' => 'SKU-001-V2',
            'description' => 'Servicio de consultoría profesional actualizado',
            'unit_of_measure' => 'Sp',
            'commercial_unit' => 'caja x 12',
            'unit_price' => 55000.0,
            'tax_code' => '01',
            'tax_rate_code' => '08',
            'tax_rate' => 13.0,
            'is_active' => true,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Item actualizado):


{
    "success": true,
    "data": {
        "id": "019d867d-0010-7288-8ece-fd64da756010",
        "cabys_code": "4233201000000",
        "internal_code": "SKU-001-V2",
        "description": "Servicio de consultoría profesional actualizado",
        "unit_of_measure": "Sp",
        "unit_of_measure_label": "Servicios Profesionales",
        "commercial_unit": null,
        "unit_price": "55000.00000",
        "tax": {
            "code": "01",
            "rate_code": "08",
            "rate": "13.00",
            "label": "IVA 13%"
        },
        "is_mercancia": false,
        "is_servicio": true,
        "is_active": true,
        "created_at": "2026-04-01T10:00:00-06:00",
        "updated_at": "2026-04-16T11:00:00-06:00"
    },
    "message": "Item actualizado correctamente.",
    "errors": null
}
 

Example response (404, Item no encontrado):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Example response (422, Código interno duplicado):


{
    "success": false,
    "data": null,
    "message": "Ya existe un item con el código interno [SKU-001-V2].",
    "errors": {
        "internal_code": [
            "Duplicado para este contribuyente."
        ]
    }
}
 

Example response (422, Código CABYS no existe):


{
    "success": false,
    "data": null,
    "message": "El código CABYS [9999999999999] no existe en el catálogo vigente.",
    "errors": {
        "cabys_code": [
            "Código CABYS no encontrado."
        ]
    }
}
 

Example response (422, Falta tax_rate_code para IVA):


{
    "success": false,
    "data": null,
    "message": "tax_rate_code es obligatorio cuando tax_code es IVA (01) o IVA cálculo especial (07).",
    "errors": {
        "tax_rate_code": [
            "tax_rate_code es obligatorio cuando tax_code es IVA (01) o IVA cálculo especial (07)."
        ]
    }
}
 

Example response (422, Falta tax_rate cuando hay tax_code):


{
    "success": false,
    "data": null,
    "message": "tax_rate es obligatorio cuando se especifica tax_code.",
    "errors": {
        "tax_rate": [
            "Envíe tax_rate junto con tax_code."
        ]
    }
}
 

Example response (422, unit_of_measure 'Otros' sin commercial_unit):


{
    "success": false,
    "data": null,
    "message": "commercial_unit es obligatoria cuando unit_of_measure es 'Otros'.",
    "errors": {
        "commercial_unit": [
            "commercial_unit es obligatoria cuando unit_of_measure es 'Otros'."
        ]
    }
}
 

Request      

PUT api/v1/public/items/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del item. Example: 019d867d-0010-7288-8ece-fd64da756010

Body Parameters

cabys_code   string  optional    

Código CABYS (13 dígitos). Debe existir en el catálogo vigente. Example: 4233201000000

internal_code   string  optional    

Código interno / SKU (máx 20 chars). Único por contribuyente. Example: SKU-001-V2

description   string  optional    

Descripción (3-200 chars). Example: Servicio de consultoría profesional actualizado

unit_of_measure   string  optional    

Unidad de medida (ver lista en Guía). Example: Sp

commercial_unit   string  optional    

Unidad comercial (obligatoria si unit_of_measure es Otros). Example: caja x 12

unit_price   number  optional    

Precio unitario (> 0, hasta 5 decimales). Example: 55000

tax_code   string  optional    

Código de impuesto (ver lista en Guía). Example: 01

tax_rate_code   string  optional    

Código de tarifa IVA (obligatorio si tax_code es 01 o 07). Example: 08

tax_rate   number  optional    

Tarifa efectiva en % (0-100, 2 decimales). Example: 13

is_active   boolean  optional    

Estado del item. Example: true

Eliminar un item

requires authentication

Realiza un soft delete del item. El registro queda marcado como eliminado pero no se borra físicamente | por eso puede:

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/items/019d867d-0010-7288-8ece-fd64da756010" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/items/019d867d-0010-7288-8ece-fd64da756010"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/items/019d867d-0010-7288-8ece-fd64da756010';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Item eliminado):


{
    "success": true,
    "data": null,
    "message": "Item eliminado correctamente.",
    "errors": null
}
 

Example response (404, No encontrado):


{
    "success": false,
    "data": null,
    "message": "Recurso no encontrado.",
    "errors": null
}
 

Request      

DELETE api/v1/public/items/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del item. Example: 019d867d-0010-7288-8ece-fd64da756010

Secuencias

Listar secuencias del contribuyente

requires authentication

Devuelve todas las secuencias de consecutivos (sucursal + terminal + tipo de comprobante) registradas por el contribuyente autenticado, ordenadas por sucursal, terminal y tipo.

Cada secuencia representa un contador independiente del consecutivo de 20 dígitos definido en la normativa (pág. 65 Anexos): los 3 dígitos de sucursal (local_code), 5 de terminal (terminal_code), 2 del tipo (voucher_type) y 10 del consecutivo correlativo.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/sequences" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/sequences"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/sequences';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado de secuencias):


{
    "success": true,
    "data": [
        {
            "id": "019d867d-0001-7288-8ece-fd64da756001",
            "local_code": "001",
            "terminal_code": "00001",
            "voucher_type": "01",
            "voucher_type_label": "Factura Electrónica",
            "voucher_type_abbr": "FE",
            "current_sequence": 42,
            "next_sequence": 43,
            "last_consecutive": "00100001010000000042",
            "next_consecutive": "00100001010000000043",
            "last_issued_at": "2026-04-10T14:30:00-06:00",
            "last_issued_key": "506041026...00000042",
            "is_active": true,
            "is_near_rollover": false,
            "display_key": "001-00001-01"
        }
    ],
    "message": "Secuencias del contribuyente.",
    "errors": null
}
 

Request      

GET api/v1/public/sequences

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
id   string     

UUID de la secuencia. Úselo como {id} en PUT /sequences/{id}.

local_code   string     

Código de sucursal (3 dígitos). 001 = casa matriz.

terminal_code   string     

Código de terminal (5 dígitos). 00001 = terminal directa.

voucher_type   string     

Código del tipo de comprobante (01-10).

voucher_type_label   string     

Nombre legible del tipo en español.

voucher_type_abbr   string     

Abreviatura del tipo (FE, ND, NC, TE, FEC, FEE, REP).

current_sequence   integer     

Último consecutivo emitido (0 = ninguno).

next_sequence   integer     

Próximo consecutivo que se usará (current_sequence + 1).

last_consecutive   string     

Consecutivo de 20 dígitos del último comprobante emitido. null si ninguno.

next_consecutive   string     

Consecutivo de 20 dígitos que tendrá el próximo comprobante (preview).

last_issued_at   string     

Fecha del último comprobante emitido (ISO 8601). null si ninguno.

last_issued_key   string     

Clave de 50 dígitos del último comprobante emitido (auditoría). null si ninguno.

is_active   boolean     

true si la terminal está habilitada para este tipo.

is_near_rollover   boolean     

true si la secuencia está cerca del límite 9,999,999,999.

display_key   string     

Representación legible sucursal-terminal-tipo (ej. 001-00001-01). No es el formato XML.

Crear una secuencia (sucursal/terminal)

requires authentication

Registra una nueva secuencia de consecutivos para una combinación de sucursal + terminal + tipo de comprobante del contribuyente autenticado. Útil para dar de alta una nueva caja, sucursal o un tipo de comprobante que aún no tiene secuencia.

Reglas de validación:

La secuencia se crea con is_active=true.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/sequences" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"local_code\": \"001\",
    \"terminal_code\": \"00001\",
    \"voucher_type\": \"01\",
    \"current_sequence\": 9992
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/sequences"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "local_code": "001",
    "terminal_code": "00001",
    "voucher_type": "01",
    "current_sequence": 9992
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/sequences';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'local_code' => '001',
            'terminal_code' => '00001',
            'voucher_type' => '01',
            'current_sequence' => 9992,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Secuencia creada):


{
    "success": true,
    "data": {
        "id": "019d867d-0002-7288-8ece-fd64da756002",
        "local_code": "001",
        "terminal_code": "00001",
        "voucher_type": "01",
        "voucher_type_label": "Factura Electrónica",
        "voucher_type_abbr": "FE",
        "current_sequence": 9992,
        "next_sequence": 9993,
        "last_consecutive": "00100001010000009992",
        "next_consecutive": "00100001010000009993",
        "last_issued_at": null,
        "last_issued_key": null,
        "is_active": true,
        "is_near_rollover": false,
        "display_key": "001-00001-01"
    },
    "message": "Secuencia creada.",
    "errors": null
}
 

Example response (422, Secuencia duplicada):


{
    "success": false,
    "data": null,
    "message": "Ya existe una secuencia para esa sucursal, terminal y tipo.",
    "errors": {
        "voucher_type": [
            "Secuencia duplicada."
        ]
    }
}
 

Example response (422, Límite del plan alcanzado):


{
    "success": false,
    "data": null,
    "message": "Ha alcanzado el límite de terminales de su plan.",
    "errors": {
        "terminal_code": [
            "Ha alcanzado el límite de terminales de su plan."
        ]
    }
}
 

Request      

POST api/v1/public/sequences

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

local_code   string     

Código de sucursal (3 dígitos). No puede ser 000. Example: 001

terminal_code   string     

Código de terminal (5 dígitos). No puede ser 00000. Example: 00001

voucher_type   string     

Tipo de comprobante (pos 09-10 del consecutivo). Valores: 01 (FE), 02 (ND), 03 (NC), 04 (TE), 08 (FEC), 09 (FEE), 10 (REP). Example: 01

current_sequence   integer     

Último consecutivo emitido. El próximo comprobante usará este valor + 1. Rango: 0 a 9,999,999,999. Example: 9992

Ajustar una secuencia existente

requires authentication

Admite actualización parcial del current_sequence y/o del estado is_active de una secuencia del contribuyente autenticado. Solo se pueden ajustar secuencias propias; una secuencia inexistente o de otro contribuyente responde HTTP 404.

Importante: bajar current_sequence por debajo del último consecutivo ya emitido se rechaza con 422: el próximo comprobante repetiría un número existente y Hacienda lo rechazaría. Sí se permite ajustarlo por encima de ese piso, que es el escenario legítimo de migración desde otro facturador.

Cambiar el consecutivo exige confirmación de contraseña y queda registrado en la bitácora de auditoría con el valor anterior, el nuevo, el motivo, el usuario y la IP (Resolución MH-DGT-RES-0027-2024, art. 6).

El motivo (reason) es obligatorio en toda actualización.

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/sequences/019d867d-0001-7288-8ece-fd64da756001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"current_sequence\": 9992,
    \"is_active\": true,
    \"reason\": \"Migración desde Alegra, último consecutivo tipo 01 fue 9992.\",
    \"password\": \"MiC0ntr4señ4\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/sequences/019d867d-0001-7288-8ece-fd64da756001"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "current_sequence": 9992,
    "is_active": true,
    "reason": "Migración desde Alegra, último consecutivo tipo 01 fue 9992.",
    "password": "MiC0ntr4señ4"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/sequences/019d867d-0001-7288-8ece-fd64da756001';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'current_sequence' => 9992,
            'is_active' => true,
            'reason' => 'Migración desde Alegra, último consecutivo tipo 01 fue 9992.',
            'password' => 'MiC0ntr4señ4',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Secuencia actualizada):


{
    "success": true,
    "data": {
        "id": "019d867d-0001-7288-8ece-fd64da756001",
        "local_code": "001",
        "terminal_code": "00001",
        "voucher_type": "01",
        "voucher_type_label": "Factura Electrónica",
        "voucher_type_abbr": "FE",
        "current_sequence": 9992,
        "next_sequence": 9993,
        "last_consecutive": "00100001010000009992",
        "next_consecutive": "00100001010000009993",
        "last_issued_at": "2026-04-10T14:30:00-06:00",
        "last_issued_key": "506041026...00009992",
        "is_active": true,
        "is_near_rollover": false,
        "display_key": "001-00001-01"
    },
    "message": "Secuencia actualizada.",
    "errors": null
}
 

Example response (404, Secuencia no encontrada):


{
    "success": false,
    "data": null,
    "message": "Secuencia no encontrada.",
    "errors": null
}
 

Request      

PUT api/v1/public/sequences/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID de la secuencia. Example: 019d867d-0001-7288-8ece-fd64da756001

Body Parameters

current_sequence   integer  optional    

Último consecutivo emitido. El próximo comprobante usará este valor + 1. No puede ser menor al último realmente emitido en esta secuencia. Rango: 0 a 9,999,999,999. Example: 9992

is_active   boolean  optional    

Si la terminal está habilitada para este tipo de comprobante. Example: true

reason   string     

Motivo del ajuste (5-500 caracteres). Se registra en la bitácora de auditoría (Resolución art. 6). Example: Migración desde Alegra, último consecutivo tipo 01 fue 9992.

password   string  optional    

Contraseña del usuario autenticado. Obligatoria cuando se cambia current_sequence. Example: MiC0ntr4señ4

Sucursales y cajas

Listar sucursales y cajas propias

requires authentication

Devuelve las sucursales (branches) directas del contribuyente autenticado, cada una con sus cajas (terminals). La sucursal directa usa slot 00 en el terminal_code (= 00 + caja de 3 díg.); la primera caja es 00001.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/branches" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/branches"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/branches';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Sucursales del contribuyente):


{
    "success": true,
    "data": [
        {
            "id": "019ee0-...",
            "local_code": "001",
            "name": "Casa Matriz",
            "is_active": true,
            "is_direct": true,
            "terminals": [
                {
                    "id": "019ee1-...",
                    "terminal_code": "00001",
                    "slot": "00",
                    "box": "001",
                    "name": "Caja 1",
                    "is_active": true
                }
            ],
            "created_at": "2026-06-19T15:00:00-06:00"
        }
    ],
    "message": "Sucursales del contribuyente.",
    "errors": null
}
 

Request      

GET api/v1/public/branches

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
id   string     

UUID de la sucursal.

local_code   string     

Código de sucursal Hacienda (001-999).

name   string     

Nombre de la sucursal.

is_direct   boolean     

true = sucursal propia/directa; false = creada por un integrador.

terminals   object[]     

Cajas de la sucursal.

terminal_code   string     

Terminal de 5 díg. (posición 04-08 del NumeroConsecutivo).

slot   string     

Slot de 2 díg. (00 = directo).

box   string     

Caja de 3 díg. dentro del slot.

Crear una sucursal

requires authentication

Crea una sucursal directa del contribuyente y, automáticamente, su primera caja (toda sucursal nace con ≥1 caja). Consume cupo del POZO del plan (max_branches). La primera caja directa es 00001.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/branches" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"local_code\": \"002\",
    \"name\": \"Sucursal Heredia\",
    \"box_name\": \"Caja Principal\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/branches"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "local_code": "002",
    "name": "Sucursal Heredia",
    "box_name": "Caja Principal"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/branches';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'local_code' => '002',
            'name' => 'Sucursal Heredia',
            'box_name' => 'Caja Principal',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Sucursal creada (con su primera caja)):


{
    "success": true,
    "data": {
        "id": "019ee0-...",
        "local_code": "002",
        "name": "Sucursal Heredia",
        "is_direct": true,
        "terminals": [
            {
                "terminal_code": "00002",
                "name": "Caja Principal",
                "is_active": true
            }
        ]
    },
    "message": "Sucursal creada.",
    "errors": null
}
 

Example response (422, Sucursal duplicada):


{
    "success": false,
    "data": null,
    "message": "Esa sucursal ya existe.",
    "errors": {
        "local_code": [
            "Ya tenés una sucursal con ese código."
        ]
    }
}
 

Example response (422, Pozo del plan agotado):


{
    "success": false,
    "data": null,
    "message": "Alcanzó el pozo de 2 sucursal(es) de su plan. Actualice su plan para agregar más.",
    "errors": {
        "local_code": [
            "Alcanzó el pozo de 2 sucursal(es) de su plan. Actualice su plan para agregar más."
        ]
    }
}
 

Request      

POST api/v1/public/branches

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

local_code   string     

Código de sucursal Hacienda, 3 díg., distinto de 000. Example: 002

name   string     

Nombre de la sucursal (2-100 chars). Example: Sucursal Heredia

box_name   string  optional    

Nombre de la primera caja (2-100 chars). Default: "Caja 1". Example: Caja Principal

Actualizar una sucursal (nombre y/o código)

requires authentication

Cambia el nombre de la sucursal y/o su local_code (posiciones 01-03 del NumeroConsecutivo). Cambiar el código exige confirmar la contraseña y migra las secuencias existentes al código nuevo (nunca las reinicia), de modo que la numeración continúa donde iba. Pensado para cuentas que migran desde otro sistema de facturación y necesitan conservar su numeración de sucursal (p. ej. casa matriz 002 en vez de 001).

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Casa Matriz San José\",
    \"local_code\": \"002\",
    \"reason\": \"Numeración heredada del sistema anterior\",
    \"password\": \"MiC0ntr4señ4\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Casa Matriz San José",
    "local_code": "002",
    "reason": "Numeración heredada del sistema anterior",
    "password": "MiC0ntr4señ4"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Casa Matriz San José',
            'local_code' => '002',
            'reason' => 'Numeración heredada del sistema anterior',
            'password' => 'MiC0ntr4señ4',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Sucursal actualizada):


{
    "success": true,
    "data": {
        "id": "019ee0-...",
        "local_code": "002",
        "name": "Casa Matriz San José"
    },
    "message": "Sucursal actualizada.",
    "errors": null
}
 

Example response (404, Sucursal inexistente):


{
    "success": false,
    "data": null,
    "message": "La sucursal no existe.",
    "errors": null
}
 

Example response (422, Código duplicado):


{
    "success": false,
    "data": null,
    "message": "Ya tenés una sucursal con ese código.",
    "errors": {
        "local_code": [
            "Ya tenés una sucursal con ese código."
        ]
    }
}
 

Request      

PUT api/v1/public/branches/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID de la sucursal. Example: 019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee

Body Parameters

name   string  optional    

Nuevo nombre (2-100 chars). Opcional. Example: Casa Matriz San José

local_code   string  optional    

Nuevo código de sucursal (3 dígitos, 001-999). Opcional; al cambiarlo se migran las secuencias. Example: 002

reason   string  optional    

Motivo del cambio de código (bitácora). Opcional, máx 500 chars. Example: Numeración heredada del sistema anterior

password   string  optional    

Contraseña del usuario autenticado. Obligatoria cuando cambia local_code. Example: MiC0ntr4señ4

Agregar una caja a una sucursal

requires authentication

Crea una caja (terminal) en la sucursal directa indicada. El terminal_code se asigna automáticamente: slot 00 + la siguiente caja libre (los códigos borrados no se reutilizan). Respeta el tope de cajas por sucursal del plan.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Caja 2\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Caja 2"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Caja 2',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Caja creada):


{
    "success": true,
    "data": {
        "id": "019ee0-...",
        "local_code": "001",
        "terminals": [
            {
                "terminal_code": "00001",
                "name": "Caja 1"
            },
            {
                "terminal_code": "00002",
                "name": "Caja 2"
            }
        ]
    },
    "message": "Caja creada.",
    "errors": null
}
 

Example response (404, Sucursal inexistente):


{
    "success": false,
    "data": null,
    "message": "La sucursal no existe.",
    "errors": null
}
 

Example response (422, Tope de cajas alcanzado):


{
    "success": false,
    "data": null,
    "message": "Alcanzó el tope de 3 caja(s) por sucursal de su plan.",
    "errors": {
        "name": [
            "Actualice su plan para agregar más cajas."
        ]
    }
}
 

Request      

POST api/v1/public/branches/{id}/terminals

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID de la sucursal. Example: 019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee

Body Parameters

name   string     

Nombre de la caja (2-100 chars). Example: Caja 2

Actualizar una caja (nombre y/o código)

requires authentication

Cambia el nombre de la caja y/o su terminal_code (posiciones 04-08 del NumeroConsecutivo). Cambiar el código exige confirmar la contraseña y migra las secuencias existentes al código nuevo (nunca las reinicia). Pensado para cuentas que migran desde otro sistema y necesitan conservar su numeración de caja (p. ej. 00003 en vez de 00001). No se permite un código ya reservado por la terminal de un integrador que gestione (o haya gestionado) esta cuenta.

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"POS Entrada\",
    \"terminal_code\": \"00003\",
    \"delegated_issuance\": true,
    \"reason\": \"Numeración heredada del sistema anterior\",
    \"password\": \"MiC0ntr4señ4\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "POS Entrada",
    "terminal_code": "00003",
    "delegated_issuance": true,
    "reason": "Numeración heredada del sistema anterior",
    "password": "MiC0ntr4señ4"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'POS Entrada',
            'terminal_code' => '00003',
            'delegated_issuance' => true,
            'reason' => 'Numeración heredada del sistema anterior',
            'password' => 'MiC0ntr4señ4',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Caja actualizada):


{
    "success": true,
    "data": {
        "id": "019ee0-...",
        "terminals": [
            {
                "terminal_code": "00003",
                "name": "POS Entrada"
            }
        ]
    },
    "message": "Caja actualizada.",
    "errors": null
}
 

Example response (404, Caja inexistente):


{
    "success": false,
    "data": null,
    "message": "La caja no existe.",
    "errors": null
}
 

Example response (422, Código en uso):


{
    "success": false,
    "data": null,
    "message": "Ya tenés una caja con ese código.",
    "errors": {
        "terminal_code": [
            "Ya tenés una caja con ese código."
        ]
    }
}
 

Request      

PUT api/v1/public/branches/{id}/terminals/{tid}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID de la sucursal. Example: 019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee

tid   string     

UUID de la caja. Example: 019ee1bb-cccc-dddd-eeee-ffffffffffff

Body Parameters

name   string  optional    

Nuevo nombre (2-100 chars). Opcional. Example: POS Entrada

terminal_code   string  optional    

Nuevo código de caja (5 dígitos, ≠00000). Opcional; al cambiarlo se migran las secuencias. Example: 00003

delegated_issuance   boolean  optional    

Modo terminal delegada (aditivo POS): la caja genera su propia Clave de 50 dígitos y POST /vouchers la acepta. Cambiarlo exige contraseña. Example: true

reason   string  optional    

Motivo del cambio (bitácora). Opcional, máx 500 chars. Example: Numeración heredada del sistema anterior

password   string  optional    

Contraseña del usuario autenticado. Obligatoria cuando cambia terminal_code o delegated_issuance. Example: MiC0ntr4señ4

Eliminar una caja

requires authentication

Borra (soft delete) una caja de la sucursal. No se puede eliminar la última caja activa de la sucursal (toda sucursal debe conservar ≥1 caja). El código de la caja borrada no se reutiliza.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Caja eliminada):


{
    "success": true,
    "data": null,
    "message": "Caja eliminada.",
    "errors": null
}
 

Example response (404, Caja inexistente):


{
    "success": false,
    "data": null,
    "message": "La caja no existe.",
    "errors": null
}
 

Example response (409, Es la última caja de la sucursal):


{
    "success": false,
    "data": null,
    "message": "Debés mantener al menos una caja en la sucursal.",
    "errors": null
}
 

Request      

DELETE api/v1/public/branches/{id}/terminals/{tid}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID de la sucursal. Example: 019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee

tid   string     

UUID de la caja. Example: 019ee1bb-cccc-dddd-eeee-ffffffffffff

Eliminar una sucursal

requires authentication

Borra (soft delete) la sucursal junto con sus cajas. No se puede eliminar la última sucursal del contribuyente (siempre debe quedar ≥1).

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Sucursal eliminada):


{
    "success": true,
    "data": null,
    "message": "Sucursal eliminada.",
    "errors": null
}
 

Example response (404, Sucursal inexistente):


{
    "success": false,
    "data": null,
    "message": "La sucursal no existe.",
    "errors": null
}
 

Example response (409, Es la última sucursal):


{
    "success": false,
    "data": null,
    "message": "Debés mantener al menos una sucursal.",
    "errors": null
}
 

Request      

DELETE api/v1/public/branches/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID de la sucursal. Example: 019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee

Webhooks

Listar webhook endpoints registrados

requires authentication

Devuelve todos los endpoints de su cuenta (activos e inactivos), ordenados del más reciente al más antiguo. Cada endpoint incluye su estado de salud actual y los timestamps del último intento de entrega.

El campo health_status puede tomar los valores:

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/webhooks?is_active=1&per_page=15&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/webhooks"
);

const params = {
    "is_active": "1",
    "per_page": "15",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/webhooks';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'is_active' => '1',
            'per_page' => '15',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, success):


{
    "success": true,
    "data": [
        {
            "id": "9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a",
            "url": "https://api.miempresa.cr/webhooks/Almendro",
            "secret": "whsec_****************************ab3f",
            "events": [
                "voucher.accepted",
                "voucher.rejected"
            ],
            "events_detail": [
                {
                    "value": "voucher.accepted",
                    "label": "Comprobante aceptado por Hacienda",
                    "group": "voucher"
                },
                {
                    "value": "voucher.rejected",
                    "label": "Comprobante rechazado por Hacienda",
                    "group": "voucher"
                }
            ],
            "description": "Webhook principal e-commerce",
            "is_active": true,
            "health_status": "healthy",
            "consecutive_failures": 0,
            "last_triggered_at": "2026-03-22T10:30:00-06:00",
            "last_success_at": "2026-03-22T10:30:00-06:00",
            "last_failure_at": null,
            "last_failure_reason": null,
            "created_at": "2026-03-20T08:00:00-06:00",
            "updated_at": "2026-03-22T10:30:00-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "total": 3,
        "per_page": 15,
        "last_page": 1
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Request      

GET api/v1/public/webhooks

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

is_active   boolean  optional    

Filtrar por estado. Sin filtro: muestra todos. Example: true

per_page   integer  optional    

Resultados por página (1-100). Example: 15

page   integer  optional    

Número de página. Example: 1

Crear un webhook endpoint

requires authentication

Registra una URL HTTPS que recibirá notificaciones para los eventos que usted seleccione. El sistema genera automáticamente un secret único de 64 caracteres que deberá usar para verificar la firma de cada entrega.

Importante: el secret completo se devuelve una única vez en esta respuesta, en el campo secret. Almacénelo de forma segura en ese momento | en consultas posteriores (GET /webhooks/{id}) solo verá una versión enmascarada (whsec_****...ab3f). Para obtener un nuevo secret deberá eliminar el endpoint y crear uno nuevo.

Consejos para la URL:

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/webhooks" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"url\": \"https:\\/\\/api.miempresa.cr\\/webhooks\\/Almendro\",
    \"events\": [
        \"voucher.accepted\",
        \"voucher.rejected\"
    ],
    \"description\": \"Webhook principal e-commerce\",
    \"is_active\": true
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/webhooks"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "https:\/\/api.miempresa.cr\/webhooks\/Almendro",
    "events": [
        "voucher.accepted",
        "voucher.rejected"
    ],
    "description": "Webhook principal e-commerce",
    "is_active": true
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/webhooks';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'url' => 'https://api.miempresa.cr/webhooks/Almendro',
            'events' => [
                'voucher.accepted',
                'voucher.rejected',
            ],
            'description' => 'Webhook principal e-commerce',
            'is_active' => true,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, created):


{
    "success": true,
    "data": {
        "id": "9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a",
        "url": "https://api.miempresa.cr/webhooks/Almendro",
        "secret": "a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f6ab3f",
        "events": [
            "voucher.accepted",
            "voucher.rejected"
        ],
        "events_detail": [
            {
                "value": "voucher.accepted",
                "label": "Comprobante aceptado por Hacienda",
                "group": "voucher"
            },
            {
                "value": "voucher.rejected",
                "label": "Comprobante rechazado por Hacienda",
                "group": "voucher"
            }
        ],
        "description": "Webhook principal e-commerce",
        "is_active": true,
        "health_status": "never_used",
        "consecutive_failures": 0,
        "last_triggered_at": null,
        "last_success_at": null,
        "last_failure_at": null,
        "last_failure_reason": null,
        "created_at": "2026-03-22T10:30:00-06:00",
        "updated_at": "2026-03-22T10:30:00-06:00"
    },
    "message": "Webhook endpoint creado. Almacene el secret | no se mostrará de nuevo.",
    "errors": null
}
 

Example response (422, validation_error):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "url": [
            "La URL debe usar HTTPS."
        ],
        "events.0": [
            "El evento seleccionado no es válido."
        ]
    }
}
 

Example response (422, plan_restriction):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan": [
            "Su plan actual no incluye webhooks. Actualice su plan para usar esta funcionalidad."
        ]
    }
}
 

Example response (422, limit_reached):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan": [
            "Ha alcanzado el límite de webhooks de su plan (3 endpoints)."
        ]
    }
}
 

Request      

POST api/v1/public/webhooks

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

url   string     

URL HTTPS del endpoint receptor. Example: https://api.miempresa.cr/webhooks/Almendro

events   string[]     

Lista de eventos a suscribir. Valores permitidos: voucher.sent, voucher.accepted, voucher.rejected, receiver.confirmed, receiver.deadline, certificate.expiring.

description   string  optional    

Descripción opcional para identificar el endpoint (máx 255). Example: Webhook principal e-commerce

is_active   boolean  optional    

Estado del endpoint. Solo disponible en PUT para reactivar endpoints desactivados. Al reactivar (true), se resetean los fallos consecutivos. Example: true

Response

Response Fields

data   object     
id   string     

UUID del webhook endpoint. Úselo en PUT, DELETE y GET /webhooks/{id}/logs.

url   string     

URL HTTPS registrada que recibirá los POST de cada evento.

secret   string     

Secret HMAC-SHA256 para verificar la firma de cada entrega. En POST (creación) se devuelve el texto plano completo (64 hex chars) | única vez. En GET/PUT/DELETE se devuelve enmascarado: whsec_****...últimos4.

events   string[]     

Códigos de los eventos suscritos. Valores posibles: voucher.sent, voucher.accepted, voucher.rejected, receiver.confirmed, receiver.deadline, certificate.expiring.

events_detail   object[]     

Eventos con detalle legible para UI. Cada objeto tiene value (código), label (descripción en español) y group (categoría: voucher, receiver, certificate).

description   string     

Descripción libre del endpoint para identificarlo en listados. Puede ser null.

is_active   boolean     

true si el endpoint está activo y recibirá entregas. false si fue desactivado manualmente o automáticamente tras 10 fallos consecutivos.

health_status   string     

Estado de salud computado: healthy (sin fallos), degraded (1-9 fallos consecutivos), disabled (≥10 fallos, auto-desactivado), inactive (desactivado manualmente), never_used (activo, nunca disparado).

consecutive_failures   integer     

Cantidad de fallos consecutivos sin éxito intermedio. Se resetea a 0 con cada entrega exitosa (HTTP 2xx). Al llegar a 10, el endpoint se auto-desactiva.

last_triggered_at   string     

Fecha/hora del último intento de entrega (ISO 8601). null si nunca se disparó.

last_success_at   string     

Fecha/hora de la última entrega exitosa | HTTP 2xx (ISO 8601). null si nunca hubo éxito.

last_failure_at   string     

Fecha/hora del último fallo de entrega (ISO 8601). null si nunca falló.

last_failure_reason   string     

Razón del último fallo para debugging rápido. Ejemplos: HTTP 503 Service Unavailable, Connection timeout 15s. null si nunca falló.

created_at   string     

Fecha de creación del endpoint (ISO 8601).

updated_at   string     

Fecha de última actualización del endpoint (ISO 8601).

Consultar un webhook endpoint

requires authentication

Devuelve el detalle completo de un endpoint: su URL, eventos suscritos, estado de salud actual, fallos consecutivos acumulados y timestamps de la última entrega (exitosa y fallida).

El campo secret se devuelve siempre enmascarado en este endpoint (whsec_****...ab3f), revelando solo los últimos 4 caracteres para permitir identificarlo visualmente. Si extravió el secret original, deberá eliminar el endpoint y crear uno nuevo.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, found):


{
    "success": true,
    "data": {
        "id": "9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a",
        "url": "https://api.miempresa.cr/webhooks/Almendro",
        "secret": "whsec_****************************ab3f",
        "events": [
            "voucher.accepted",
            "voucher.rejected"
        ],
        "events_detail": [
            {
                "value": "voucher.accepted",
                "label": "Comprobante aceptado por Hacienda",
                "group": "voucher"
            }
        ],
        "description": "Webhook principal e-commerce",
        "is_active": true,
        "health_status": "healthy",
        "consecutive_failures": 0,
        "last_triggered_at": "2026-03-22T10:30:00-06:00",
        "last_success_at": "2026-03-22T10:30:00-06:00",
        "last_failure_at": null,
        "last_failure_reason": null,
        "created_at": "2026-03-20T08:00:00-06:00",
        "updated_at": "2026-03-22T10:30:00-06:00"
    },
    "message": "",
    "errors": null
}
 

Example response (404, not_found):


{
    "success": false,
    "data": null,
    "message": "El recurso solicitado no existe.",
    "errors": null
}
 

Request      

GET api/v1/public/webhooks/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del webhook endpoint. Example: 9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a

Editar un webhook endpoint

requires authentication

Admite actualización parcial | envíe solo los campos que desee modificar. Los campos no enviados conservan su valor actual.

Reactivación de endpoints desactivados automáticamente: si un endpoint fue desactivado por acumular 10 fallos consecutivos, puede reactivarlo enviando is_active = true. Esto reinicia el contador de fallos a 0 y vuelve a incluir el endpoint en futuras entregas. Asegúrese antes de corregir el problema que causó los fallos.

Campos editables:

El secret no es editable. Si necesita rotarlo (por ejemplo, tras una sospecha de compromiso), elimine el endpoint actual y cree uno nuevo | recibirá un secret nuevo en la respuesta.

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"url\": \"https:\\/\\/api.miempresa.cr\\/webhooks\\/v2\",
    \"events\": [
        \"voucher.accepted\",
        \"voucher.rejected\",
        \"voucher.sent\"
    ],
    \"description\": \"Webhook e-commerce v2\",
    \"is_active\": true
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "https:\/\/api.miempresa.cr\/webhooks\/v2",
    "events": [
        "voucher.accepted",
        "voucher.rejected",
        "voucher.sent"
    ],
    "description": "Webhook e-commerce v2",
    "is_active": true
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'url' => 'https://api.miempresa.cr/webhooks/v2',
            'events' => [
                'voucher.accepted',
                'voucher.rejected',
                'voucher.sent',
            ],
            'description' => 'Webhook e-commerce v2',
            'is_active' => true,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, updated):


{
    "success": true,
    "data": {
        "id": "9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a",
        "url": "https://api.miempresa.cr/webhooks/v2",
        "secret": "whsec_****************************ab3f",
        "events": [
            "voucher.accepted",
            "voucher.rejected",
            "voucher.sent"
        ],
        "description": "Webhook e-commerce v2",
        "is_active": true,
        "health_status": "never_used",
        "consecutive_failures": 0,
        "last_triggered_at": "2026-03-22T10:30:00-06:00",
        "last_success_at": "2026-03-22T10:30:00-06:00",
        "last_failure_at": null,
        "last_failure_reason": null,
        "created_at": "2026-03-20T08:00:00-06:00",
        "updated_at": "2026-03-25T15:45:00-06:00"
    },
    "message": "Webhook endpoint actualizado correctamente.",
    "errors": null
}
 

Example response (404, not_found):


{
    "success": false,
    "data": null,
    "message": "El recurso solicitado no existe.",
    "errors": null
}
 

Request      

PUT api/v1/public/webhooks/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del webhook endpoint. Example: 9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a

Body Parameters

url   string  optional    

URL HTTPS del endpoint receptor. Example: https://api.miempresa.cr/webhooks/v2

events   string[]  optional    

Lista de eventos a suscribir.

description   string  optional    

Descripción opcional (máx 255). Example: Webhook e-commerce v2

is_active   boolean  optional    

Activar o desactivar el endpoint. Example: true

Eliminar un webhook endpoint

requires authentication

Elimina el endpoint y deja de recibir notificaciones de todos sus eventos. Los logs históricos de entregas se conservan durante el período de retención para consulta y auditoría | eliminar el endpoint no borra sus logs.

Tras la eliminación, puede crear un endpoint nuevo con la misma URL si lo desea. Recibirá un secret nuevo en la respuesta de creación.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, deleted):


{
    "success": true,
    "data": null,
    "message": "Webhook endpoint eliminado correctamente.",
    "errors": null
}
 

Example response (404, not_found):


{
    "success": false,
    "data": null,
    "message": "El recurso solicitado no existe.",
    "errors": null
}
 

Request      

DELETE api/v1/public/webhooks/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del webhook endpoint. Example: 9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a

Consultar el historial de entregas de un endpoint

requires authentication

Devuelve el registro de cada intento de entrega hacia el endpoint: evento disparado, ID de entrega (útil para deduplicar), resultado (éxito o fallo), código HTTP devuelto por su servidor y tiempo de respuesta en milisegundos.

Los logs se conservan durante 3 meses. Útil para:

Los campos voluminosos (payload enviado, response_body recibido) se excluyen del listado por razones de volumen. Si necesita esa información para una entrega específica, contacte a soporte indicando el delivery_id.

Interpretando el campo status:

Cuando una entrega tiene reintentos, cada intento queda registrado como una fila separada con el mismo delivery_id pero distinto valor de attempt (1, 2, 3 o 4).

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a/logs?event=voucher.accepted&status=failed&per_page=25&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a/logs"
);

const params = {
    "event": "voucher.accepted",
    "status": "failed",
    "per_page": "25",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a/logs';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'event' => 'voucher.accepted',
            'status' => 'failed',
            'per_page' => '25',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, success):


{
    "success": true,
    "data": [
        {
            "id": 12345,
            "event": "voucher.accepted",
            "event_label": "Comprobante aceptado por Hacienda",
            "delivery_id": "550e8400-e29b-41d4-a716-446655440000",
            "attempt": 1,
            "status": "success",
            "response_status": 200,
            "response_time_ms": 145,
            "response_time_formatted": "145ms",
            "error_message": null,
            "created_at": "2026-03-22T10:30:05-06:00"
        },
        {
            "id": 12344,
            "event": "voucher.rejected",
            "event_label": "Comprobante rechazado por Hacienda",
            "delivery_id": "660f9511-f30c-52e5-b827-557766551111",
            "attempt": 2,
            "status": "failed",
            "response_status": 503,
            "response_time_ms": 15000,
            "response_time_formatted": "15.00s",
            "error_message": "HTTP 503",
            "created_at": "2026-03-22T10:28:30-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "total": 87,
        "per_page": 25,
        "last_page": 4
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": "..."
    }
}
 

Example response (404, endpoint_not_found):


{
    "success": false,
    "data": null,
    "message": "El recurso solicitado no existe.",
    "errors": null
}
 

Request      

GET api/v1/public/webhooks/{id}/logs

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del webhook endpoint. Example: 9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a

Query Parameters

event   string  optional    

Filtrar por tipo de evento. Example: voucher.accepted

status   string  optional    

Filtrar por resultado: success o failed. Example: failed

per_page   integer  optional    

Resultados por página (1-100). Example: 25

page   integer  optional    

Número de página. Example: 1

Enviar un evento de prueba

requires authentication

Envía SÍNCRONAMENTE un payload webhook.test al endpoint, firmado con la misma HMAC-SHA256 y los mismos headers X-Almendro-* que las entregas reales. Sirve para verificar que el receptor está accesible, valida la firma y responde 2xx, sin tener que emitir un comprobante real.

La entrega queda registrada en el historial (/webhooks/{id}/logs) pero NO afecta los contadores de salud del endpoint (no auto-desactiva ni resetea fallos reales).

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a/test" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a/test"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/webhooks/9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a/test';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, delivered):


{
    "success": true,
    "data": {
        "success": true,
        "response_status": 200,
        "response_time_ms": 142,
        "error_message": null,
        "delivery_id": "…"
    },
    "message": "Evento de prueba entregado correctamente.",
    "errors": null
}
 

Request      

POST api/v1/public/webhooks/{id}/test

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del webhook endpoint. Example: 9c1b4e5a-3b2c-4d5e-6f7a-8b9c0d1e2f3a

Reportes

Dashboard

Resumen del dashboard.

requires authentication

Retorna conteos por estado Hacienda, montos agregados (venta, descuentos, impuestos, total comprobante) y desglose por tipo de comprobante para el periodo solicitado.

Los datos son base para la preparacion de la declaracion 150 (IVA mensual) y 101/102/103 (utilidades).


Modo Integrador

Cuando el token pertenece a un integrador, este endpoint agrega automaticamente los comprobantes de todos sus clientes gestionados. La respuesta incluye adicionalmente el campo by_contributor con el desglose por cliente, ordenado de mayor a menor uso.

Para filtrar solo un cliente especifico, use ?managed_contributor_id={uuid}.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/summary?date_from=2026-04-01&date_to=2026-04-30&voucher_type=01%2C04&status=accepted%2Crejected&currency_code=CRC&environment=production&receiver_id_number=3101000001&sale_condition=01%2C02&payment_method=01%2C02&group_by=month&limit=10&managed_contributor_id=019d867d-0241-7288-8ece-fd64da75616d" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/summary"
);

const params = {
    "date_from": "2026-04-01",
    "date_to": "2026-04-30",
    "voucher_type": "01,04",
    "status": "accepted,rejected",
    "currency_code": "CRC",
    "environment": "production",
    "receiver_id_number": "3101000001",
    "sale_condition": "01,02",
    "payment_method": "01,02",
    "group_by": "month",
    "limit": "10",
    "managed_contributor_id": "019d867d-0241-7288-8ece-fd64da75616d",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/summary';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'date_from' => '2026-04-01',
            'date_to' => '2026-04-30',
            'voucher_type' => '01,04',
            'status' => 'accepted,rejected',
            'currency_code' => 'CRC',
            'environment' => 'production',
            'receiver_id_number' => '3101000001',
            'sale_condition' => '01,02',
            'payment_method' => '01,02',
            'group_by' => 'month',
            'limit' => '10',
            'managed_contributor_id' => '019d867d-0241-7288-8ece-fd64da75616d',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Contribuyente normal):


{
    "success": true,
    "data": {
        "period": {
            "date_from": "2026-04-01",
            "date_to": "2026-04-30",
            "currency_code": "CRC",
            "environment": "production"
        },
        "counts": {
            "total": 150,
            "accepted": 140,
            "rejected": 5,
            "sent": 3,
            "error": 2,
            "cancelled": 0,
            "draft": 0,
            "pending": 0,
            "acceptance_rate": 96.55
        },
        "totals": {
            "total_venta": "15000000.00000",
            "total_descuentos": "500000.00000",
            "total_venta_neta": "14500000.00000",
            "total_impuesto": "1885000.00000",
            "total_otros_cargos": "0.00000",
            "total_comprobante": "16385000.00000",
            "average_per_voucher": "117035.71429"
        },
        "by_type": [
            {
                "voucher_type": "01",
                "voucher_type_label": "Factura Electrónica",
                "count": 100,
                "total_venta": "12000000.00000",
                "total_impuesto": "1560000.00000",
                "total_comprobante": "13560000.00000"
            }
        ]
    },
    "message": "",
    "errors": null
}
 

Example response (200, Integrador (incluye desglose por cliente)):


{
    "success": true,
    "data": {
        "period": {
            "date_from": "2026-04-01",
            "date_to": "2026-04-30",
            "currency_code": "CRC",
            "environment": "production"
        },
        "counts": {
            "total": 8500,
            "accepted": 8200,
            "rejected": 180,
            "sent": 70,
            "error": 50,
            "cancelled": 0,
            "draft": 0,
            "pending": 0,
            "acceptance_rate": 97.85
        },
        "totals": {
            "total_venta": "850000000.00000",
            "total_descuentos": "0.00000",
            "total_venta_neta": "850000000.00000",
            "total_impuesto": "110500000.00000",
            "total_otros_cargos": "0.00000",
            "total_comprobante": "960500000.00000",
            "average_per_voucher": "117134.14634"
        },
        "by_type": [
            {
                "voucher_type": "01",
                "voucher_type_label": "Factura Electrónica",
                "count": 7200,
                "total_venta": "720000000.00000",
                "total_impuesto": "93600000.00000",
                "total_comprobante": "813600000.00000"
            }
        ],
        "by_contributor": [
            {
                "contributor_id": "019d867d-a001-7288-8ece-fd64da756a01",
                "legal_name": "Hotel Las Palmas S.A.",
                "count": 5200,
                "total_comprobante": "621000000.00000",
                "total_impuesto": "80730000.00000"
            },
            {
                "contributor_id": "019d867d-a002-7288-8ece-fd64da756a02",
                "legal_name": "Restaurante El Mango S.A.",
                "count": 2800,
                "total_comprobante": "280000000.00000",
                "total_impuesto": "21000000.00000"
            },
            {
                "contributor_id": "019d867d-a003-7288-8ece-fd64da756a03",
                "legal_name": "SistemasPOS de Costa Rica S.A.",
                "count": 500,
                "total_comprobante": "59500000.00000",
                "total_impuesto": "8770000.00000"
            }
        ]
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/reports/summary

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

date_from   string  optional    

Inicio del periodo (YYYY-MM-DD). Default: primer dia del mes actual. Example: 2026-04-01

date_to   string  optional    

Fin del periodo (YYYY-MM-DD). Default: hoy. Example: 2026-04-30

voucher_type   string  optional    

CSV de tipos. 01,02,03,04,08,09,10. Example: 01,04

status   string  optional    

CSV de estados. Default: accepted. Example: accepted,rejected

currency_code   string  optional    

ISO 4217. Default: CRC. Example: CRC

environment   string  optional    

production o sandbox. Default: production. Example: production

receiver_id_number   string  optional    

Cedula del receptor para filtrar. Example: 3101000001

sale_condition   string  optional    

CSV de condiciones de venta. Example: 01,02

payment_method   string  optional    

CSV de medios de pago. Example: 01,02

group_by   string  optional    

Agrupación temporal (solo sales-by-period). Valores: day, week, month. Auto-detectado según rango. Example: month

Must be one of:
  • day
  • week
  • month
limit   integer  optional    

Cantidad de registros top (solo sales-by-receiver). Default: 10, máximo: 100. El campo value debe ser al menos 1. El campo value no puede ser mayor a 100. Example: 10

managed_contributor_id   string  optional    

UUID de un cliente gestionado. Solo para integradores. Filtra el resumen para mostrar unicamente los comprobantes de ese cliente. Sin este parametro se agregan todos. Example: 019d867d-0241-7288-8ece-fd64da75616d

Response

Response Fields

data   object     
period   object     

Parámetros del periodo consultado.

date_from   string     

Inicio del rango (YYYY-MM-DD). Default: primer día del mes actual.

date_to   string     

Fin del rango (YYYY-MM-DD). Default: hoy.

currency_code   string     

Moneda ISO 4217 del reporte. Default: CRC.

environment   string     

Ambiente consultado: production o sandbox.

counts   object     

Conteos de comprobantes por estado en el periodo.

total   integer     

Total de comprobantes en el rango (todos los estados).

accepted   integer     

Comprobantes aceptados por Hacienda (Mensaje=1).

rejected   integer     

Comprobantes rechazados por Hacienda (Mensaje=3).

sent   integer     

Comprobantes enviados, esperando respuesta de Hacienda.

error   integer     

Comprobantes con error temporal (se reintentan automáticamente).

cancelled   integer     

Comprobantes anulados mediante Nota de Crédito.

draft   integer     

Comprobantes en borrador (no firmados aún).

pending   integer     

Comprobantes firmados, encolados para envío.

acceptance_rate   number     

Porcentaje de aceptación: accepted / (accepted + rejected) * 100. 0 si no hay comprobantes resueltos.

totals   object     

Montos agregados del ResumenFactura (Decimal 18,5). Solo incluye comprobantes en los estados filtrados (default: accepted).

total_venta   string     

Suma de TotalVenta de todos los comprobantes.

total_descuentos   string     

Suma de TotalDescuentos.

total_venta_neta   string     

TotalVenta − TotalDescuentos.

total_impuesto   string     

Suma de todos los impuestos (IVA, selectivo, etc.).

total_otros_cargos   string     

Suma de otros cargos adicionales.

total_comprobante   string     

Monto final agregado de todos los comprobantes.

average_per_voucher   string     

Promedio por comprobante: total_comprobante / count.

by_type   object[]     

Desglose por tipo de comprobante. Cada objeto agrupa un tipo (01-10).

voucher_type   string     

Código del tipo: 01=FE, 02=ND, 03=NC, 04=TE, 08=FEC, 09=FEE, 10=REP.

voucher_type_label   string     

Nombre legible del tipo en español.

count   integer     

Cantidad de comprobantes de este tipo.

total_venta   string     

TotalVenta agregado para este tipo.

total_impuesto   string     

TotalImpuesto agregado para este tipo.

total_comprobante   string     

TotalComprobante agregado para este tipo.

by_contributor   object[]     

Desglose por cliente gestionado. Solo presente en modo integrador. Ordenado de mayor a menor uso.

contributor_id   string     

UUID del cliente gestionado.

legal_name   string     

Razón social del cliente.

count   integer     

Comprobantes emitidos por este cliente.

total_comprobante   string     

TotalComprobante agregado del cliente.

total_impuesto   string     

TotalImpuesto agregado del cliente.

Ventas por período

Ventas agrupadas por periodo.

requires authentication

Retorna series temporales de ventas agrupadas por dia, semana o mes. La agrupacion se controla con el parametro group_by (day, week, month) y se auto-detecta segun el rango de fechas si no se especifica.

Util para graficos de tendencia en el dashboard y para verificar datos antes de la declaracion 150 mensual.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/sales-by-period?date_from=2026-08-01&date_to=2026-08-11&voucher_type[]=4&status[]=draft&currency_code=CRC&environment=production&receiver_id_number=3101234567&sale_condition[]=4&payment_method[]=6&group_by=month&limit=10" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/sales-by-period"
);

const params = {
    "date_from": "2026-08-01",
    "date_to": "2026-08-11",
    "voucher_type[0]": "4",
    "status[0]": "draft",
    "currency_code": "CRC",
    "environment": "production",
    "receiver_id_number": "3101234567",
    "sale_condition[0]": "4",
    "payment_method[0]": "6",
    "group_by": "month",
    "limit": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/sales-by-period';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'date_from' => '2026-08-01',
            'date_to' => '2026-08-11',
            'voucher_type[0]' => '4',
            'status[0]' => 'draft',
            'currency_code' => 'CRC',
            'environment' => 'production',
            'receiver_id_number' => '3101234567',
            'sale_condition[0]' => '4',
            'payment_method[0]' => '6',
            'group_by' => 'month',
            'limit' => '10',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "success": true,
    "data": {
        "period": {
            "date_from": "2026-03-01",
            "date_to": "2026-03-31",
            "currency_code": "CRC",
            "environment": "production"
        },
        "group_by": "week",
        "data": [
            {
                "period_start": "2026-03-02",
                "period_label": "Sem. 02 mar 2026",
                "count": 48,
                "total_venta": "3120500.00000",
                "total_descuentos": "45000.00000",
                "total_impuesto": "399815.00000",
                "total_comprobante": "3475315.00000"
            },
            {
                "period_start": "2026-03-09",
                "period_label": "Sem. 09 mar 2026",
                "count": 52,
                "total_venta": "3410820.50000",
                "total_descuentos": "38000.00000",
                "total_impuesto": "438846.67000",
                "total_comprobante": "3811667.17000"
            },
            {
                "period_start": "2026-03-16",
                "period_label": "Sem. 16 mar 2026",
                "count": 46,
                "total_venta": "2719000.00000",
                "total_descuentos": "42000.00000",
                "total_impuesto": "298880.00000",
                "total_comprobante": "2975880.00000"
            }
        ],
        "grand_total": {
            "count": 146,
            "total_venta": "9250320.50000",
            "total_impuesto": "1137541.67000",
            "total_comprobante": "10262862.17000"
        }
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/reports/sales-by-period

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

date_from   string  optional    

Inicio del rango de fecha de emisión (YYYY-MM-DD). Default: primer día del mes actual. Must be a valid date in the format Y-m-d. Example: 2026-08-01

date_to   string  optional    

Fin del rango de fecha de emisión (YYYY-MM-DD). Default: hoy. Debe ser >= date_from. Must be a valid date in the format Y-m-d. El campo value debe ser una fecha posterior o igual a date_from. Example: 2026-08-11

voucher_type   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 8
  • 9
  • 10
status   string[]     
Must be one of:
  • draft
  • pending
  • sent
  • accepted
  • rejected
  • error
  • cancelled
currency_code   string  optional    

Moneda ISO 4217 (3 letras). Default: CRC (150 en colones). Must match the regex /^[A-Z]{3}$/. El campo value debe tener 3 caracteres. Example: CRC

environment   string  optional    

Ambiente: sandbox o production. Default: production (solo comprobantes con validez fiscal). Example: production

Must be one of:
  • sandbox
  • production
receiver_id_number   string  optional    

Cédula del receptor (9-12 dígitos). Art. 13.10 criterio 1. Must match the regex /^\d{9,12}$/. Example: 3101234567

sale_condition   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 99
payment_method   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 99
group_by   string  optional    

Agrupación temporal (solo sales-by-period). Valores: day, week, month. Auto-detectado según rango. Example: month

Must be one of:
  • day
  • week
  • month
limit   integer  optional    

Cantidad de registros top (solo sales-by-receiver). Default: 10, máximo: 100. El campo value debe ser al menos 1. El campo value no puede ser mayor a 100. Example: 10

Ventas por receptor

Ranking de ventas por receptor.

requires authentication

Retorna los receptores (clientes) con mayor monto facturado en el periodo, ordenados de mayor a menor. El parametro limit controla cuantos registros retornar (default 10, maximo 100).

Util para identificar clientes principales y para conciliacion fiscal por cedula de receptor.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/sales-by-receiver?date_from=2026-08-01&date_to=2026-08-11&voucher_type[]=9&status[]=pending&currency_code=CRC&environment=production&receiver_id_number=3101234567&sale_condition[]=11&payment_method[]=2&group_by=month&limit=10" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/sales-by-receiver"
);

const params = {
    "date_from": "2026-08-01",
    "date_to": "2026-08-11",
    "voucher_type[0]": "9",
    "status[0]": "pending",
    "currency_code": "CRC",
    "environment": "production",
    "receiver_id_number": "3101234567",
    "sale_condition[0]": "11",
    "payment_method[0]": "2",
    "group_by": "month",
    "limit": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/sales-by-receiver';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'date_from' => '2026-08-01',
            'date_to' => '2026-08-11',
            'voucher_type[0]' => '9',
            'status[0]' => 'pending',
            'currency_code' => 'CRC',
            'environment' => 'production',
            'receiver_id_number' => '3101234567',
            'sale_condition[0]' => '11',
            'payment_method[0]' => '2',
            'group_by' => 'month',
            'limit' => '10',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "success": true,
    "data": {
        "period": {
            "date_from": "2026-03-01",
            "date_to": "2026-03-31",
            "currency_code": "CRC",
            "environment": "production"
        },
        "limit": 10,
        "data": [
            {
                "receiver_id_type": "02",
                "receiver_id_number": "3101123456",
                "receiver_name": "Comercializadora La Catedral S.A.",
                "count": 24,
                "total_venta": "2980000.00000",
                "total_impuesto": "387400.00000",
                "total_comprobante": "3367400.00000",
                "percentage": 32.81
            },
            {
                "receiver_id_type": "02",
                "receiver_id_number": "3101998877",
                "receiver_name": "Inversiones del Valle S.R.L.",
                "count": 18,
                "total_venta": "1740500.50000",
                "total_impuesto": "226265.07000",
                "total_comprobante": "1966765.57000",
                "percentage": 19.16
            },
            {
                "receiver_id_type": "01",
                "receiver_id_number": "108880777",
                "receiver_name": "María Fernanda Rojas Alvarado",
                "count": 11,
                "total_venta": "845000.00000",
                "total_impuesto": "109850.00000",
                "total_comprobante": "954850.00000",
                "percentage": 9.3
            }
        ],
        "others": {
            "count": 93,
            "total_comprobante": "3973846.60000",
            "percentage": 38.73
        },
        "grand_total": {
            "count": 146,
            "total_comprobante": "10262862.17000",
            "unique_receivers": 47
        }
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/reports/sales-by-receiver

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

date_from   string  optional    

Inicio del rango de fecha de emisión (YYYY-MM-DD). Default: primer día del mes actual. Must be a valid date in the format Y-m-d. Example: 2026-08-01

date_to   string  optional    

Fin del rango de fecha de emisión (YYYY-MM-DD). Default: hoy. Debe ser >= date_from. Must be a valid date in the format Y-m-d. El campo value debe ser una fecha posterior o igual a date_from. Example: 2026-08-11

voucher_type   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 8
  • 9
  • 10
status   string[]     
Must be one of:
  • draft
  • pending
  • sent
  • accepted
  • rejected
  • error
  • cancelled
currency_code   string  optional    

Moneda ISO 4217 (3 letras). Default: CRC (150 en colones). Must match the regex /^[A-Z]{3}$/. El campo value debe tener 3 caracteres. Example: CRC

environment   string  optional    

Ambiente: sandbox o production. Default: production (solo comprobantes con validez fiscal). Example: production

Must be one of:
  • sandbox
  • production
receiver_id_number   string  optional    

Cédula del receptor (9-12 dígitos). Art. 13.10 criterio 1. Must match the regex /^\d{9,12}$/. Example: 3101234567

sale_condition   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 99
payment_method   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 99
group_by   string  optional    

Agrupación temporal (solo sales-by-period). Valores: day, week, month. Auto-detectado según rango. Example: month

Must be one of:
  • day
  • week
  • month
limit   integer  optional    

Cantidad de registros top (solo sales-by-receiver). Default: 10, máximo: 100. El campo value debe ser al menos 1. El campo value no puede ser mayor a 100. Example: 10

Ventas por actividad

Ventas agrupadas por actividad economica.

requires authentication

Retorna montos y conteos agrupados por codigo de actividad economica CIIU del emisor. Esencial para la declaracion 150 que requiere reportar IVA desglosado por actividad economica.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/sales-by-activity?date_from=2026-08-01&date_to=2026-08-11&voucher_type[]=8&status[]=sent&currency_code=CRC&environment=production&receiver_id_number=3101234567&sale_condition[]=10&payment_method[]=6&group_by=month&limit=10" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/sales-by-activity"
);

const params = {
    "date_from": "2026-08-01",
    "date_to": "2026-08-11",
    "voucher_type[0]": "8",
    "status[0]": "sent",
    "currency_code": "CRC",
    "environment": "production",
    "receiver_id_number": "3101234567",
    "sale_condition[0]": "10",
    "payment_method[0]": "6",
    "group_by": "month",
    "limit": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/sales-by-activity';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'date_from' => '2026-08-01',
            'date_to' => '2026-08-11',
            'voucher_type[0]' => '8',
            'status[0]' => 'sent',
            'currency_code' => 'CRC',
            'environment' => 'production',
            'receiver_id_number' => '3101234567',
            'sale_condition[0]' => '10',
            'payment_method[0]' => '6',
            'group_by' => 'month',
            'limit' => '10',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "success": true,
    "data": {
        "period": {
            "date_from": "2026-03-01",
            "date_to": "2026-03-31",
            "currency_code": "CRC",
            "environment": "production"
        },
        "data": [
            {
                "activity_code": "620100",
                "activity_description": "Actividades de programación informática",
                "count": 86,
                "total_venta": "6120000.00000",
                "total_impuesto": "795600.00000",
                "total_comprobante": "6915600.00000",
                "percentage": 67.38
            },
            {
                "activity_code": "702000",
                "activity_description": "Actividades de consultoría de gestión",
                "count": 41,
                "total_venta": "2380320.50000",
                "total_impuesto": "309441.67000",
                "total_comprobante": "2689762.17000",
                "percentage": 26.21
            },
            {
                "activity_code": "631100",
                "activity_description": "Procesamiento de datos y hospedaje",
                "count": 19,
                "total_venta": "750000.00000",
                "total_impuesto": "97500.00000",
                "total_comprobante": "657500.00000",
                "percentage": 6.41
            }
        ],
        "grand_total": {
            "count": 146,
            "total_comprobante": "10262862.17000",
            "unique_activities": 3
        }
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/reports/sales-by-activity

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

date_from   string  optional    

Inicio del rango de fecha de emisión (YYYY-MM-DD). Default: primer día del mes actual. Must be a valid date in the format Y-m-d. Example: 2026-08-01

date_to   string  optional    

Fin del rango de fecha de emisión (YYYY-MM-DD). Default: hoy. Debe ser >= date_from. Must be a valid date in the format Y-m-d. El campo value debe ser una fecha posterior o igual a date_from. Example: 2026-08-11

voucher_type   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 8
  • 9
  • 10
status   string[]     
Must be one of:
  • draft
  • pending
  • sent
  • accepted
  • rejected
  • error
  • cancelled
currency_code   string  optional    

Moneda ISO 4217 (3 letras). Default: CRC (150 en colones). Must match the regex /^[A-Z]{3}$/. El campo value debe tener 3 caracteres. Example: CRC

environment   string  optional    

Ambiente: sandbox o production. Default: production (solo comprobantes con validez fiscal). Example: production

Must be one of:
  • sandbox
  • production
receiver_id_number   string  optional    

Cédula del receptor (9-12 dígitos). Art. 13.10 criterio 1. Must match the regex /^\d{9,12}$/. Example: 3101234567

sale_condition   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 99
payment_method   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 99
group_by   string  optional    

Agrupación temporal (solo sales-by-period). Valores: day, week, month. Auto-detectado según rango. Example: month

Must be one of:
  • day
  • week
  • month
limit   integer  optional    

Cantidad de registros top (solo sales-by-receiver). Default: 10, máximo: 100. El campo value debe ser al menos 1. El campo value no puede ser mayor a 100. Example: 10

IVA | Desglose por tarifa

Desglose de IVA por tarifa | datos para la declaracion 150.

requires authentication

Retorna el impuesto agrupado por codigo de tarifa IVA (0%, 1%, 2%, 4%, 13%, exento, etc.). La declaracion 150 ante Hacienda requiere reportar el IVA desglosado exactamente por estas tarifas.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/tax-summary?date_from=2026-08-01&date_to=2026-08-11&voucher_type[]=2&status[]=draft&currency_code=CRC&environment=production&receiver_id_number=3101234567&sale_condition[]=4&payment_method[]=99&group_by=month&limit=10" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/tax-summary"
);

const params = {
    "date_from": "2026-08-01",
    "date_to": "2026-08-11",
    "voucher_type[0]": "2",
    "status[0]": "draft",
    "currency_code": "CRC",
    "environment": "production",
    "receiver_id_number": "3101234567",
    "sale_condition[0]": "4",
    "payment_method[0]": "99",
    "group_by": "month",
    "limit": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/tax-summary';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'date_from' => '2026-08-01',
            'date_to' => '2026-08-11',
            'voucher_type[0]' => '2',
            'status[0]' => 'draft',
            'currency_code' => 'CRC',
            'environment' => 'production',
            'receiver_id_number' => '3101234567',
            'sale_condition[0]' => '4',
            'payment_method[0]' => '99',
            'group_by' => 'month',
            'limit' => '10',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "success": true,
    "data": {
        "period": {
            "date_from": "2026-03-01",
            "date_to": "2026-03-31",
            "currency_code": "CRC",
            "environment": "production"
        },
        "by_tax_code": [
            {
                "tax_code": "01",
                "tax_code_label": "IVA",
                "total_monto": "1118041.67000",
                "voucher_count": 138,
                "breakdown_by_rate": [
                    {
                        "rate_code": "08",
                        "rate_label": "13% General",
                        "rate_percentage": 13,
                        "total_monto": "1095541.67000",
                        "voucher_count": 132
                    },
                    {
                        "rate_code": "04",
                        "rate_label": "4% Bienes y servicios especiales",
                        "rate_percentage": 4,
                        "total_monto": "22500.00000",
                        "voucher_count": 6
                    }
                ]
            },
            {
                "tax_code": "07",
                "tax_code_label": "Impuesto específico",
                "total_monto": "19500.00000",
                "voucher_count": 8,
                "breakdown_by_rate": [
                    {
                        "rate_code": "11",
                        "rate_label": "Tarifa especial",
                        "rate_percentage": 0,
                        "total_monto": "19500.00000",
                        "voucher_count": 8
                    }
                ]
            }
        ],
        "totals": {
            "total_impuesto": "1137541.67000",
            "total_iva_devuelto": "0.00000",
            "voucher_count": 146
        }
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/reports/tax-summary

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

date_from   string  optional    

Inicio del rango de fecha de emisión (YYYY-MM-DD). Default: primer día del mes actual. Must be a valid date in the format Y-m-d. Example: 2026-08-01

date_to   string  optional    

Fin del rango de fecha de emisión (YYYY-MM-DD). Default: hoy. Debe ser >= date_from. Must be a valid date in the format Y-m-d. El campo value debe ser una fecha posterior o igual a date_from. Example: 2026-08-11

voucher_type   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 8
  • 9
  • 10
status   string[]     
Must be one of:
  • draft
  • pending
  • sent
  • accepted
  • rejected
  • error
  • cancelled
currency_code   string  optional    

Moneda ISO 4217 (3 letras). Default: CRC (150 en colones). Must match the regex /^[A-Z]{3}$/. El campo value debe tener 3 caracteres. Example: CRC

environment   string  optional    

Ambiente: sandbox o production. Default: production (solo comprobantes con validez fiscal). Example: production

Must be one of:
  • sandbox
  • production
receiver_id_number   string  optional    

Cédula del receptor (9-12 dígitos). Art. 13.10 criterio 1. Must match the regex /^\d{9,12}$/. Example: 3101234567

sale_condition   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 99
payment_method   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 99
group_by   string  optional    

Agrupación temporal (solo sales-by-period). Valores: day, week, month. Auto-detectado según rango. Example: month

Must be one of:
  • day
  • week
  • month
limit   integer  optional    

Cantidad de registros top (solo sales-by-receiver). Default: 10, máximo: 100. El campo value debe ser al menos 1. El campo value no puede ser mayor a 100. Example: 10

MensajeReceptor

Resumen de confirmaciones de receptor (MensajeReceptor).

requires authentication

Retorna el conteo de mensajes de receptor agrupados por estado: confirmados, pendientes y vencidos. Incluye alertas de plazo segun el art. 15 del Reglamento (8 dias habiles para confirmar).

Este endpoint solo acepta los filtros date_from, date_to y environment. Los demas filtros (voucher_type, currency_code, etc.) no aplican a mensajes de receptor.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/receiver-messages?date_from=2026-08-01&date_to=2026-08-11&voucher_type[]=2&status[]=accepted&currency_code=CRC&environment=production&receiver_id_number=3101234567&sale_condition[]=10&payment_method[]=4&group_by=month&limit=10" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/receiver-messages"
);

const params = {
    "date_from": "2026-08-01",
    "date_to": "2026-08-11",
    "voucher_type[0]": "2",
    "status[0]": "accepted",
    "currency_code": "CRC",
    "environment": "production",
    "receiver_id_number": "3101234567",
    "sale_condition[0]": "10",
    "payment_method[0]": "4",
    "group_by": "month",
    "limit": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/receiver-messages';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'date_from' => '2026-08-01',
            'date_to' => '2026-08-11',
            'voucher_type[0]' => '2',
            'status[0]' => 'accepted',
            'currency_code' => 'CRC',
            'environment' => 'production',
            'receiver_id_number' => '3101234567',
            'sale_condition[0]' => '10',
            'payment_method[0]' => '4',
            'group_by' => 'month',
            'limit' => '10',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "success": true,
    "data": {
        "period": {
            "date_from": "2026-03-01",
            "date_to": "2026-03-31",
            "environment": "production"
        },
        "summary": {
            "total": 64,
            "by_status": {
                "accepted": 58,
                "sent": 3,
                "draft": 2,
                "error": 1
            },
            "by_message": {
                "accepted_total": 55,
                "accepted_partial": 3,
                "rejected": 2
            }
        },
        "deadline_alerts": {
            "overdue": 2,
            "due_soon": 5,
            "on_time": 57,
            "deadline_business_days": 8,
            "alert_threshold_days": 2
        },
        "by_issuer": [
            {
                "issuer_id_number": "3101456789",
                "count": 31,
                "total_invoice": "4120000.00000",
                "total_tax": "535600.00000",
                "accepted": 29,
                "rejected": 1
            },
            {
                "issuer_id_number": "3101778899",
                "count": 22,
                "total_invoice": "1890500.00000",
                "total_tax": "245765.00000",
                "accepted": 21,
                "rejected": 0
            },
            {
                "issuer_id_number": "108450321",
                "count": 11,
                "total_invoice": "612000.00000",
                "total_tax": "79560.00000",
                "accepted": 8,
                "rejected": 1
            }
        ]
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/reports/receiver-messages

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

date_from   string  optional    

Inicio del rango de fecha de emisión (YYYY-MM-DD). Default: primer día del mes actual. Must be a valid date in the format Y-m-d. Example: 2026-08-01

date_to   string  optional    

Fin del rango de fecha de emisión (YYYY-MM-DD). Default: hoy. Debe ser >= date_from. Must be a valid date in the format Y-m-d. El campo value debe ser una fecha posterior o igual a date_from. Example: 2026-08-11

voucher_type   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 8
  • 9
  • 10
status   string[]     
Must be one of:
  • draft
  • pending
  • sent
  • accepted
  • rejected
  • error
  • cancelled
currency_code   string  optional    

Moneda ISO 4217 (3 letras). Default: CRC (150 en colones). Must match the regex /^[A-Z]{3}$/. El campo value debe tener 3 caracteres. Example: CRC

environment   string  optional    

Ambiente: sandbox o production. Default: production (solo comprobantes con validez fiscal). Example: production

Must be one of:
  • sandbox
  • production
receiver_id_number   string  optional    

Cédula del receptor (9-12 dígitos). Art. 13.10 criterio 1. Must match the regex /^\d{9,12}$/. Example: 3101234567

sale_condition   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 99
payment_method   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 99
group_by   string  optional    

Agrupación temporal (solo sales-by-period). Valores: day, week, month. Auto-detectado según rango. Example: month

Must be one of:
  • day
  • week
  • month
limit   integer  optional    

Cantidad de registros top (solo sales-by-receiver). Default: 10, máximo: 100. El campo value debe ser al menos 1. El campo value no puede ser mayor a 100. Example: 10

Estado Hacienda

Comprobantes agrupados por estado Hacienda.

requires authentication

Retorna conteo y montos agrupados por el estado de confirmacion de Hacienda (aceptado, rechazado, enviado, error, pendiente). Incluye metricas como tasa de aceptacion, tasa de rechazo y conteo de comprobantes pendientes.

A diferencia de otros reportes, este endpoint no filtra por estado (ya que el proposito es mostrar TODOS los estados agrupados).

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/vouchers-by-status?date_from=2026-08-01&date_to=2026-08-11&voucher_type[]=9&status[]=error&currency_code=CRC&environment=production&receiver_id_number=3101234567&sale_condition[]=14&payment_method[]=99&group_by=month&limit=10" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/vouchers-by-status"
);

const params = {
    "date_from": "2026-08-01",
    "date_to": "2026-08-11",
    "voucher_type[0]": "9",
    "status[0]": "error",
    "currency_code": "CRC",
    "environment": "production",
    "receiver_id_number": "3101234567",
    "sale_condition[0]": "14",
    "payment_method[0]": "99",
    "group_by": "month",
    "limit": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/vouchers-by-status';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'date_from' => '2026-08-01',
            'date_to' => '2026-08-11',
            'voucher_type[0]' => '9',
            'status[0]' => 'error',
            'currency_code' => 'CRC',
            'environment' => 'production',
            'receiver_id_number' => '3101234567',
            'sale_condition[0]' => '14',
            'payment_method[0]' => '99',
            'group_by' => 'month',
            'limit' => '10',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200):


{
    "success": true,
    "data": {
        "period": {
            "date_from": "2026-03-01",
            "date_to": "2026-03-31",
            "currency_code": "CRC",
            "environment": "production"
        },
        "data": [
            {
                "status": "accepted",
                "status_label": "Aceptado",
                "count": 138,
                "total_venta": "8920320.50000",
                "total_impuesto": "1118041.67000",
                "total_comprobante": "9912362.17000",
                "percentage_count": 94.52,
                "percentage_amount": 96.58
            },
            {
                "status": "rejected",
                "status_label": "Rechazado",
                "count": 4,
                "total_venta": "210000.00000",
                "total_impuesto": "16000.00000",
                "total_comprobante": "275500.00000",
                "percentage_count": 2.74,
                "percentage_amount": 2.68
            },
            {
                "status": "pending",
                "status_label": "Pendiente",
                "count": 3,
                "total_venta": "108000.00000",
                "total_impuesto": "3500.00000",
                "total_comprobante": "62500.00000",
                "percentage_count": 2.05,
                "percentage_amount": 0.61
            },
            {
                "status": "error",
                "status_label": "Error",
                "count": 1,
                "total_venta": "12000.00000",
                "total_impuesto": "0.00000",
                "total_comprobante": "12500.00000",
                "percentage_count": 0.68,
                "percentage_amount": 0.12
            }
        ],
        "grand_total": {
            "count": 146,
            "total_comprobante": "10262862.17000"
        },
        "metrics": {
            "acceptance_rate": 94.52,
            "rejection_rate": 2.74,
            "pending_count": 3,
            "error_count": 1,
            "avg_processing_time_hours": 1.4
        }
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/reports/vouchers-by-status

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

date_from   string  optional    

Inicio del rango de fecha de emisión (YYYY-MM-DD). Default: primer día del mes actual. Must be a valid date in the format Y-m-d. Example: 2026-08-01

date_to   string  optional    

Fin del rango de fecha de emisión (YYYY-MM-DD). Default: hoy. Debe ser >= date_from. Must be a valid date in the format Y-m-d. El campo value debe ser una fecha posterior o igual a date_from. Example: 2026-08-11

voucher_type   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 8
  • 9
  • 10
status   string[]     
Must be one of:
  • draft
  • pending
  • sent
  • accepted
  • rejected
  • error
  • cancelled
currency_code   string  optional    

Moneda ISO 4217 (3 letras). Default: CRC (150 en colones). Must match the regex /^[A-Z]{3}$/. El campo value debe tener 3 caracteres. Example: CRC

environment   string  optional    

Ambiente: sandbox o production. Default: production (solo comprobantes con validez fiscal). Example: production

Must be one of:
  • sandbox
  • production
receiver_id_number   string  optional    

Cédula del receptor (9-12 dígitos). Art. 13.10 criterio 1. Must match the regex /^\d{9,12}$/. Example: 3101234567

sale_condition   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 99
payment_method   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 99
group_by   string  optional    

Agrupación temporal (solo sales-by-period). Valores: day, week, month. Auto-detectado según rango. Example: month

Must be one of:
  • day
  • week
  • month
limit   integer  optional    

Cantidad de registros top (solo sales-by-receiver). Default: 10, máximo: 100. El campo value debe ser al menos 1. El campo value no puede ser mayor a 100. Example: 10

IVA | Auxiliar D-150

Auxiliar del IVA (D-150): ventas y compras por tarifa.

requires authentication

Produce los dos auxiliares de la declaracion autoliquidativa 150 de TRIBU-CR: ventas generales por tarifa (base + impuesto), ventas sin IVA, compras por tarifa (importe + impuesto soportado), y el detalle por cliente y por proveedor. Solo comprobantes ACEPTADOS; las notas de credito restan.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/iva-auxiliary?date_from=2026-08-01&date_to=2026-08-11&voucher_type[]=1&status[]=accepted&currency_code=CRC&environment=production&receiver_id_number=3101234567&sale_condition[]=10&payment_method[]=6&group_by=month&limit=10" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/iva-auxiliary"
);

const params = {
    "date_from": "2026-08-01",
    "date_to": "2026-08-11",
    "voucher_type[0]": "1",
    "status[0]": "accepted",
    "currency_code": "CRC",
    "environment": "production",
    "receiver_id_number": "3101234567",
    "sale_condition[0]": "10",
    "payment_method[0]": "6",
    "group_by": "month",
    "limit": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/iva-auxiliary';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'date_from' => '2026-08-01',
            'date_to' => '2026-08-11',
            'voucher_type[0]' => '1',
            'status[0]' => 'accepted',
            'currency_code' => 'CRC',
            'environment' => 'production',
            'receiver_id_number' => '3101234567',
            'sale_condition[0]' => '10',
            'payment_method[0]' => '6',
            'group_by' => 'month',
            'limit' => '10',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/reports/iva-auxiliary

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

date_from   string  optional    

Inicio del rango de fecha de emisión (YYYY-MM-DD). Default: primer día del mes actual. Must be a valid date in the format Y-m-d. Example: 2026-08-01

date_to   string  optional    

Fin del rango de fecha de emisión (YYYY-MM-DD). Default: hoy. Debe ser >= date_from. Must be a valid date in the format Y-m-d. El campo value debe ser una fecha posterior o igual a date_from. Example: 2026-08-11

voucher_type   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 8
  • 9
  • 10
status   string[]     
Must be one of:
  • draft
  • pending
  • sent
  • accepted
  • rejected
  • error
  • cancelled
currency_code   string  optional    

Moneda ISO 4217 (3 letras). Default: CRC (150 en colones). Must match the regex /^[A-Z]{3}$/. El campo value debe tener 3 caracteres. Example: CRC

environment   string  optional    

Ambiente: sandbox o production. Default: production (solo comprobantes con validez fiscal). Example: production

Must be one of:
  • sandbox
  • production
receiver_id_number   string  optional    

Cédula del receptor (9-12 dígitos). Art. 13.10 criterio 1. Must match the regex /^\d{9,12}$/. Example: 3101234567

sale_condition   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 99
payment_method   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 99
group_by   string  optional    

Agrupación temporal (solo sales-by-period). Valores: day, week, month. Auto-detectado según rango. Example: month

Must be one of:
  • day
  • week
  • month
limit   integer  optional    

Cantidad de registros top (solo sales-by-receiver). Default: 10, máximo: 100. El campo value debe ser al menos 1. El campo value no puede ser mayor a 100. Example: 10

Exporta el auxiliar del IVA (D-150) en CSV o PDF.

requires authentication

format=csv&section=sales|purchases descarga el auxiliar pedido en CSV; format=pdf descarga el documento completo (ventas + compras) listo para imprimir o adjuntar al expediente del periodo.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/iva-auxiliary/export?date_from=2026-08-01&date_to=2026-08-11&voucher_type[]=9&status[]=rejected&currency_code=CRC&environment=production&receiver_id_number=3101234567&sale_condition[]=9&payment_method[]=6&group_by=month&limit=10" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/iva-auxiliary/export"
);

const params = {
    "date_from": "2026-08-01",
    "date_to": "2026-08-11",
    "voucher_type[0]": "9",
    "status[0]": "rejected",
    "currency_code": "CRC",
    "environment": "production",
    "receiver_id_number": "3101234567",
    "sale_condition[0]": "9",
    "payment_method[0]": "6",
    "group_by": "month",
    "limit": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/iva-auxiliary/export';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'date_from' => '2026-08-01',
            'date_to' => '2026-08-11',
            'voucher_type[0]' => '9',
            'status[0]' => 'rejected',
            'currency_code' => 'CRC',
            'environment' => 'production',
            'receiver_id_number' => '3101234567',
            'sale_condition[0]' => '9',
            'payment_method[0]' => '6',
            'group_by' => 'month',
            'limit' => '10',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/reports/iva-auxiliary/export

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

date_from   string  optional    

Inicio del rango de fecha de emisión (YYYY-MM-DD). Default: primer día del mes actual. Must be a valid date in the format Y-m-d. Example: 2026-08-01

date_to   string  optional    

Fin del rango de fecha de emisión (YYYY-MM-DD). Default: hoy. Debe ser >= date_from. Must be a valid date in the format Y-m-d. El campo value debe ser una fecha posterior o igual a date_from. Example: 2026-08-11

voucher_type   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 8
  • 9
  • 10
status   string[]     
Must be one of:
  • draft
  • pending
  • sent
  • accepted
  • rejected
  • error
  • cancelled
currency_code   string  optional    

Moneda ISO 4217 (3 letras). Default: CRC (150 en colones). Must match the regex /^[A-Z]{3}$/. El campo value debe tener 3 caracteres. Example: CRC

environment   string  optional    

Ambiente: sandbox o production. Default: production (solo comprobantes con validez fiscal). Example: production

Must be one of:
  • sandbox
  • production
receiver_id_number   string  optional    

Cédula del receptor (9-12 dígitos). Art. 13.10 criterio 1. Must match the regex /^\d{9,12}$/. Example: 3101234567

sale_condition   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 99
payment_method   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 99
group_by   string  optional    

Agrupación temporal (solo sales-by-period). Valores: day, week, month. Auto-detectado según rango. Example: month

Must be one of:
  • day
  • week
  • month
limit   integer  optional    

Cantidad de registros top (solo sales-by-receiver). Default: 10, máximo: 100. El campo value debe ser al menos 1. El campo value no puede ser mayor a 100. Example: 10

Export

Exporta un reporte del portal en CSV o PDF.

requires authentication

Reportes: sales-by-period, sales-by-receiver, sales-by-activity. format=csv descarga la tabla; format=pdf el documento membretado.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/sales-by-period|sales-by-receiver|sales-by-activity/export?date_from=2026-08-01&date_to=2026-08-11&voucher_type[]=3&status[]=sent&currency_code=CRC&environment=production&receiver_id_number=3101234567&sale_condition[]=11&payment_method[]=4&group_by=month&limit=10" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/sales-by-period|sales-by-receiver|sales-by-activity/export"
);

const params = {
    "date_from": "2026-08-01",
    "date_to": "2026-08-11",
    "voucher_type[0]": "3",
    "status[0]": "sent",
    "currency_code": "CRC",
    "environment": "production",
    "receiver_id_number": "3101234567",
    "sale_condition[0]": "11",
    "payment_method[0]": "4",
    "group_by": "month",
    "limit": "10",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/sales-by-period|sales-by-receiver|sales-by-activity/export';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'date_from' => '2026-08-01',
            'date_to' => '2026-08-11',
            'voucher_type[0]' => '3',
            'status[0]' => 'sent',
            'currency_code' => 'CRC',
            'environment' => 'production',
            'receiver_id_number' => '3101234567',
            'sale_condition[0]' => '11',
            'payment_method[0]' => '4',
            'group_by' => 'month',
            'limit' => '10',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/reports/{report}/export

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

report   string     

The report. Example: sales-by-period|sales-by-receiver|sales-by-activity

Query Parameters

date_from   string  optional    

Inicio del rango de fecha de emisión (YYYY-MM-DD). Default: primer día del mes actual. Must be a valid date in the format Y-m-d. Example: 2026-08-01

date_to   string  optional    

Fin del rango de fecha de emisión (YYYY-MM-DD). Default: hoy. Debe ser >= date_from. Must be a valid date in the format Y-m-d. El campo value debe ser una fecha posterior o igual a date_from. Example: 2026-08-11

voucher_type   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 8
  • 9
  • 10
status   string[]     
Must be one of:
  • draft
  • pending
  • sent
  • accepted
  • rejected
  • error
  • cancelled
currency_code   string  optional    

Moneda ISO 4217 (3 letras). Default: CRC (150 en colones). Must match the regex /^[A-Z]{3}$/. El campo value debe tener 3 caracteres. Example: CRC

environment   string  optional    

Ambiente: sandbox o production. Default: production (solo comprobantes con validez fiscal). Example: production

Must be one of:
  • sandbox
  • production
receiver_id_number   string  optional    

Cédula del receptor (9-12 dígitos). Art. 13.10 criterio 1. Must match the regex /^\d{9,12}$/. Example: 3101234567

sale_condition   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 99
payment_method   string[]     
Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 99
group_by   string  optional    

Agrupación temporal (solo sales-by-period). Valores: day, week, month. Auto-detectado según rango. Example: month

Must be one of:
  • day
  • week
  • month
limit   integer  optional    

Cantidad de registros top (solo sales-by-receiver). Default: 10, máximo: 100. El campo value debe ser al menos 1. El campo value no puede ser mayor a 100. Example: 10

Filtros

Facets para los filtros de reportes.

requires authentication

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/reports/filters" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reports/filters"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reports/filters';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/reports/filters

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Catálogos

Buscar en el catalogo CABYS (Bienes y Servicios).

requires authentication

Busqueda paginada en el catalogo oficial de bienes y servicios de Hacienda (~20,501 codigos). El codigo CABYS es obligatorio en todos los comprobantes electronicos (tipos 01-09).

Si q contiene solo digitos, busca por prefijo de codigo. Si contiene texto, busca por descripcion. Sin q, retorna todos los codigos activos ordenados por codigo.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/catalogs/cabys?q=arroz&per_page=10&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/catalogs/cabys"
);

const params = {
    "q": "arroz",
    "per_page": "10",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/catalogs/cabys';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'q' => 'arroz',
            'per_page' => '10',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Busqueda por texto):


{
    "success": true,
    "data": [
        {
            "code": "0111101000100",
            "description": "Arroz con cáscara (arroz paddy)",
            "tax_rate": "01",
            "tax_code": "08",
            "is_merchandise": true,
            "is_medicine": false
        },
        {
            "code": "0111101000200",
            "description": "Arroz descascarillado (arroz cargo o arroz pardo)",
            "tax_rate": "01",
            "tax_code": "08",
            "is_merchandise": true,
            "is_medicine": false
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "per_page": 25,
        "has_more": true
    },
    "links": {
        "prev": null,
        "next": "/api/v1/public/catalogs/cabys?q=arroz&page=2"
    }
}
 

Request      

GET api/v1/public/catalogs/cabys

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

q   string  optional    

Texto libre o prefijo numerico del codigo CABYS. Minimo 2 caracteres. Example: arroz

per_page   integer  optional    

Resultados por pagina (1-100). Default: 25. Example: 10

page   integer  optional    

Pagina actual. Example: 1

Response

Response Fields

data   object     
code   string     

Código CABYS de 13 dígitos exactos. Va directo al campo CodigoCABYS de cada LineaDetalle del XML.

description   string     

Descripción oficial del bien o servicio. Útil para autocompletar en la UI del integrador.

tax_rate   string     

Tasa de IVA aplicable: 01=1%, 02=2%, 04=4%, 08=13% (general), 10=Exenta. Mapea a CodigoTarifaIVA del XSD.

tax_code   string     

Código de impuesto para el campo CodigoImpuesto del XSD. Normalmente 01 (IVA).

is_merchandise   boolean     

true si el código corresponde a mercancía (afecta los totales merc_gravadas/merc_exentas del ResumenFactura). false si es servicio (afecta serv_gravados/serv_exentos).

is_medicine   boolean     

true si el código requiere el campo FormaFarmaceutica obligatorio en la LineaDetalle. Solo aplica a productos farmacéuticos registrados.

meta   object     
current_page   integer     

Página actual.

per_page   integer     

Resultados por página.

has_more   boolean     

true si hay más páginas disponibles. CABYS usa simplePaginate (sin COUNT total) por rendimiento con 20K+ registros.

Buscar actividad economica (CIIU).

requires authentication

Busqueda paginada en el catalogo de actividades economicas CIIU 4 (~800 codigos). El codigo de actividad es obligatorio en el campo CodigoActividadEmisor de todos los comprobantes electronicos.

Si q contiene solo digitos, busca por prefijo de codigo CIIU. Si contiene texto, busca por descripcion de la actividad.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/catalogs/activities?q=desarrollo+software&per_page=10&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/catalogs/activities"
);

const params = {
    "q": "desarrollo software",
    "per_page": "10",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/catalogs/activities';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'q' => 'desarrollo software',
            'per_page' => '10',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Busqueda por texto):


{
    "success": true,
    "data": [
        {
            "code": "6201.0",
            "description": "Actividades de programación informática"
        },
        {
            "code": "6202.0",
            "description": "Actividades de consultoría informática y gestión de instalaciones informáticas"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 25,
        "total": 2
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Request      

GET api/v1/public/catalogs/activities

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

q   string  optional    

Texto libre o prefijo numerico. Minimo 2 caracteres. Example: desarrollo software

per_page   integer  optional    

Resultados por pagina (1-100). Default: 25. Example: 10

page   integer  optional    

Pagina actual. Example: 1

Consultar ubicaciones (provincia, canton, distrito).

requires authentication

Consulta jerarquica de la division territorial de Costa Rica, necesaria para construir el nodo Ubicacion del emisor y receptor en el XML del comprobante.

Comportamiento jerarquico:

No usa paginacion: el resultado maximo es de aproximadamente 16 registros (distritos de un canton).

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/catalogs/locations?province=1&canton=01&q=escazu" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/catalogs/locations"
);

const params = {
    "province": "1",
    "canton": "01",
    "q": "escazu",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/catalogs/locations';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'province' => '1',
            'canton' => '01',
            'q' => 'escazu',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, 7 provincias (sin parametros)):


{
    "success": true,
    "data": [
        {
            "level": "province",
            "province_code": 1,
            "canton_code": null,
            "district_code": null,
            "name": "San José"
        },
        {
            "level": "province",
            "province_code": 2,
            "canton_code": null,
            "district_code": null,
            "name": "Alajuela"
        },
        {
            "level": "province",
            "province_code": 3,
            "canton_code": null,
            "district_code": null,
            "name": "Cartago"
        },
        {
            "level": "province",
            "province_code": 4,
            "canton_code": null,
            "district_code": null,
            "name": "Heredia"
        },
        {
            "level": "province",
            "province_code": 5,
            "canton_code": null,
            "district_code": null,
            "name": "Guanacaste"
        },
        {
            "level": "province",
            "province_code": 6,
            "canton_code": null,
            "district_code": null,
            "name": "Puntarenas"
        },
        {
            "level": "province",
            "province_code": 7,
            "canton_code": null,
            "district_code": null,
            "name": "Limón"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "count": 7
    }
}
 

Example response (200, Cantones de una provincia):


{
    "success": true,
    "data": [
        {
            "level": "canton",
            "province_code": 1,
            "canton_code": 1,
            "district_code": null,
            "name": "San José"
        },
        {
            "level": "canton",
            "province_code": 1,
            "canton_code": 2,
            "district_code": null,
            "name": "Escazú"
        },
        {
            "level": "canton",
            "province_code": 1,
            "canton_code": 3,
            "district_code": null,
            "name": "Desamparados"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "count": 20
    }
}
 

Request      

GET api/v1/public/catalogs/locations

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

province   string  optional    

Codigo de provincia (1-7) para obtener sus cantones. Example: 1

canton   string  optional    

Codigo de canton (01-20) para obtener sus distritos. Requiere province. Example: 01

q   string  optional    

Busqueda por nombre de ubicacion. Minimo 2 caracteres. Example: escazu

Catálogo de monedas activas (ISO 4217) para el selector de moneda en la emisión de comprobantes.

requires authentication

Las más usadas en Costa Rica (CRC, USD, EUR) se devuelven primero; el resto en orden alfabético por código. bccr_rate trae el tipo de cambio de referencia del BCCR cuando aplica (USD), para prellenar el campo de tipo de cambio en la UI; si es null, el usuario lo ingresa a mano.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/catalogs/currencies" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/catalogs/currencies"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/catalogs/currencies';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/catalogs/currencies

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
code   string     

Código ISO 4217 (3 letras). Example: USD

name   string     

Nombre de la moneda. Example: Dólar Americano

requires_bccr_rate   boolean     

Si exige tipo de cambio (≠ CRC).

bccr_rate   string|null     

Tipo de cambio de referencia BCCR. Example: 530.50000

Consulta de Contribuyentes

Consultar identificacion por numero.

requires authentication

Busca primero en cache (24 horas), luego en el API publico de Hacienda. Para cedulas fisicas de 9 digitos no encontradas en Hacienda, y planes que incluyen consulta de padron, intenta el Padron Electoral TSE como fuente alternativa de nombre verificado.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/taxpayer/3101234567" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/taxpayer/3101234567"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/taxpayer/3101234567';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Caso A | Contribuyente en Hacienda con TSE (plan business|integrator)):


{
    "success": true,
    "data": {
        "id_number": "101230456",
        "id_type": "01",
        "id_type_label": "Cédula Física",
        "name": "JUAN PÉREZ SOLÍS",
        "hacienda_registered": true,
        "regime": {
            "code": "02",
            "description": "Simplificado"
        },
        "situation": {
            "status": "Activo",
            "morphs_fijo": true
        },
        "activities": [
            {
                "code": "620100",
                "status": "A",
                "description": "Actividades de consultoría informática"
            }
        ],
        "cached": false,
        "cached_at": "2026-04-08T10:30:00-06:00",
        "tse": {
            "found": true,
            "nombre_completo": "JUAN PÉREZ SOLÍS",
            "nombre": "JUAN",
            "apellido1": "PÉREZ",
            "apellido2": "SOLÍS",
            "cedula_vence": "2028-05-14",
            "cedula_vigente": true,
            "provincia": "San José",
            "canton": "Escazú",
            "distrito_codigo": "003",
            "padron_version": "2026-04-01",
            "nota": "Datos del Padrón Electoral TSE (Código Electoral Art. 105).",
            "cached": false
        }
    },
    "message": "Contribuyente encontrado.",
    "errors": null
}
 

Example response (200, Caso B | Consumidor final: no en Hacienda pero sí en padrón TSE (plan business|integrator)):


{
    "success": true,
    "data": {
        "id_number": "101230456",
        "id_type": "01",
        "id_type_label": "Cédula Física",
        "name": "JUAN PÉREZ SOLÍS",
        "hacienda_registered": false,
        "regime": null,
        "situation": null,
        "activities": [],
        "cached": false,
        "cached_at": "2026-04-08T10:30:00-06:00",
        "tse": {
            "found": true,
            "nombre_completo": "JUAN PÉREZ SOLÍS",
            "nombre": "JUAN",
            "apellido1": "PÉREZ",
            "apellido2": "SOLÍS",
            "cedula_vence": "2028-05-14",
            "cedula_vigente": true,
            "provincia": "San José",
            "canton": "Escazú",
            "distrito_codigo": "003",
            "padron_version": "2026-04-01",
            "nota": "Datos del Padrón Electoral TSE (Código Electoral Art. 105).",
            "cached": false
        }
    },
    "message": "Identificación verificada en Padrón Electoral TSE. No inscrita como contribuyente ante Hacienda.",
    "errors": null
}
 

Example response (200, Cédula jurídica o plan sin consulta de padrón (solo Hacienda)):


{
    "success": true,
    "data": {
        "id_number": "3101234567",
        "id_type": "02",
        "id_type_label": "Cédula Jurídica",
        "name": "EMPRESA EJEMPLO S.A.",
        "hacienda_registered": true,
        "regime": {
            "code": "01",
            "description": "Tradicional"
        },
        "situation": {
            "status": "Activo",
            "morphs_fijo": true
        },
        "activities": [
            {
                "code": "620100",
                "status": "A",
                "description": "Actividades de consultoría informática"
            }
        ],
        "cached": false,
        "cached_at": "2026-04-08T10:30:00-06:00"
    },
    "message": "Contribuyente encontrado.",
    "errors": null
}
 

Example response (404, Caso C | No encontrado en Hacienda ni en padrón TSE):


{
    "success": false,
    "data": null,
    "message": "No se encontró la identificación '999999999' en el registro de Hacienda ni en el Padrón Electoral TSE.",
    "errors": null
}
 

Example response (502, Hacienda no disponible):


{
    "success": false,
    "data": null,
    "message": "El API público de Hacienda retornó un error interno. Intente nuevamente más tarde.",
    "errors": null
}
 

Example response (504, Timeout Hacienda):


{
    "success": false,
    "data": null,
    "message": "El API público de Hacienda no respondió dentro del tiempo límite. Intente nuevamente.",
    "errors": null
}
 

Request      

GET api/v1/public/taxpayer/{id_number}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id_number   string     

Número de identificación (9-12 dígitos). Example: 3101234567

Response

Response Fields

data   object     
id_number   string     

Número de identificación consultado (9-12 dígitos).

id_type   string     

Tipo de identificación: 01=Física, 02=Jurídica, 03=DIMEX, 04=NITE, 05=Extranjero, 06=No Contribuyente.

id_type_label   string     

Nombre legible del tipo de identificación.

name   string     

Nombre o razón social. En Caso A proviene del RUT de Hacienda. En Caso B proviene del Padrón Electoral TSE.

hacienda_registered   boolean     

true si la identificación está inscrita en el RUT de Hacienda (Caso A). false si el nombre proviene del TSE y no es contribuyente (Caso B).

regime   object     

Régimen tributario del contribuyente. null en Caso B (no inscrito en Hacienda).

code   string     

Código del régimen (ej. 01=Tradicional, 02=Simplificado).

description   string     

Descripción del régimen.

situation   object     

Situación del contribuyente ante Hacienda. null en Caso B.

status   string     

Estado ante Hacienda: Activo, Inactivo, Moroso, etc.

morphs_fijo   boolean     

Indicador de morphs fijo del contribuyente.

activities   object[]     

Actividades económicas CIIU inscritas ante Hacienda. Array vacío en Caso B. Usar solo las que tengan status=A (activas).

code   string     

Código CIIU de 6 dígitos. Mapea a CodigoActividadReceptor al emitir FEC (tipo 08).

status   string     

Estado de la actividad: A=Activa, I=Inactiva.

description   string     

Descripción de la actividad económica.

cached   boolean     

true si los datos provienen de la cache Redis (TTL 24h para Hacienda, 1h para TSE-only).

cached_at   string     

Fecha/hora en que se almacenaron en cache (ISO 8601). null si no están cacheados.

tse   object     

Datos del Padrón Electoral TSE. Solo presente cuando: (a) plan business/integrator + cédula física de 9 dígitos + encontrada en el padrón, o (b) Caso B (TSE es la fuente principal). Ausente (clave omitida del JSON, no null) cuando el plan no incluye TSE, la cédula no es física, o no se encontró en el padrón.

found   boolean     

true si la cédula fue encontrada en el padrón.

nombre_completo   string     

Nombre completo en mayúsculas tal como el TSE lo almacena.

nombre   string     

Primer nombre del titular.

apellido1   string     

Primer apellido.

apellido2   string     

Segundo apellido.

cedula_vence   string     

Fecha de vencimiento de la cédula (YYYY-MM-DD). No es fecha de nacimiento.

cedula_vigente   boolean     

true si la cédula no ha expirado. Cédula vencida no invalida el comprobante | Hacienda no verifica vigencia del receptor.

provincia   string     

Nombre de la provincia de inscripción electoral (no necesariamente domicilio actual).

canton   string     

Nombre del cantón de inscripción electoral.

distrito_codigo   string     

Código TSE del distrito (3 dígitos). Distinto al sistema de 2 dígitos de Hacienda.

padron_version   string     

Fecha de corte del padrón fuente (YYYY-MM-DD). El TSE actualiza mensualmente.

nota   string     

Nota legal sobre la fuente de los datos.

cached   boolean     

true si los datos TSE provienen de cache Redis (TTL 30 días).

platform   object     

Información de existencia en la plataforma Almendro. Solo presente cuando el plan del contributor permite gestionar clientes (max_managed_contributors > 1). Ausente para planes que no gestionan clientes.

exists   boolean     

true si la cédula ya tiene cuenta activa en Almendro.

is_managed   boolean     

true si la cuenta está gestionada por algún integrador. El integrador sabe si será "el primero" o si comparte gestión.

available_certificates   object     

Ambientes con certificado .p12 activo.

sandbox   boolean     

true si tiene certificado activo de sandbox.

production   boolean     

true si tiene certificado activo de producción.

Confirmación del Receptor

Confirmar o rechazar comprobante recibido.

requires authentication

Genera y envia un MensajeReceptor a Hacienda para aceptar (total o parcial) o rechazar un comprobante electronico recibido de un proveedor.

Valores del campo message:

Plazo: 8 dias habiles desde la emision del documento (art. 15 Reglamento). Vencido el plazo se considera aceptacion tacita y no se puede enviar MensajeReceptor.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/receiver/confirm" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: Identificador único de ESTA confirmación, generado por usted. Es" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"voucher_key\": \"50619032600310199999900100001010000000001112345678\",
    \"issuer_id_number\": \"3101999999\",
    \"doc_issued_at\": \"2026-03-18T10:00:00-06:00\",
    \"message\": \"1\",
    \"message_detail\": \"Comprobante aceptado conforme.\",
    \"total_tax\": 6500,
    \"activity_code\": \"6201.0\",
    \"tax_condition\": \"01\",
    \"tax_creditable_amount\": 6500,
    \"expense_applicable_amount\": 50000,
    \"total_invoice\": 56500
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/receiver/confirm"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "Identificador único de ESTA confirmación, generado por usted. Es",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "voucher_key": "50619032600310199999900100001010000000001112345678",
    "issuer_id_number": "3101999999",
    "doc_issued_at": "2026-03-18T10:00:00-06:00",
    "message": "1",
    "message_detail": "Comprobante aceptado conforme.",
    "total_tax": 6500,
    "activity_code": "6201.0",
    "tax_condition": "01",
    "tax_creditable_amount": 6500,
    "expense_applicable_amount": 50000,
    "total_invoice": 56500
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/receiver/confirm';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Idempotency-Key' => 'Identificador único de ESTA confirmación, generado por usted. Es',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'voucher_key' => '50619032600310199999900100001010000000001112345678',
            'issuer_id_number' => '3101999999',
            'doc_issued_at' => '2026-03-18T10:00:00-06:00',
            'message' => '1',
            'message_detail' => 'Comprobante aceptado conforme.',
            'total_tax' => 6500.0,
            'activity_code' => '6201.0',
            'tax_condition' => '01',
            'tax_creditable_amount' => 6500.0,
            'expense_applicable_amount' => 50000.0,
            'total_invoice' => 56500.0,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Aceptación enviada exitosamente):


{
    "success": true,
    "data": {
        "id": "019d867d-0030-7288-8ece-fd64da756030",
        "voucher_key": "50619032600310199999900100001010000000001112345678",
        "voucher_id": "019d867d-0031-7288-8ece-fd64da756031",
        "issuer_id_number": "3101999999",
        "doc_issued_at": "2026-03-26T08:00:00-06:00",
        "message": "1",
        "message_label": "Aceptado",
        "message_detail": null,
        "total_tax": "130000.00000",
        "activity_code": "620100",
        "tax_condition": "01",
        "tax_condition_label": "Genera crédito IVA",
        "tax_creditable_amount": "130000.00000",
        "expense_applicable_amount": null,
        "total_invoice": "1130000.00000",
        "receiver_id_number": "3101999999",
        "receiver_consecutive": "00100001050000000001",
        "status": "draft",
        "has_xml": true,
        "is_signed": true,
        "environment": "production",
        "deadline": {
            "business_days_elapsed": 2,
            "business_days_remaining": 6,
            "deadline_date": "2026-04-07",
            "is_within_deadline": true,
            "is_near_deadline": false,
            "is_overdue": false
        },
        "hacienda": {
            "status": null,
            "message": null,
            "sent_at": null,
            "processed_at": null
        },
        "issued_by": "019d867d-0032-7288-8ece-fd64da756032",
        "created_at": "2026-04-16T10:00:00-06:00",
        "updated_at": "2026-04-16T10:00:00-06:00"
    },
    "message": "Aceptado del comprobante [506...001] generada y validada correctamente. Pendiente de envío a Hacienda.",
    "errors": null
}
 

Example response (422, Plazo de 8 días hábiles vencido):


{
    "success": false,
    "data": null,
    "message": "El plazo de 8 días hábiles para confirmar/rechazar el comprobante ha vencido.",
    "errors": {
        "voucher_key": [
            "El plazo de 8 días hábiles ha vencido."
        ]
    }
}
 

Example response (422, Ya existe confirmación para este comprobante):


{
    "success": false,
    "data": null,
    "message": "Ya existe una confirmación/rechazo para este comprobante.",
    "errors": {
        "voucher_key": [
            "Ya existe una confirmación/rechazo para este comprobante."
        ]
    }
}
 

Request      

POST api/v1/public/receiver/confirm

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: Identificador único de ESTA confirmación, generado por usted. Es

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

voucher_key   string     

Clave del comprobante recibido (50 dígitos exactos). Estructura: [3 país][8 fecha][12 cédula][10 consecutivo][1 situación][8 seguridad][8 verificador]. Must match the regex /^\d{50}$/. Example: 50619032600310199999900100001010000000001112345678

issuer_id_number   string     

Número de cédula del emisor del comprobante (máx 20 chars). El campo value no puede tener más de 20 caracteres. Example: 3101999999

doc_issued_at   string     

Fecha de emisión del comprobante recibido (xs:dateTime). No puede ser futura. El campo value no es una fecha válida. El campo value debe ser una fecha anterior o igual a now. Example: 2026-03-18T10:00:00-06:00

message   string     

Tipo de confirmación. 1=Aceptado, 2=Aceptado Parcialmente, 3=Rechazado. Example: 1

Must be one of:
  • 1
  • 2
  • 3
message_detail   string     

Detalle del mensaje (1-160 chars). Motivo de aceptación o rechazo. El campo value debe tener al menos 1 caracteres. El campo value no puede tener más de 160 caracteres. Example: Comprobante aceptado conforme.

total_tax   number  optional    

Monto total de impuesto del comprobante (Decimal 18,5). Condicional: obligatorio si el comprobante tiene impuestos. El campo value debe ser al menos 0. Example: 6500

activity_code   string  optional    

Código CIIU del receptor (6 dígitos). Obligatorio si tax_condition05. Prohibido si tax_condition = 05. Must match the regex /^(\d{4}.\d|\d{6})$/. Example: 6201.0

tax_condition   string  optional    

Condición de impuesto del receptor. 01=Crédito IVA general, 02=Crédito parcial, 03=Bienes de capital, 04=Gasto sin crédito, 05=Proporcionalidad. Example: 01

Must be one of:
  • 1
  • 2
  • 3
  • 4
  • 5
tax_creditable_amount   number  optional    

Monto total de impuesto acreditable (Decimal 18,5). Obligatorio si tax_condition05. El campo value debe ser al menos 0. Example: 6500

expense_applicable_amount   number  optional    

Monto total de gasto aplicable (Decimal 18,5). Obligatorio si tax_condition05. El campo value debe ser al menos 0. Example: 50000

total_invoice   number     

Monto total del comprobante (Decimal 18,5). Siempre obligatorio. El campo value debe ser al menos 0. Example: 56500

voucher_id   integer  optional    

ID interno del comprobante en el sistema (opcional). Referencia para tracking. The id of an existing record in the vouchers table.

Response

Response Fields

data   object     
id   string     

UUID del MensajeReceptor generado.

voucher_key   string     

Clave de 50 dígitos del comprobante al que se responde.

voucher_id   string     

UUID interno del voucher en Almendro. null si el comprobante no fue emitido por este sistema.

issuer_id_number   string     

Cédula del emisor del comprobante original (NumeroCedulaEmisor del MensajeReceptor XSD).

doc_issued_at   string     

Fecha de emisión del documento original (ISO 8601). Usada para calcular el plazo de 8 días hábiles.

message   string     

Código del mensaje: 1=Aceptado, 2=Aceptado Parcialmente, 3=Rechazado.

message_label   string     

Nombre legible del tipo de mensaje en español.

message_detail   string     

Detalle o razón del rechazo/aceptación parcial. null si fue aceptación total.

total_tax   string     

MontoTotalImpuesto del comprobante (Decimal 18,5). Obligatorio cuando message es 1 o 2.

activity_code   string     

Código de actividad económica CIIU (6 dígitos). Obligatorio cuando message es 1 o 2.

tax_condition   string     

Código de condición de impuesto: 01=Crédito IVA general, 02=Crédito parcial, 03=Bienes de capital, 04=Gasto sin crédito, 05=Proporcionalidad.

tax_condition_label   string     

Nombre legible de la condición de impuesto.

tax_creditable_amount   string     

MontoTotalImpuestoAcreditar (Decimal 18,5). Monto de IVA que el receptor puede acreditar. null si no aplica.

expense_applicable_amount   string     

MontoTotalDeGastoAplicable (Decimal 18,5). Monto del gasto aplicable. null si no aplica.

total_invoice   string     

TotalFactura del comprobante original (Decimal 18,5).

receiver_id_number   string     

Cédula del receptor (quien emite este MensajeReceptor).

receiver_consecutive   string     

Consecutivo de 20 dígitos del receptor (tipo 05/06/07 según el mensaje).

status   string     

Estado del envío: draft (generado, pendiente de envío), sent, accepted, rejected, error.

has_xml   boolean     

true si el XML del MensajeReceptor fue generado.

is_signed   boolean     

true si el XML fue firmado con XAdES-EPES.

environment   string     

Ambiente: production o sandbox.

deadline   object     

Información del plazo de 8 días hábiles (art. 15 Reglamento). Calculado considerando feriados de CR.

business_days_elapsed   integer     

Días hábiles transcurridos desde la emisión del comprobante.

business_days_remaining   integer     

Días hábiles restantes para responder. 0 si venció.

deadline_date   string     

Fecha límite para responder (YYYY-MM-DD).

is_within_deadline   boolean     

true si aún está dentro del plazo.

is_near_deadline   boolean     

true si quedan 2 días hábiles o menos.

is_overdue   boolean     

true si el plazo venció.

hacienda   object     

Respuesta de Hacienda al MensajeReceptor.

status   string     

Estado: aceptado, rechazado, o null si no procesado aún.

message   string     

Detalle del mensaje de Hacienda. null si pendiente.

sent_at   string     

Fecha/hora de envío a Hacienda (ISO 8601). null si no enviado.

processed_at   string     

Fecha/hora de procesamiento por Hacienda (ISO 8601). null si pendiente.

issued_by   string     

UUID del usuario que generó el MensajeReceptor.

created_at   string     

Fecha de creación del registro (ISO 8601).

updated_at   string     

Fecha de última actualización (ISO 8601).

Buzón de Recibidos

Listar comprobantes del buzón de recibidos

requires authentication

Devuelve, paginados, los comprobantes que el contribuyente ha recibido como receptor: tanto los emitidos por otros usuarios de Almendro como los cargados manualmente (XML de otro facturador). Es un listado de solo lectura | la aceptación o rechazo se hace por el flujo de POST /receiver/confirm (MensajeReceptor).

Cada fila expone solo la información pública del documento (Art. 16): emisor, clave, tipo, fecha, moneda y totales, junto al plazo de respuesta (8 días hábiles), el estado de respuesta del receptor y el origen del comprobante en el buzón.

La búsqueda por texto (search) filtra por clave, nombre del emisor o número de identificación del emisor.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/inbox?search=3101123456&per_page=20&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/inbox"
);

const params = {
    "search": "3101123456",
    "per_page": "20",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/inbox';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'search' => '3101123456',
            'per_page' => '20',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado paginado):


{
    "success": true,
    "data": [
        {
            "voucher_key": "50601012600310112345600100001010000000001199999999",
            "consecutive_number": "00100001010000000001",
            "voucher_type": "01",
            "voucher_type_label": "Factura Electrónica",
            "issued_at": "2026-06-01T10:00:00-06:00",
            "currency_code": "CRC",
            "emisor": {
                "name": "PROVEEDOR EJEMPLO S.A.",
                "id_number": "3101123456"
            },
            "alias": "Hosting junio",
            "notes": "Pagado con tarjeta corporativa",
            "xsd_valid": null,
            "totals": {
                "total_impuesto": "1300.00000",
                "total_comprobante": "11300.00000"
            },
            "deadline": {
                "deadline_at": "2026-06-11",
                "remaining_business_days": 5,
                "overdue": false
            },
            "status": "accepted",
            "environment": "prod",
            "source": "almendro",
            "source_label": "Emitido en Almendro",
            "response_status": "pending",
            "response_status_label": "Pendiente",
            "is_automatic": false,
            "confirmation": {
                "confirmed": false,
                "message": null,
                "message_label": null,
                "status": null
            },
            "line_items": null
        }
    ],
    "message": "Comprobantes recibidos (buzón).",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 3,
        "per_page": 20,
        "total": 45,
        "from": 1,
        "to": 20
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": "..."
    }
}
 

Example response (200, Buzón vacío):


{
    "success": true,
    "data": [],
    "message": "Comprobantes recibidos (buzón).",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 20,
        "total": 0,
        "from": null,
        "to": null
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Request      

GET api/v1/public/inbox

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

search   string  optional    

Filtra por clave, nombre del emisor o número de identificación del emisor. Example: 3101123456

per_page   integer  optional    

Resultados por página (1-100). Default: 20. Example: 20

page   integer  optional    

Número de página. Example: 1

Cargar un comprobante externo al buzón

requires authentication

Sube el XML firmado de un comprobante (generado por cualquier facturador, dentro o fuera de Almendro) al buzón del receptor. El sistema parsea el XML, valida que el receptor del documento sea el contribuyente autenticado (la cédula del receptor debe coincidir con la del certificado .p12) y consulta su estado en Hacienda.

Si el comprobante ya estaba en el buzón, la carga refresca su estado en vez de duplicarlo.

Envíe el XML de una de estas dos formas (al menos una es obligatoria):

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/inbox/upload" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"xml\": \"<FacturaElectronica>...<\\/FacturaElectronica>\",
    \"source\": \"api_upload\",
    \"target_contributor_id\": \"a4855dc5-0acb-33c3-b921-f4291f719ca0\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/inbox/upload"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "xml": "<FacturaElectronica>...<\/FacturaElectronica>",
    "source": "api_upload",
    "target_contributor_id": "a4855dc5-0acb-33c3-b921-f4291f719ca0"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/inbox/upload';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'xml' => '<FacturaElectronica>...</FacturaElectronica>',
            'source' => 'api_upload',
            'target_contributor_id' => 'a4855dc5-0acb-33c3-b921-f4291f719ca0',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Comprobante cargado):


{
    "success": true,
    "data": {
        "voucher_key": "50601012600310112345600100001010000000001199999999",
        "consecutive_number": "00100001010000000001",
        "voucher_type": "01",
        "voucher_type_label": "Factura Electrónica",
        "issued_at": "2026-06-01T10:00:00-06:00",
        "currency_code": "CRC",
        "emisor": {
            "name": "PROVEEDOR EJEMPLO S.A.",
            "id_number": "3101123456"
        },
        "alias": null,
        "notes": null,
        "xsd_valid": true,
        "totals": {
            "total_impuesto": "1300.00000",
            "total_comprobante": "11300.00000"
        },
        "deadline": {
            "deadline_at": "2026-06-11",
            "remaining_business_days": 5,
            "overdue": false
        },
        "status": "accepted",
        "environment": "prod",
        "source": "api_upload",
        "source_label": "Cargado por API",
        "response_status": "pending",
        "response_status_label": "Pendiente",
        "is_automatic": false,
        "confirmation": {
            "confirmed": false,
            "message": null,
            "message_label": null,
            "status": null
        },
        "line_items": null
    },
    "message": "Comprobante cargado en el buzón.",
    "errors": null
}
 

Example response (422, Falta el XML):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "xml": [
            "Debe enviar el comprobante en el campo \"xml\" (string) o \"xml_file\" (archivo)."
        ]
    }
}
 

Example response (422, No está a nombre del contribuyente):


{
    "success": false,
    "data": null,
    "message": "El comprobante no está a nombre de su contribuyente.",
    "errors": {
        "xml": [
            "El comprobante no está a nombre de su contribuyente."
        ]
    }
}
 

Example response (422, XML inválido o no respondible):


{
    "success": false,
    "data": null,
    "message": "No se pudo procesar el XML del comprobante.",
    "errors": {
        "xml": [
            "No se pudo procesar el XML del comprobante."
        ]
    }
}
 

Request      

POST api/v1/public/inbox/upload

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

xml   string  optional    

El XML firmado del comprobante como texto. Obligatorio si no envía xml_file. Example: <FacturaElectronica>...</FacturaElectronica>

xml_file   file  optional    

Archivo .xml del comprobante (máximo 5 MB). Obligatorio si no envía xml.

source   string  optional    

Origen de la carga. Valores: gui_upload (portal web), api_upload (integración). Default: api_upload. Example: api_upload

target_contributor_id   string  optional    

El campo value debe ser un UUID válido. Example: a4855dc5-0acb-33c3-b921-f4291f719ca0

Cargar comprobantes externos en lote

requires authentication

Carga masiva al buzón: procesa cada comprobante de forma independiente, de modo que un XML inválido o ajeno no aborta el lote | el resultado se reporta por ítem.

Envíe el lote de una o ambas de estas formas (al menos una es obligatoria):

El total de comprobantes procesados se limita a 50 (archivos sueltos + entradas del ZIP). Siempre responde HTTP 200 con el detalle por ítem y un resumen, aunque algunos o todos hayan fallado.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/inbox/upload-batch" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"source\": \"api_upload\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/inbox/upload-batch"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "source": "api_upload"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/inbox/upload-batch';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'source' => 'api_upload',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Lote procesado (con un fallo)):


{
    "success": true,
    "data": {
        "results": [
            {
                "name": "factura-001.xml",
                "status": "ok",
                "voucher_key": "50601012600310112345600100001010000000001199999999",
                "message": null
            },
            {
                "name": "factura-002.xml",
                "status": "error",
                "voucher_key": null,
                "message": "El comprobante no está a nombre de su contribuyente."
            }
        ],
        "summary": {
            "total": 2,
            "ok": 1,
            "failed": 1
        }
    },
    "message": "Lote procesado: 1 de 2 comprobante(s) cargado(s).",
    "errors": null
}
 

Example response (422, Lote vacío):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "files": [
            "Debe enviar al menos un archivo en \"files[]\" o un \"zip\" con comprobantes."
        ]
    }
}
 

Request      

POST api/v1/public/inbox/upload-batch

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

files   file[]  optional    

Archivos .xml de comprobantes (máximo 50 archivos, 5 MB cada uno). Obligatorio si no envía zip.

zip   file  optional    

Archivo .zip con comprobantes .xml (máximo 20 MB). Obligatorio si no envía files[].

source   string  optional    

Origen de la carga. Valores: gui_upload (portal web), api_upload (integración). Default: api_upload. Example: api_upload

Response

Response Fields

data   object     
results   object[]     

Resultado individual de cada comprobante del lote.

name   string     

Nombre del archivo XML procesado.

status   string     

Resultado del ítem: ok (cargado) o error (rechazado).

voucher_key   string     

Clave del comprobante cargado. null si el ítem falló.

message   string     

Mensaje de error del ítem. null si el ítem se cargó correctamente.

summary   object     

Resumen agregado del lote.

total   integer     

Total de comprobantes procesados.

ok   integer     

Cantidad cargada correctamente.

failed   integer     

Cantidad que falló.

Validar una clave contra Hacienda

requires authentication

Consulta directamente en Hacienda el estado de una clave (aceptado / rechazado), incluso de comprobantes emitidos por otros facturadores y que no estén en su buzón. Sirve para validar la factura de un proveedor antes de aceptarla.

La consulta usa el certificado activo del contribuyente para el ambiente en curso. Si no hay certificado activo para ese ambiente, responde HTTP 422. Si Hacienda no responde, responde HTTP 502.

El campo in_inbox indica si esa clave ya figura como recibida por el contribuyente (su cédula es la del receptor del comprobante).

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/inbox/lookup/50601012600310112345600100001010000000001199999999" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/inbox/lookup/50601012600310112345600100001010000000001199999999"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/inbox/lookup/50601012600310112345600100001010000000001199999999';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Clave encontrada en Hacienda):


{
    "success": true,
    "data": {
        "clave": "50601012600310112345600100001010000000001199999999",
        "found": true,
        "ind_estado": "aceptado",
        "detail": "Comprobante aceptado por Hacienda.",
        "in_inbox": false
    },
    "message": "Estado consultado en Hacienda.",
    "errors": null
}
 

Example response (200, Clave no registrada en Hacienda):


{
    "success": true,
    "data": {
        "clave": "50601012600310112345600100001010000000001199999999",
        "found": false,
        "ind_estado": null,
        "detail": null,
        "in_inbox": false
    },
    "message": "Hacienda no tiene registro de esta clave.",
    "errors": null
}
 

Example response (422, Sin certificado activo):


{
    "success": false,
    "data": null,
    "message": "No hay un certificado activo para consultar a Hacienda en este ambiente.",
    "errors": {
        "certificate": [
            "Falta el certificado de sandbox."
        ]
    }
}
 

Example response (502, Hacienda no disponible):


{
    "success": false,
    "data": null,
    "message": "No se pudo consultar a Hacienda: Connection timed out.",
    "errors": null
}
 

Request      

GET api/v1/public/inbox/lookup/{clave}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

clave   string     

Clave de 50 dígitos a consultar en Hacienda. Example: 50601012600310112345600100001010000000001199999999

Response

Response Fields

data   object     
clave   string     

La clave consultada (eco del parámetro de URL).

found   boolean     

true si Hacienda tiene registro de la clave.

ind_estado   string     

Indicador de estado devuelto por Hacienda (ej. aceptado, rechazado). null si no se encontró.

detail   string     

Detalle textual del estado devuelto por Hacienda. null si no se encontró.

in_inbox   boolean     

true si esa clave ya figura como comprobante recibido por el contribuyente.

Consultar un comprobante del buzón por clave

requires authentication

Devuelve el detalle de un comprobante recibido, identificado por su clave (50 dígitos). A diferencia del listado, el detalle incluye además las líneas del comprobante (line_items): provienen del comprobante interno cuando el emisor es de Almendro, o del snapshot parseado del XML cuando es un comprobante externo.

Solo se retornan comprobantes que están en el buzón del contribuyente autenticado. Si la clave no está en su buzón, responde HTTP 404.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/inbox/50601012600310112345600100001010000000001199999999" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/inbox/50601012600310112345600100001010000000001199999999"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/inbox/50601012600310112345600100001010000000001199999999';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Comprobante encontrado):


{
    "success": true,
    "data": {
        "voucher_key": "50601012600310112345600100001010000000001199999999",
        "consecutive_number": "00100001010000000001",
        "voucher_type": "01",
        "voucher_type_label": "Factura Electrónica",
        "issued_at": "2026-06-01T10:00:00-06:00",
        "currency_code": "CRC",
        "emisor": {
            "name": "PROVEEDOR EJEMPLO S.A.",
            "id_number": "3101123456"
        },
        "alias": "Hosting junio",
        "notes": "Pagado con tarjeta corporativa",
        "xsd_valid": null,
        "totals": {
            "total_impuesto": "1300.00000",
            "total_comprobante": "11300.00000"
        },
        "deadline": {
            "deadline_at": "2026-06-11",
            "remaining_business_days": 5,
            "overdue": false
        },
        "status": "accepted",
        "environment": "prod",
        "source": "almendro",
        "source_label": "Emitido en Almendro",
        "response_status": "pending",
        "response_status_label": "Pendiente",
        "is_automatic": false,
        "confirmation": {
            "confirmed": false,
            "message": null,
            "message_label": null,
            "status": null
        },
        "line_items": [
            {
                "line_number": 1,
                "detail": "Servicio de hosting mensual",
                "quantity": "1.00000",
                "unit_price": "10000.00000",
                "total_line_amount": "10000.00000"
            }
        ]
    },
    "message": "Detalle del comprobante recibido.",
    "errors": null
}
 

Example response (404, No está en el buzón):


{
    "success": false,
    "data": null,
    "message": "El comprobante no está en su buzón.",
    "errors": null
}
 

Request      

GET api/v1/public/inbox/{key}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

key   string     

Clave de 50 dígitos del comprobante recibido. Example: 50601012600310112345600100001010000000001199999999

Response

Response Fields

data   object     
voucher_key   string     

Clave de 50 dígitos del comprobante. Identificador único del documento ante Hacienda.

consecutive_number   string     

Consecutivo del comprobante. null si es externo y no se pudo resolver el comprobante interno.

voucher_type   string     

Tipo de comprobante: 01=Factura, 02=Nota Débito, 03=Nota Crédito, 04=Tiquete, 08=FEC, etc.

voucher_type_label   string     

Nombre legible del tipo de comprobante en español. null si el tipo no es reconocido.

issued_at   string     

Fecha de emisión del documento (ISO 8601).

currency_code   string     

Código de moneda del comprobante (ej. CRC, USD).

receiver_activity_code   string     

CodigoActividadReceptor declarado por el emisor (formato XXXX.X o 6 dígitos). Es la actividad que debe usarse como activity_code al responder con MensajeReceptor. null si el comprobante no lo trae: en ese caso el receptor elige su actividad.

emisor   object     

Datos públicos del emisor (dueño del documento).

name   string     

Nombre o razón social del emisor.

id_number   string     

Número de identificación del emisor.

alias   string     

Alias privado que el receptor le puso al comprobante. null si no tiene. No fiscal.

notes   string     

Observaciones internas privadas del receptor. null si no tiene. No fiscal.

xsd_valid   boolean     

Resultado de validar el XML cargado contra el XSD v4.4. null=no validado (internos), true=válido, false=no válido.

totals   object     

Totales del comprobante.

total_impuesto   string     

Total de impuestos del comprobante.

total_comprobante   string     

Total general del comprobante.

deadline   object     

Plazo de respuesta del receptor (8 días hábiles, Art. 15 del Reglamento).

deadline_at   string     

Fecha límite para responder (Y-m-d). null si no se pudo calcular.

remaining_business_days   integer     

Días hábiles restantes para responder. null si no aplica.

overdue   boolean     

true si ya venció el plazo de respuesta. null si no aplica.

status   string     

Estado fiscal del comprobante: el del comprobante interno (puede ser cancelled) o el ind-estado de Hacienda para los externos.

environment   string     

Ambiente del comprobante: sandbox o prod.

source   string     

Origen del comprobante en el buzón: almendro, gui_upload, api_upload, email_inbound (recibido por correo en facturas@almendro.cr).

source_label   string     

Nombre legible del origen del comprobante.

response_status   string     

Estado de respuesta del receptor: pending, accepted, partial, rejected, tacit.

response_status_label   string     

Nombre legible del estado de respuesta.

is_automatic   boolean     

true si la aceptación fue tácita por vencimiento del plazo (sin tratamiento manual).

confirmation   object     

Datos del MensajeReceptor de este receptor, si respondió.

confirmed   boolean     

true si el receptor ya resolvió (aceptó o rechazó) el comprobante.

message   string     

Código del mensaje de respuesta del receptor. null si no ha respondido.

message_label   string     

Nombre legible del mensaje de respuesta. null si no ha respondido.

status   string     

Estado del envío del MensajeReceptor a Hacienda. null si no ha respondido.

line_items   object[]     

Líneas de detalle del comprobante. Solo presente en el detalle (no en el listado). null si no hay líneas disponibles.

line_number   integer     

Número de línea.

detail   string     

Descripción de la línea.

quantity   string     

Cantidad de la línea.

unit_price   string     

Precio unitario de la línea.

total_line_amount   string     

Importe total de la línea.

Clientes del Integrador

Listar clientes gestionados por el integrador

requires authentication

Devuelve los contribuyentes-cliente que el integrador autenticado gestiona actualmente, con búsqueda por texto y paginación. Ordenado por fecha de creación descendente (los más recientes primero).

Solo los clientes con vínculo activo se incluyen. Si un cliente fue desvinculado, no aparece en este listado aunque su cuenta siga existiendo.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/my-contributors?search=cliente&per_page=15&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors"
);

const params = {
    "search": "cliente",
    "per_page": "15",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'search' => 'cliente',
            'per_page' => '15',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado paginado de clientes gestionados (cliente exclusivo)):


{
    "success": true,
    "data": [
        {
            "id": "019d867d-c001-7288-8ece-fd64da756c01",
            "legal_name": "CLIENTE DEL INTEGRADOR S.A.",
            "trade_name": "Cliente Shop",
            "id_type": "02",
            "id_type_label": "Cédula Jurídica",
            "id_number": "3101123456",
            "primary_email": "juan@cliente.cr",
            "is_active": true,
            "can_emit_from_portal": true,
            "plan_code": "free",
            "plan_name": "Gratis",
            "integrators_count": 1,
            "created_at": "2026-04-10T09:00:00-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 1,
        "from": 1,
        "to": 1
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Example response (200, Cliente compartido con otros integradores (N:N)):


{
    "success": true,
    "data": [
        {
            "id": "019d867d-c001-7288-8ece-fd64da756c02",
            "legal_name": "CLIENTE COMPARTIDO S.A.",
            "trade_name": null,
            "id_type": "02",
            "id_type_label": "Cédula Jurídica",
            "id_number": "3101999999",
            "primary_email": "contacto@compartido.cr",
            "is_active": true,
            "can_emit_from_portal": true,
            "plan_code": "pyme",
            "plan_name": "Pyme",
            "integrators_count": 3,
            "created_at": "2026-03-15T12:00:00-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 1,
        "from": 1,
        "to": 1
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Example response (200, Sin clientes gestionados):


{
    "success": true,
    "data": [],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 0,
        "from": null,
        "to": null
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Example response (403, Plan no es Integrador):


{
    "success": false,
    "data": null,
    "message": "Su plan no permite gestionar clientes. Actualice a Contadores o Integrador para gestionar la cartera de clientes.",
    "errors": null
}
 

Request      

GET api/v1/public/my-contributors

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

search   string  optional    

Búsqueda por razón social o número de identificación (mínimo 2 caracteres). Example: cliente

per_page   integer  optional    

Resultados por página (1-100). Default: 15. Example: 15

page   integer  optional    

Número de página. Example: 1

Response

Response Fields

data   object     
id   string     

UUID del contribuyente-cliente.

legal_name   string     

Razón social del cliente.

trade_name   string     

Nombre comercial. null si no tiene.

id_type   string     

Tipo de identificación: 01-04.

id_type_label   string     

Nombre legible del tipo.

id_number   string     

Número de identificación.

primary_email   string     

Correo principal del cliente.

is_active   boolean     

Estado de la cuenta del cliente.

can_emit_from_portal   boolean     

true si el plan del cliente permite emitir desde el portal web (determinado por el plan del cliente, no del integrador).

plan_code   string     

Código del plan del cliente: free, starter, pyme, etc.

plan_name   string     

Nombre comercial del plan del cliente.

integrators_count   integer     

Cantidad de integradores activos que gestionan a este cliente. 1 = exclusivo, >1 = compartido (badge "Compartido" en UI). Solo se expone el conteo, no la identidad de otros integradores.

created_at   string     

Fecha de creación de la cuenta del cliente (ISO 8601).

Crear un cliente gestionado

requires authentication

Crea un nuevo contribuyente-cliente en el sistema y establece un vínculo de gestión con el integrador autenticado. El cliente recibe un magic link en el correo indicado para activar su cuenta.

El cliente queda con:

El cliente NO queda con:

Tras crear:

  1. El cliente recibe un email con el magic link.
  2. El cliente debe activar la cuenta en ≤ 72 horas.
  3. Si quiere firmar por él, solicite un grant con POST /access-requests.
Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/my-contributors" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"legal_name\": \"CLIENTE DEL INTEGRADOR S.A.\",
    \"trade_name\": \"Cliente Shop\",
    \"id_type\": \"02\",
    \"id_number\": \"3101123456\",
    \"email\": \"contacto@cliente.cr\",
    \"phone\": \"22001234\",
    \"economic_activities\": [
        \"6201.0\"
    ],
    \"province\": 1,
    \"canton\": 1,
    \"district\": 1,
    \"neighborhood\": \"San Pedro\",
    \"address\": \"200m norte del parque central\",
    \"owner_name\": \"Juan Cliente\",
    \"owner_email\": \"juan@cliente.cr\",
    \"phone_country_code\": 506,
    \"emails\": [
        \"contacto@cliente.cr\"
    ]
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "legal_name": "CLIENTE DEL INTEGRADOR S.A.",
    "trade_name": "Cliente Shop",
    "id_type": "02",
    "id_number": "3101123456",
    "email": "contacto@cliente.cr",
    "phone": "22001234",
    "economic_activities": [
        "6201.0"
    ],
    "province": 1,
    "canton": 1,
    "district": 1,
    "neighborhood": "San Pedro",
    "address": "200m norte del parque central",
    "owner_name": "Juan Cliente",
    "owner_email": "juan@cliente.cr",
    "phone_country_code": 506,
    "emails": [
        "contacto@cliente.cr"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'legal_name' => 'CLIENTE DEL INTEGRADOR S.A.',
            'trade_name' => 'Cliente Shop',
            'id_type' => '02',
            'id_number' => '3101123456',
            'email' => 'contacto@cliente.cr',
            'phone' => '22001234',
            'economic_activities' => [
                '6201.0',
            ],
            'province' => 1,
            'canton' => 1,
            'district' => 1,
            'neighborhood' => 'San Pedro',
            'address' => '200m norte del parque central',
            'owner_name' => 'Juan Cliente',
            'owner_email' => 'juan@cliente.cr',
            'phone_country_code' => 506,
            'emails' => [
                'contacto@cliente.cr',
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Cliente creado y magic link enviado):


{
    "success": true,
    "data": {
        "id": "019d867d-c001-7288-8ece-fd64da756c01",
        "id_type": "02",
        "id_number": "3101123456",
        "legal_name": "CLIENTE DEL INTEGRADOR S.A.",
        "commercial_name": "Cliente Shop",
        "emails": [
            "juan@cliente.cr"
        ],
        "economic_activities": [
            "6201.0"
        ],
        "province": 1,
        "canton": 1,
        "district": 1,
        "neighborhood": "San Pedro",
        "address": "200m norte del parque central",
        "phone": "22001234",
        "phone_country_code": 506,
        "is_active": true,
        "plan": {
            "code": "free",
            "name": "Gratis"
        },
        "has_active_grant": false,
        "created_at": "2026-04-16T10:00:00-06:00",
        "updated_at": "2026-04-16T10:00:00-06:00"
    },
    "message": "Cliente creado correctamente. Se envió email de bienvenida.",
    "errors": null
}
 

Example response (403, Plan no es Integrador):


{
    "success": false,
    "data": null,
    "message": "Su plan no permite gestionar clientes. Actualice a Contadores o Integrador para gestionar la cartera de clientes.",
    "errors": null
}
 

Example response (422, Límite del plan alcanzado):


{
    "success": false,
    "data": null,
    "message": "Ha alcanzado el máximo de clientes gestionados de su plan.",
    "errors": {
        "managed_contributors": [
            "Ha alcanzado el máximo de clientes gestionados de su plan."
        ]
    }
}
 

Example response (422, Correo del propietario ya registrado):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "owner_email": [
            "Este correo ya está registrado en el sistema."
        ]
    }
}
 

Request      

POST api/v1/public/my-contributors

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

legal_name   string     

Razón social o nombre legal del cliente (3-150 chars). Example: CLIENTE DEL INTEGRADOR S.A.

trade_name   string  optional    

Nombre comercial del cliente (3-80 chars). Example: Cliente Shop

id_type   string     

Tipo de identificación del cliente. Valores: 01 (Física), 02 (Jurídica), 03 (DIMEX), 04 (NITE). Example: 02

id_number   string     

Número de identificación del cliente. Example: 3101123456

email   string     

Correo del cliente. Se usa para la cuenta en el portal y envío de facturación. Único en el sistema. El campo value no es un correo electrónico válido. El campo value no puede tener más de 160 caracteres. Example: contacto@cliente.cr

phone   string  optional    

Teléfono de contacto (4-20 dígitos). Example: 22001234

economic_activities   string[]  optional    

Códigos CIIU en formato Hacienda XXXX.X.

province   integer  optional    

Código de provincia (1-7). Example: 1

canton   integer  optional    

Código de cantón (1-99). Example: 1

district   integer  optional    

Código de distrito (1-99). Example: 1

neighborhood   string  optional    

Barrio (5-50 chars). Example: San Pedro

address   string  optional    

Señas exactas (5-300 chars). Example: 200m norte del parque central

owner_name   string     

Nombre completo del usuario administrador del cliente. Example: Juan Cliente

owner_email   string     

Correo del administrador del cliente (recibe el magic link). Example: juan@cliente.cr

phone_country_code   integer  optional    

Código de país del teléfono. Default: 506. Example: 506

emails   string[]  optional    

Correos de contacto adicionales del cliente (máx 4).

Response

Response Fields

data   object     
id   string     

UUID del contribuyente-cliente creado.

id_type   string     

Tipo de identificación: 01=Física, 02=Jurídica, 03=DIMEX, 04=NITE.

id_number   string     

Número de identificación del cliente.

legal_name   string     

Razón social o nombre legal del cliente.

commercial_name   string     

Nombre comercial. null si no se envió.

emails   string[]     

Correos del cliente.

economic_activities   string[]     

Códigos CIIU en formato XXXX.X.

province   integer     

Código de provincia (1-7). null si no se envió.

canton   integer     

Código de cantón. null si no se envió.

district   integer     

Código de distrito. null si no se envió.

neighborhood   string     

Barrio. null si no se envió.

address   string     

Señas exactas. null si no se envió.

phone   string     

Teléfono de contacto. null si no se envió.

phone_country_code   integer     

Código de país del teléfono. Default: 506.

is_active   boolean     

true | el cliente se crea activo.

plan   object     

Plan asignado al cliente (siempre Gratis al crear).

code   string     

Código del plan: free.

name   string     

Nombre del plan: Gratis.

has_active_grant   boolean     

false al crear | el integrador debe solicitar un grant por separado vía POST /access-requests.

created_at   string     

Fecha de creación (ISO 8601).

updated_at   string     

Fecha de última actualización (ISO 8601).

Consultar un cliente gestionado

requires authentication

Devuelve el detalle completo del cliente indicado. Solo se retornan clientes con vínculo activo con el integrador autenticado. Si el UUID no corresponde a un cliente gestionado actualmente por usted, retorna HTTP 404.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Cliente encontrado):


{
    "success": true,
    "data": {
        "id": "019d867d-c001-7288-8ece-fd64da756c01",
        "id_type": "02",
        "id_number": "3101123456",
        "legal_name": "CLIENTE DEL INTEGRADOR S.A.",
        "commercial_name": "Cliente Shop",
        "emails": [
            "juan@cliente.cr"
        ],
        "economic_activities": [
            "6201.0"
        ],
        "province": 1,
        "canton": 1,
        "district": 1,
        "neighborhood": "San Pedro",
        "address": "200m norte del parque central",
        "phone": "22001234",
        "phone_country_code": 506,
        "is_active": true,
        "plan": {
            "code": "free",
            "name": "Gratis"
        },
        "has_active_grant": true,
        "active_grant_environment": "sandbox",
        "created_at": "2026-04-10T09:00:00-06:00",
        "updated_at": "2026-04-10T09:00:00-06:00"
    },
    "message": "",
    "errors": null
}
 

Example response (403, Plan no es Integrador):


{
    "success": false,
    "data": null,
    "message": "Su plan no permite gestionar clientes. Actualice a Contadores o Integrador para gestionar la cartera de clientes.",
    "errors": null
}
 

Example response (404, Cliente no encontrado o desvinculado):


{
    "success": false,
    "data": null,
    "message": "Cliente no encontrado.",
    "errors": null
}
 

Request      

GET api/v1/public/my-contributors/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

Actualizar relación con el cliente gestionado

requires authentication

Permite modificar únicamente campos de la relación de gestión entre el integrador y el cliente. Los datos propios del cliente (razón social, dirección, teléfono, etc.) no pueden ser modificados por el integrador | solo el cliente puede editarlos desde su propio portal o API (Art. 16 Reglamento).

Campo editable:

Campos bloqueados (retornan 403):

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"default_pdf_template_id\": 3,
    \"identifier_color\": \"#fEEeDb\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "default_pdf_template_id": 3,
    "identifier_color": "#fEEeDb"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'default_pdf_template_id' => 3,
            'identifier_color' => '#fEEeDb',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Relación actualizada):



 

Example response (403, Intento de modificar datos del cliente):


{
    "success": false,
    "data": null,
    "message": "El integrador no puede modificar los datos del cliente. Solo el cliente puede editar su información desde su propio portal o API. Campos bloqueados: legal_name, trade_name.",
    "errors": null
}
 

Request      

PUT api/v1/public/my-contributors/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente. Example: 019d867d-c001-7288-8ece-fd64da756c01

Body Parameters

default_pdf_template_id   integer  optional    

ID de la plantilla PDF. Example: 3

identifier_color   string  optional    

Must match the regex /^#[0-9A-Fa-f]{6}$/. Example: #fEEeDb

Desvincular al integrador del cliente

requires authentication

Rompe únicamente la relación de gestión entre usted y este cliente. El cliente conserva:

Efectos inmediatos de la desvinculación:

  1. La relación de gestión queda marcada como desvinculada.
  2. Todos los grants activos de este cliente hacia usted se revocan automáticamente (no podrá seguir firmando por él).
  3. Este cliente desaparecerá del listado GET /my-contributors.

No se puede usar este endpoint para desvincularse a sí mismo (es decir, pasar su propio UUID de integrador) | retorna HTTP 403.

Usos típicos: finalización de contrato con el cliente, transferencia del cliente a otro integrador, limpieza de clientes inactivos.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Cliente desvinculado):


{
    "success": true,
    "data": null,
    "message": "Cliente desvinculado correctamente. Se revocaron los accesos asociados.",
    "errors": null
}
 

Example response (403, Plan no es Integrador):


{
    "success": false,
    "data": null,
    "message": "Su plan no permite gestionar clientes. Actualice a Contadores o Integrador para gestionar la cartera de clientes.",
    "errors": null
}
 

Example response (403, Intento de auto-desvinculación):


{
    "success": false,
    "data": null,
    "message": "No puede desvincularse a sí mismo desde este endpoint.",
    "errors": null
}
 

Example response (404, Cliente no encontrado o ya desvinculado):


{
    "success": false,
    "data": null,
    "message": "Cliente no encontrado.",
    "errors": null
}
 

Request      

DELETE api/v1/public/my-contributors/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente a desvincular. Example: 019d867d-c001-7288-8ece-fd64da756c01

Listar los certificados digitales de un cliente gestionado

requires authentication

Devuelve el historial completo de certificados del cliente (activo + desactivados), ordenados por fecha de creación descendente. Los datos sensibles (contenido del .p12, contraseñas, PIN de Hacienda) nunca se incluyen en la respuesta.

La estructura es idéntica a GET /api/v1/public/certificates (grupo Certificados Digitales), pero opera sobre el cliente indicado en la URL.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/certificates" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/certificates"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/certificates';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado de certificados del cliente):


{
    "success": true,
    "data": [
        {
            "id": "019d867d-1111-7288-8ece-fd64da756001",
            "environment": "sandbox",
            "is_active": true,
            "is_expired": false,
            "days_remaining": 120,
            "valid_from": "2024-01-01T00:00:00-06:00",
            "valid_until": "2026-08-15T23:59:59-06:00",
            "certificate_subject": "CN=CLIENTE DEL INTEGRADOR S.A., serialNumber=3101123456",
            "certificate_serial": "0A1B2C3D4E5F",
            "created_at": "2026-04-09T10:00:00-06:00"
        }
    ],
    "message": "",
    "errors": null
}
 

Example response (200, Cliente sin certificados):


{
    "success": true,
    "data": [],
    "message": "",
    "errors": null
}
 

Example response (403, Plan no es Integrador):


{
    "success": false,
    "data": null,
    "message": "Su plan no permite gestionar clientes. Actualice a Contadores o Integrador para gestionar la cartera de clientes.",
    "errors": null
}
 

Example response (404, Cliente no encontrado o desvinculado):


{
    "success": false,
    "data": null,
    "message": "Cliente no encontrado o no pertenece a este integrador.",
    "errors": null
}
 

Request      

GET api/v1/public/my-contributors/{id}/certificates

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

Eliminar un certificado del cliente (siempre denegado)

requires authentication

Este endpoint siempre retorna HTTP 403 y no realiza ningún cambio. El integrador no puede eliminar certificados del cliente | el certificado es propiedad del contribuyente emisor y solo él puede desactivarlo desde su propio portal.

Si quiere dejar de usar el certificado del cliente:

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/certificates/019d867d-1111-7288-8ece-fd64da756001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/certificates/019d867d-1111-7288-8ece-fd64da756001"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/certificates/019d867d-1111-7288-8ece-fd64da756001';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (403, Operación siempre denegada):


{
    "success": false,
    "data": null,
    "message": "El integrador no puede eliminar certificados del cliente. El certificado es propiedad del contribuyente emisor. Si desea dejar de usar este certificado, revoque su acceso desde DELETE /api/v1/public/my-access/{grantId}.",
    "errors": null
}
 

Request      

DELETE api/v1/public/my-contributors/{id}/certificates/{certId}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

certId   string     

UUID del certificado (no relevante, siempre 403). Example: 019d867d-1111-7288-8ece-fd64da756001

Subir un certificado digital para el cliente

requires authentication

Carga el archivo .p12 del cliente desde la cuenta del integrador. El certificado queda vinculado al cliente (no al integrador): el integrador administra el .p12 en nombre del cliente pero el certificado pertenece al cliente.

Este endpoint es equivalente a POST /api/v1/public/certificates del grupo Certificados Digitales, pero opera sobre el cliente gestionado indicado en la URL. Consulte la guía de ese grupo para entender el formato del .p12, el PIN de Hacienda, etc.

Request: multipart/form-data (no JSON).

Ejemplo con curl:

curl -X POST "https://api.almendro.cr/api/v1/public/my-contributors/{id}/certificates" \
  -H "Authorization: Bearer {su_token_api}" \
  -F "p12_file=@/ruta/cert-del-cliente.p12" \
  -F "p12_password=contrasena-del-p12" \
  -F "hacienda_pin=1234" \
  -F "environment=sandbox"

Recuerde: cargar el certificado del cliente no le da automáticamente permiso de firmar por él. Necesita además un grant activo (ver grupo Certificados de terceros).

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/certificates" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: multipart/form-data" \
    --header "Accept: application/json" \
    --form "p12_password=ContrasenaDelP12"\
    --form "hacienda_pin=1234"\
    --form "environment=sandbox"\
    --form "p12_file=@/tmp/phpqot0fgk5vbs7bodOCQ1" 
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/certificates"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "multipart/form-data",
    "Accept": "application/json",
};

const body = new FormData();
body.append('p12_password', 'ContrasenaDelP12');
body.append('hacienda_pin', '1234');
body.append('environment', 'sandbox');
body.append('p12_file', document.querySelector('input[name="p12_file"]').files[0]);

fetch(url, {
    method: "POST",
    headers,
    body,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/certificates';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'multipart/form-data',
            'Accept' => 'application/json',
        ],
        'multipart' => [
            [
                'name' => 'p12_password',
                'contents' => 'ContrasenaDelP12'
            ],
            [
                'name' => 'hacienda_pin',
                'contents' => '1234'
            ],
            [
                'name' => 'environment',
                'contents' => 'sandbox'
            ],
            [
                'name' => 'p12_file',
                'contents' => fopen('/tmp/phpqot0fgk5vbs7bodOCQ1', 'r')
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Certificado del cliente registrado):


{
    "success": true,
    "data": {
        "id": "019d867d-3333-7288-8ece-fd64da756c03",
        "environment": "sandbox",
        "is_active": true,
        "is_expired": false,
        "days_remaining": 730,
        "valid_from": "2026-01-01T00:00:00-06:00",
        "valid_until": "2028-01-01T00:00:00-06:00",
        "certificate_subject": "CN=CLIENTE DEL INTEGRADOR S.A., serialNumber=3101123456",
        "certificate_serial": "0A1B2C3D4E5F",
        "created_at": "2026-04-16T10:00:00-06:00"
    },
    "message": "Certificado del cliente registrado y activado correctamente.",
    "errors": null
}
 

Example response (403, Plan no es Integrador):


{
    "success": false,
    "data": null,
    "message": "Su plan no permite gestionar clientes. Actualice a Contadores o Integrador para gestionar la cartera de clientes.",
    "errors": null
}
 

Example response (404, Cliente no encontrado o desvinculado):


{
    "success": false,
    "data": null,
    "message": "Cliente no encontrado.",
    "errors": null
}
 

Example response (422, Contraseña del .p12 incorrecta):


{
    "success": false,
    "data": null,
    "message": "No se pudo leer el certificado .p12. Verifique que la contraseña sea correcta y que el archivo sea un certificado digital válido.",
    "errors": {
        "p12_file": [
            "No se pudo leer el certificado .p12. Verifique que la contraseña sea correcta y que el archivo sea un certificado digital válido."
        ]
    }
}
 

Request      

POST api/v1/public/my-contributors/{id}/certificates

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: multipart/form-data

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

Body Parameters

p12_file   file     

Archivo .p12 o .pfx de firma digital emitida por BCCR al cliente. Máximo 5 MB. Example: /tmp/phpqot0fgk5vbs7bodOCQ1

p12_password   string     

Contraseña que protege el archivo .p12 (la que el cliente definió al generarlo). Example: ContrasenaDelP12

hacienda_pin   string     

PIN de Hacienda del cliente (asignado por ATV al registrarse como emisor). Example: 1234

environment   string     

Ambiente al que se asigna el certificado. Valores: sandbox o production. Example: sandbox

requires authentication

Genera un nuevo token de magic link y envía un correo de bienvenida al usuario propietario del cliente. Útil cuando el magic link original expiró (TTL 72 horas) y el cliente aún no activó su cuenta.

Restricciones:

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/resend-magic-link" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/resend-magic-link"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/resend-magic-link';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Magic link reenviado):


{
    "success": true,
    "data": null,
    "message": "Magic link reenviado al correo del cliente.",
    "errors": null
}
 

Example response (403, Plan no es Integrador):


{
    "success": false,
    "data": null,
    "message": "Su plan no permite gestionar clientes. Actualice a Contadores o Integrador para gestionar la cartera de clientes.",
    "errors": null
}
 

Example response (404, Cliente no encontrado):


{
    "success": false,
    "data": null,
    "message": "Cliente no encontrado.",
    "errors": null
}
 

Example response (422, Cliente ya activó su cuenta):


{
    "success": false,
    "data": null,
    "message": "El cliente ya estableció su contraseña. No se puede reenviar el magic link.",
    "errors": null
}
 

Listar las secuencias de consecutivos del cliente

requires authentication

Devuelve todas las secuencias (voucher_sequences) del cliente gestionado filtradas por la terminal asignada a este integrador. Cada secuencia corresponde a un tipo de comprobante (01–10).

¿Para qué sirve?

Al migrar un cliente desde otro facturador (Alegra, Gosocket, etc.), Almendro no tiene historial de los consecutivos previos. Si se emite desde la secuencia 1, Hacienda rechaza por "consecutivo duplicado". Este endpoint permite verificar en qué número va cada tipo antes de emitir, y el endpoint PUT permite ajustarlo.

Tipos de comprobante (01–10):

Código Tipo Abrev.
01 Factura Electrónica FE
02 Nota de Débito Electrónica ND
03 Nota de Crédito Electrónica NC
04 Tiquete Electrónico TE
05 Confirmación Aceptación Total CA
06 Confirmación Aceptación Parcial CP
07 Confirmación Rechazo CR
08 Factura Electrónica de Compra FEC
09 Factura Electrónica de Exportación FEE
10 Recibo Electrónico de Pago REP

Las secuencias se crean automáticamente al emitir el primer comprobante de cada tipo. Si nunca se ha emitido una FEC (tipo 08), no existirá secuencia para ella | eso es normal.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/sequences?include_all_terminals=&terminal_code=01001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/sequences"
);

const params = {
    "include_all_terminals": "0",
    "terminal_code": "01001",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/sequences';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'include_all_terminals' => '0',
            'terminal_code' => '01001',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Secuencias de la terminal del integrador):


{
    "success": true,
    "data": [
        {
            "id": 42,
            "local_code": "001",
            "terminal_code": "00002",
            "voucher_type": "01",
            "voucher_type_label": "Factura Electrónica",
            "voucher_type_abbr": "FE",
            "current_sequence": 9992,
            "next_sequence": 9993,
            "last_consecutive": "00100002010000009992",
            "next_consecutive": "00100002010000009993",
            "last_issued_at": "2026-04-20T14:30:00-06:00",
            "last_issued_key": null,
            "is_active": true,
            "is_near_rollover": false,
            "display_key": "001-00002-01"
        }
    ],
    "message": "",
    "errors": null,
    "terminal": "00002"
}
 

Example response (200, Sin secuencias (cliente recién integrado)):


{
    "success": true,
    "data": [],
    "message": "No hay secuencias creadas aún para la terminal 00002.",
    "errors": null,
    "terminal": "00002"
}
 

Example response (403, Plan no es Integrador):


{
    "success": false,
    "data": null,
    "message": "Su plan no permite gestionar clientes. Actualice a Contadores o Integrador para gestionar la cartera de clientes.",
    "errors": null
}
 

Example response (404, Cliente no encontrado):


{
    "success": false,
    "data": null,
    "message": "Cliente no encontrado.",
    "errors": null
}
 

Request      

GET api/v1/public/my-contributors/{id}/sequences

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

Query Parameters

include_all_terminals   boolean  optional    

Si true, incluye secuencias de TODAS las terminales del cliente (diagnóstico). Default: false. Example: false

terminal_code   string  optional    

Filtra por una caja específica (5 díg., modelo nuevo). Example: 01001

Inicializar consecutivo para un tipo de comprobante

requires authentication

Crea una secuencia con un valor inicial para un tipo de comprobante que aún no tiene secuencia en la terminal del integrador. Crítico para migración desde otro facturador.

Caso de uso: el cliente usaba Alegra y su última FE fue la 8475. Si Almendro emite desde 1, Hacienda rechaza por "consecutivo duplicado". Con este endpoint, setee current_sequence=8475 para el tipo 01 → el próximo comprobante usará 8476.

Importante: si la secuencia ya existe (porque ya se emitió al menos un comprobante de ese tipo), use PUT /sequences/{seqId} para ajustarla.

Seguridad: Requiere confirmación de contraseña.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/sequences" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"voucher_type\": \"01\",
    \"terminal_code\": \"01001\",
    \"current_sequence\": 8475,
    \"reason\": \"Migración desde Alegra | último consecutivo tipo 01 fue 8475.\",
    \"password\": \"mi_contraseña\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/sequences"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "voucher_type": "01",
    "terminal_code": "01001",
    "current_sequence": 8475,
    "reason": "Migración desde Alegra | último consecutivo tipo 01 fue 8475.",
    "password": "mi_contraseña"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/sequences';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'voucher_type' => '01',
            'terminal_code' => '01001',
            'current_sequence' => 8475,
            'reason' => 'Migración desde Alegra | último consecutivo tipo 01 fue 8475.',
            'password' => 'mi_contraseña',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Secuencia inicializada):


{
    "success": true,
    "data": {
        "id": 100,
        "local_code": "001",
        "terminal_code": "00002",
        "voucher_type": "01",
        "voucher_type_label": "Factura Electrónica",
        "voucher_type_abbr": "FE",
        "current_sequence": 8475,
        "next_sequence": 8476,
        "last_consecutive": "00100002010000008475",
        "next_consecutive": "00100002010000008476",
        "last_issued_at": null,
        "last_issued_key": null,
        "is_active": true,
        "is_near_rollover": false,
        "display_key": "001-00002-01"
    },
    "message": "Secuencia tipo 01 (FE) inicializada en 8475. Próximo comprobante usará 8476.",
    "errors": null
}
 

Example response (422, Secuencia ya existe):


{
    "success": false,
    "data": null,
    "message": "Ya existe una secuencia para el tipo 01 en la terminal 00002. Use PUT para ajustarla.",
    "errors": {
        "voucher_type": [
            "Ya existe. Use PUT /sequences/{id} para ajustar."
        ]
    }
}
 

Example response (422, Contraseña incorrecta):


{
    "success": false,
    "data": null,
    "message": "La contraseña es incorrecta.",
    "errors": {
        "password": [
            "La contraseña proporcionada no coincide con la del usuario autenticado."
        ]
    }
}
 

Request      

POST api/v1/public/my-contributors/{id}/sequences

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

Body Parameters

voucher_type   string     

Tipo de comprobante: 01 (FE), 02 (ND), 03 (NC), 04 (TE), 08 (FEC), 09 (FEE), 10 (REP). Example: 01

terminal_code   string  optional    

Caja (5 díg., modelo nuevo) sobre la que se fija el consecutivo. Debe ser una caja creada por este integrador para el cliente. Si se omite, usa la terminal por defecto del grant. Example: 01001

current_sequence   integer     

Último consecutivo emitido en el facturador anterior. Próximo = este + 1. Rango: 0–9,999,999,999. Example: 8475

reason   string     

Motivo (mín. 10 chars, bitácora Art. 6). Example: Migración desde Alegra | último consecutivo tipo 01 fue 8475.

password   string     

Contraseña actual del usuario. Example: mi_contraseña

Ajustar el consecutivo de una secuencia del cliente

requires authentication

Cambia manualmente el current_sequence de una secuencia. El próximo comprobante emitido usará current_sequence + 1.

Cuándo usar:

Seguridad: Requiere confirmación de contraseña.

⚠️ Setear un valor menor al actual puede generar consecutivos duplicados que Hacienda rechazará. Se permite pero se registra advertencia en la bitácora.

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/sequences/42" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"current_sequence\": 9992,
    \"reason\": \"Migración desde Alegra | último consecutivo tipo 01 fue 9992.\",
    \"password\": \"mi_contraseña\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/sequences/42"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "current_sequence": 9992,
    "reason": "Migración desde Alegra | último consecutivo tipo 01 fue 9992.",
    "password": "mi_contraseña"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/sequences/42';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'current_sequence' => 9992,
            'reason' => 'Migración desde Alegra | último consecutivo tipo 01 fue 9992.',
            'password' => 'mi_contraseña',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Secuencia ajustada):


{
    "success": true,
    "data": {
        "id": 42,
        "local_code": "001",
        "terminal_code": "00002",
        "voucher_type": "01",
        "voucher_type_label": "Factura Electrónica",
        "voucher_type_abbr": "FE",
        "current_sequence": 9992,
        "next_sequence": 9993,
        "last_consecutive": "00100002010000009992",
        "next_consecutive": "00100002010000009993",
        "last_issued_at": null,
        "last_issued_key": null,
        "is_active": true,
        "is_near_rollover": false,
        "display_key": "001-00002-01"
    },
    "message": "Consecutivo ajustado: tipo 01 (FE) actualizado de 0 a 9992. Próximo comprobante usará 9993.",
    "errors": null
}
 

Example response (403, Secuencia de otra terminal):


{
    "success": false,
    "data": null,
    "message": "Solo puede ajustar secuencias de su propia terminal (00002).",
    "errors": null
}
 

Example response (404, Secuencia no encontrada):


{
    "success": false,
    "data": null,
    "message": "Secuencia no encontrada para este cliente.",
    "errors": null
}
 

Example response (422, Contraseña incorrecta):


{
    "success": false,
    "data": null,
    "message": "La contraseña es incorrecta.",
    "errors": {
        "password": [
            "La contraseña proporcionada no coincide con la del usuario autenticado."
        ]
    }
}
 

Request      

PUT api/v1/public/my-contributors/{id}/sequences/{seqId}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

seqId   integer     

ID de la secuencia (del listado). Example: 42

Body Parameters

current_sequence   integer     

Último consecutivo emitido. Próximo = este + 1. Rango: 0–9,999,999,999. Example: 9992

reason   string     

Motivo del ajuste (mín. 10 chars, bitácora art. 6). Example: Migración desde Alegra | último consecutivo tipo 01 fue 9992.

password   string     

Contraseña actual del usuario (confirmación de seguridad). Example: mi_contraseña

Listar sucursales y cajas de un cliente gestionado

requires authentication

Devuelve las sucursales (branches) + cajas (terminals) que ESTE integrador creó para el cliente. Las cajas usan el slot del integrador en el terminal_code (= [slot 2 díg.][caja 3 díg.]).

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Sucursales del cliente):


{
    "success": true,
    "data": [
        {
            "id": "019ee0-...",
            "local_code": "001",
            "name": "Casa Matriz",
            "is_direct": false,
            "terminals": [
                {
                    "terminal_code": "01001",
                    "slot": "01",
                    "box": "001",
                    "name": "Caja 1"
                }
            ]
        }
    ],
    "message": "Sucursales del cliente gestionado.",
    "errors": null
}
 

Example response (404, Cliente no gestionado):


{
    "success": false,
    "data": null,
    "message": "Cliente no encontrado.",
    "errors": null
}
 

Request      

GET api/v1/public/my-contributors/{id}/branches

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del contribuyente-cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

Crear una sucursal para un cliente gestionado

requires authentication

Crea una sucursal del cliente gestionada por el integrador y su primera caja (en el slot del integrador). Consume cupo del POZO del plan del INTEGRADOR (max_branches), repartido entre todos sus clientes.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"local_code\": \"001\",
    \"name\": \"Casa Matriz\",
    \"box_name\": \"Caja 1\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "local_code": "001",
    "name": "Casa Matriz",
    "box_name": "Caja 1"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'local_code' => '001',
            'name' => 'Casa Matriz',
            'box_name' => 'Caja 1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Sucursal creada (con su primera caja en el slot)):


{
    "success": true,
    "data": {
        "id": "019ee0-...",
        "local_code": "001",
        "name": "Casa Matriz",
        "is_direct": false,
        "terminals": [
            {
                "terminal_code": "01001",
                "slot": "01",
                "name": "Caja 1"
            }
        ]
    },
    "message": "Sucursal creada.",
    "errors": null
}
 

Example response (422, Pozo del integrador agotado):


{
    "success": false,
    "data": null,
    "message": "Alcanzó el pozo de 50 sucursal(es) de su plan. Actualice su plan para agregar más.",
    "errors": {
        "local_code": [
            "..."
        ]
    }
}
 

Request      

POST api/v1/public/my-contributors/{id}/branches

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

Body Parameters

local_code   string     

Código de sucursal Hacienda, 3 díg., distinto de 000. Example: 001

name   string     

Nombre de la sucursal (2-100 chars). Example: Casa Matriz

box_name   string  optional    

Nombre de la primera caja (2-100 chars). Default: "Caja 1". Example: Caja 1

Renombrar una sucursal de un cliente gestionado

requires authentication

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Sucursal Heredia\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Sucursal Heredia"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Sucursal Heredia',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Sucursal renombrada):


{
    "success": true,
    "data": {
        "id": "019ee0-...",
        "name": "Sucursal Heredia"
    },
    "message": "Sucursal actualizada.",
    "errors": null
}
 

Request      

PUT api/v1/public/my-contributors/{id}/branches/{branchId}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

branchId   string     

UUID de la sucursal. Example: 019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee

Body Parameters

name   string     

Nuevo nombre (2-100 chars). Example: Sucursal Heredia

Agregar una caja a una sucursal de un cliente gestionado

requires authentication

Crea una caja en la sucursal del cliente, en el slot del integrador (terminal_code = [slot][caja]). Respeta el tope de cajas por sucursal del plan del integrador.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"Caja 2\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "Caja 2"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'Caja 2',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Caja creada):


{
    "success": true,
    "data": {
        "id": "019ee0-...",
        "terminals": [
            {
                "terminal_code": "01002",
                "name": "Caja 2"
            }
        ]
    },
    "message": "Caja creada.",
    "errors": null
}
 

Example response (422, Tope de cajas alcanzado):


{
    "success": false,
    "data": null,
    "message": "Alcanzó el tope de 20 caja(s) por sucursal de su plan.",
    "errors": {
        "name": [
            "..."
        ]
    }
}
 

Request      

POST api/v1/public/my-contributors/{id}/branches/{branchId}/terminals

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

branchId   string     

UUID de la sucursal. Example: 019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee

Body Parameters

name   string     

Nombre de la caja (2-100 chars). Example: Caja 2

Renombrar una caja de un cliente gestionado

requires authentication

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"name\": \"POS 2\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "name": "POS 2"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'name' => 'POS 2',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Caja renombrada):


{
    "success": true,
    "data": {
        "id": "019ee0-...",
        "terminals": [
            {
                "terminal_code": "01001",
                "name": "POS 2"
            }
        ]
    },
    "message": "Caja actualizada.",
    "errors": null
}
 

Request      

PUT api/v1/public/my-contributors/{id}/branches/{branchId}/terminals/{tid}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

branchId   string     

UUID de la sucursal. Example: 019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee

tid   string     

UUID de la caja. Example: 019ee1bb-cccc-dddd-eeee-ffffffffffff

Body Parameters

name   string     

Nuevo nombre (2-100 chars). Example: POS 2

Eliminar una caja de un cliente gestionado

requires authentication

No se puede eliminar la última caja activa de la sucursal.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee/terminals/019ee1bb-cccc-dddd-eeee-ffffffffffff';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Caja eliminada):


{
    "success": true,
    "data": null,
    "message": "Caja eliminada.",
    "errors": null
}
 

Example response (409, Es la última caja de la sucursal):


{
    "success": false,
    "data": null,
    "message": "Debés mantener al menos una caja en la sucursal.",
    "errors": null
}
 

Request      

DELETE api/v1/public/my-contributors/{id}/branches/{branchId}/terminals/{tid}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

branchId   string     

UUID de la sucursal. Example: 019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee

tid   string     

UUID de la caja. Example: 019ee1bb-cccc-dddd-eeee-ffffffffffff

Eliminar una sucursal de un cliente gestionado

requires authentication

Borra la sucursal junto con sus cajas. No se puede eliminar la última sucursal que el integrador tiene para ese cliente.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/branches/019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Sucursal eliminada):


{
    "success": true,
    "data": null,
    "message": "Sucursal eliminada.",
    "errors": null
}
 

Example response (409, Es la última sucursal del cliente):


{
    "success": false,
    "data": null,
    "message": "Debés mantener al menos una sucursal para este cliente.",
    "errors": null
}
 

Request      

DELETE api/v1/public/my-contributors/{id}/branches/{branchId}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

branchId   string     

UUID de la sucursal. Example: 019ee0aa-bbbb-cccc-dddd-eeeeeeeeeeee

Cambiar la terminal asignada al integrador para este cliente

requires authentication

Modifica la assigned_terminal en la relación de gestión. La terminal ocupa la posición 04-08 del NumeroConsecutivo (20 dígitos).

Cuándo usar:

migrate_sequences:

Seguridad: Requiere confirmación de contraseña.

Las secuencias de la terminal anterior NO se eliminan | los vouchers ya emitidos las referencian.

Example request:
curl --request PUT \
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/terminal" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"assigned_terminal\": \"00003\",
    \"local_code\": \"001\",
    \"migrate_sequences\": true,
    \"reason\": \"Terminal 00002 comprometida con otro facturador.\",
    \"password\": \"mi_contraseña\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/terminal"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "assigned_terminal": "00003",
    "local_code": "001",
    "migrate_sequences": true,
    "reason": "Terminal 00002 comprometida con otro facturador.",
    "password": "mi_contraseña"
};

fetch(url, {
    method: "PUT",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/019d867d-c001-7288-8ece-fd64da756c01/terminal';
$response = $client->put(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'assigned_terminal' => '00003',
            'local_code' => '001',
            'migrate_sequences' => true,
            'reason' => 'Terminal 00002 comprometida con otro facturador.',
            'password' => 'mi_contraseña',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Terminal cambiada con migración):


{
    "success": true,
    "data": {
        "previous_terminal": "00002",
        "new_terminal": "00003",
        "sequences_migrated": 3,
        "sequences": [
            {
                "id": 100,
                "local_code": "001",
                "terminal_code": "00003",
                "voucher_type": "01",
                "voucher_type_label": "Factura Electrónica",
                "voucher_type_abbr": "FE",
                "current_sequence": 9992,
                "next_sequence": 9993,
                "last_consecutive": "00100003010000009992",
                "next_consecutive": "00100003010000009993",
                "last_issued_at": null,
                "last_issued_key": null,
                "is_active": true,
                "is_near_rollover": false,
                "display_key": "001-00003-01"
            }
        ]
    },
    "message": "Terminal cambiada de 00002 a 00003. Se migraron 3 secuencias.",
    "errors": null
}
 

Example response (200, Terminal cambiada sin migración):


{
    "success": true,
    "data": {
        "previous_terminal": "00002",
        "new_terminal": "00003",
        "sequences_migrated": 0,
        "sequences": []
    },
    "message": "Terminal cambiada de 00002 a 00003. Las secuencias se crearán al emitir.",
    "errors": null
}
 

Example response (422, Terminal colisiona con otro integrador):


{
    "success": false,
    "data": null,
    "message": "La terminal 00003 ya está asignada a otro integrador de este cliente.",
    "errors": {
        "assigned_terminal": [
            "La terminal 00003 ya está asignada a otro integrador de este cliente."
        ]
    }
}
 

Example response (422, Contraseña incorrecta):


{
    "success": false,
    "data": null,
    "message": "La contraseña es incorrecta.",
    "errors": {
        "password": [
            "La contraseña proporcionada no coincide con la del usuario autenticado."
        ]
    }
}
 

Request      

PUT api/v1/public/my-contributors/{id}/terminal

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID del cliente gestionado. Example: 019d867d-c001-7288-8ece-fd64da756c01

Body Parameters

assigned_terminal   string     

Nueva terminal de 5 dígitos (00002–99999). Example: 00003

local_code   string  optional    

Código de sucursal de 3 dígitos numéricos (posición 01-03 del NumeroConsecutivo, pág. 65 Anexos). Rango 001–999. 001 = casa matriz. Opcional | si no se envía, se mantiene el actual. Example: 001

migrate_sequences   boolean  optional    

Copiar secuencias de la terminal anterior. Default: false. Example: true

reason   string     

Motivo del cambio (mín. 10 chars, bitácora art. 6). Example: Terminal 00002 comprometida con otro facturador.

password   string     

Contraseña actual del usuario (confirmación de seguridad). Example: mi_contraseña

Mis Integradores

Listar los integradores que actualmente gestionan al contribuyente autenticado.

requires authentication

Devuelve las relaciones managed ACTIVAS (unlinked_at IS NULL) donde el tenant autenticado es el client_contributor_id. Cada entrada incluye el integrador, terminal asignada, grants activos por ambiente, retention_months_override y plantilla PDF default.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/my-integrators?per_page=15&page=1" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-integrators"
);

const params = {
    "per_page": "15",
    "page": "1",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-integrators';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'per_page' => '15',
            'page' => '1',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Integradores activos):


{
    "success": true,
    "data": [
        {
            "relationship_id": "019d870a-0001-7288-8ece-fd64da75a001",
            "integrator": {
                "id": "019d865a-1234-7000-a000-abcdef123456",
                "legal_name": "Sistemas Integrados S.A.",
                "id_number": "3101999999"
            },
            "assigned_terminal": "00002",
            "linked_at": "2026-04-10T09:00:00-06:00",
            "grants": [
                {
                    "environment": "sandbox",
                    "is_active": true
                }
            ]
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 1
    }
}
 

Example response (200, Sin integradores):


{
    "success": true,
    "data": [],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 0
    }
}
 

Request      

GET api/v1/public/my-integrators

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

per_page   integer  optional    

Resultados por página (1-100, default 15). Example: 15

page   integer  optional    

Número de página. Example: 1

Consultar detalle de un integrador específico que gestiona al cliente.

requires authentication

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/my-integrators/9c1b4e5a-..." \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-integrators/9c1b4e5a-..."
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-integrators/9c1b4e5a-...';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Detalle del integrador):


{
    "success": true,
    "data": {
        "relationship_id": "019d870a-0001-7288-8ece-fd64da75a001",
        "integrator": {
            "id": "019d865a-1234-7000-a000-abcdef123456",
            "legal_name": "Sistemas Integrados S.A.",
            "id_number": "3101999999",
            "id_type": "02",
            "emails": [
                "soporte@integrador.cr"
            ]
        },
        "assigned_terminal": "00002",
        "retention_months_override": null,
        "default_pdf_template": null,
        "linked_at": "2026-04-10T09:00:00-06:00",
        "grants": [
            {
                "id": "019d870b-0001-7288-8ece-fd64da75b001",
                "environment": "sandbox",
                "is_active": true,
                "certificate": {
                    "environment": "sandbox",
                    "is_active": true,
                    "valid_until": "2028-01-01T00:00:00-06:00"
                }
            }
        ]
    },
    "message": "",
    "errors": null
}
 

Example response (404, Integrador no encontrado):


{
    "success": false,
    "data": null,
    "message": "Integrador no encontrado.",
    "errors": null
}
 

Request      

GET api/v1/public/my-integrators/{integratorContributorId}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

integratorContributorId   string     

UUID del integrador. Example: 9c1b4e5a-...

Liberarse de un integrador específico.

requires authentication

El cliente rompe el vínculo managed con un integrador concreto. Efectos (ver UnlinkManagedRelationshipAction):

· ManagedRelationship.unlinked_at = now() con metadata de auditoría. · Cascade revoke de TODOS los grants activos del integrador sobre los .p12 del cliente (vía RevokeGrantAction, escenario GrantAutoRevoked → notifica al integrador). · Otros integradores del cliente NO se afectan (modelo N:N). · El contributor del cliente permanece intacto | NO se soft-deletea.

Irreversible: si ambas partes quieren retomar, el integrador debe iniciar una nueva IntegratorAccessRequest desde cero. La nueva relación recibirá una terminal distinta.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/my-integrators/9c1b4e5a-..." \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"reason\": \"Cambio de proveedor tecnológico.\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-integrators/9c1b4e5a-..."
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "reason": "Cambio de proveedor tecnológico."
};

fetch(url, {
    method: "DELETE",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-integrators/9c1b4e5a-...';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'reason' => 'Cambio de proveedor tecnológico.',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Integrador desvinculado):


{
    "success": true,
    "data": null,
    "message": "Se liberó del integrador. Los accesos asociados fueron revocados.",
    "errors": null
}
 

Example response (404, Integrador no encontrado):


{
    "success": false,
    "data": null,
    "message": "Integrador no encontrado.",
    "errors": null
}
 

Request      

DELETE api/v1/public/my-integrators/{integratorContributorId}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

integratorContributorId   string     

UUID del integrador del que se libera. Example: 9c1b4e5a-...

Body Parameters

reason   string  optional    

Motivo opcional (máx 500 chars). Example: Cambio de proveedor tecnológico.

Cambiar el ROL de un gestor sobre el cliente autenticado.

requires authentication

La información del cliente es del cliente: él decide qué ve cada gestor. · accountant (contador) → ve la facturación completa + recibidos. · integrator → solo ve las facturas que él mismo emitió.

Al pasar a integrator se retira el puente GUI (switch-contributor): los usuarios del gestor dejan de poder operar al cliente por la interfaz.

Example request:
curl --request PATCH \
    "https://fe.almendro.cr/api/v1/public/my-integrators/e507fb2b94-c9e62d073743f8140a658fd5f/role" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"role\": \"architecto\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-integrators/e507fb2b94-c9e62d073743f8140a658fd5f/role"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "role": "architecto"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-integrators/e507fb2b94-c9e62d073743f8140a658fd5f/role';
$response = $client->patch(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'role' => 'architecto',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

PATCH api/v1/public/my-integrators/{integratorContributorId}/role

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

integratorContributorId   string     

Example: e507fb2b94-c9e62d073743f8140a658fd5f

Body Parameters

role   string     

Example: architecto

Solicitudes de Acceso

Crear una solicitud de acceso hacia un cliente.

requires authentication

El integrador envia la cedula del cliente y los ambientes a los que desea acceder. Se crea una solicitud en estado pending y se notifica al cliente por email y notificacion en el portal para que acepte o rechace.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/access-requests" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"client_id_number\": \"3101000050\",
    \"environments\": [
        \"sandbox\"
    ],
    \"message\": \"Hola, somos su proveedor tecnológico y gustaría gestionar su facturación.\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/access-requests"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "client_id_number": "3101000050",
    "environments": [
        "sandbox"
    ],
    "message": "Hola, somos su proveedor tecnológico y gustaría gestionar su facturación."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/access-requests';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'client_id_number' => '3101000050',
            'environments' => [
                'sandbox',
            ],
            'message' => 'Hola, somos su proveedor tecnológico y gustaría gestionar su facturación.',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Solicitud creada):


{
    "success": true,
    "data": {
        "id": 1,
        "status": "pending",
        "status_label": "Pendiente",
        "status_color": "warning",
        "is_pending": true,
        "is_final": false,
        "resulted_in_grants": false,
        "was_fully_accepted": false,
        "can_be_responded": true,
        "can_be_cancelled": true,
        "requested_environments": [
            "sandbox",
            "production"
        ],
        "accepted_environments": null,
        "message": "Solicitamos acceso para integrar su facturación con nuestro POS.",
        "rejection_reason": null,
        "client": {
            "id": "019d867d-c001-7288-8ece-fd64da756c01",
            "legal_name": "Hotel Las Palmas S.A.",
            "commercial_name": "Las Palmas",
            "id_type": "02",
            "id_number": "3101456789",
            "display_name": "Las Palmas"
        },
        "integrator": {
            "id": "019d867d-0241-7288-8ece-fd64da75616d",
            "legal_name": "SistemasPOS de Costa Rica S.A.",
            "commercial_name": "SistemasPOS",
            "id_type": "02",
            "id_number": "3101999888",
            "display_name": "SistemasPOS"
        },
        "requested_at": "2026-04-16T10:00:00-06:00",
        "responded_at": null,
        "created_at": "2026-04-16T10:00:00-06:00"
    },
    "message": "Solicitud de acceso creada. El cliente recibirá un email y notificación en su portal.",
    "errors": null
}
 

Example response (422, Error de validación):


{
    "success": false,
    "data": null,
    "message": "Ya existe una solicitud pendiente para este cliente.",
    "errors": null
}
 

Request      

POST api/v1/public/access-requests

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

client_id_number   string     

Cédula del cliente al que se solicita acceso. Debe corresponder a un Contributor existente en la plataforma. Must match the regex /^[0-9A-Z-]+$/i. El campo value debe tener al menos 5 caracteres. El campo value no puede tener más de 20 caracteres. Example: 3101000050

environments   string[]     

Cada ambiente debe ser "sandbox" o "production".

Must be one of:
  • sandbox
  • production
message   string  optional    

Mensaje opcional al cliente (máx 500 caracteres). Se muestra en el email + dashboard al revisar la solicitud. El campo value no puede tener más de 500 caracteres. Example: Hola, somos su proveedor tecnológico y gustaría gestionar su facturación.

Response

Response Fields

data   object     
id   integer     

ID numérico de la solicitud de acceso.

status   string     

Estado actual: pending, accepted, partially_accepted, rejected, cancelled.

status_label   string     

Nombre legible del estado en español.

status_color   string     

Color sugerido para la UI: warning, success, danger, secondary.

is_pending   boolean     

true si la solicitud aún espera respuesta del cliente.

is_final   boolean     

true si la solicitud ya fue resuelta (aceptada, rechazada o cancelada).

resulted_in_grants   boolean     

true si la aceptación generó al menos un acceso (CertificateAccessGrant).

was_fully_accepted   boolean     

true si todos los ambientes solicitados fueron aceptados. false si fue parcial o rechazada.

can_be_responded   boolean     

true si el cliente aún puede aceptar/rechazar (estado pendiente).

can_be_cancelled   boolean     

true si el integrador puede cancelar (estado pendiente).

requested_environments   string[]     

Ambientes solicitados por el integrador: ["sandbox"], ["production"] o ["sandbox", "production"].

accepted_environments   string[]     

Ambientes aceptados por el cliente. null si pendiente o rechazada. Puede ser subconjunto de requested_environments (aceptación parcial).

message   string     

Mensaje opcional del integrador al cliente justificando la solicitud. null si no se envió.

rejection_reason   string     

Razón del rechazo proporcionada por el cliente. null si no aplica.

client   object     

Datos del cliente (receptor de la solicitud).

id   string     

UUID del contribuyente cliente.

legal_name   string     

Razón social del cliente.

commercial_name   string     

Nombre comercial. null si no tiene.

id_type   string     

Tipo de identificación: 01-06.

id_number   string     

Número de identificación.

display_name   string     

Nombre para mostrar (commercial_name o legal_name).

integrator   object     

Datos del integrador (emisor de la solicitud). Misma estructura que client.

requested_at   string     

Fecha/hora en que se creó la solicitud (ISO 8601).

responded_at   string     

Fecha/hora en que el cliente respondió (ISO 8601). null si pendiente.

created_at   string     

Fecha de creación del registro (ISO 8601).

grants_count   integer     

Cantidad de accesos (CertificateAccessGrant) generados al aceptar. Solo presente en solicitudes aceptadas.

Listar solicitudes de acceso enviadas por el integrador.

requires authentication

Retorna el historial completo de solicitudes enviadas por el integrador autenticado, incluyendo todos los estados (pendientes, aceptadas, rechazadas, canceladas). Paginacion de 15 por pagina.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/access-requests/sent?per_page=16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/access-requests/sent"
);

const params = {
    "per_page": "16",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/access-requests/sent';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'per_page' => '16',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Solicitudes enviadas por el integrador):


{
    "success": true,
    "data": [
        {
            "id": 1,
            "status": "accepted",
            "status_label": "Aceptada",
            "status_color": "success",
            "is_pending": false,
            "is_final": true,
            "resulted_in_grants": true,
            "was_fully_accepted": true,
            "can_be_responded": false,
            "can_be_cancelled": false,
            "requested_environments": [
                "sandbox",
                "production"
            ],
            "accepted_environments": [
                "sandbox",
                "production"
            ],
            "message": "Solicitamos acceso para integrar su facturación.",
            "rejection_reason": null,
            "client": {
                "id": "019d867d-c001-7288-8ece-fd64da756c01",
                "legal_name": "Hotel Las Palmas S.A.",
                "display_name": "Las Palmas",
                "id_type": "02",
                "id_number": "3101456789",
                "commercial_name": "Las Palmas"
            },
            "integrator": {
                "id": "019d867d-0241-7288-8ece-fd64da75616d",
                "legal_name": "SistemasPOS de Costa Rica S.A.",
                "display_name": "SistemasPOS",
                "id_type": "02",
                "id_number": "3101999888",
                "commercial_name": "SistemasPOS"
            },
            "requested_at": "2026-04-10T10:00:00-06:00",
            "responded_at": "2026-04-11T08:00:00-06:00",
            "created_at": "2026-04-10T10:00:00-06:00",
            "grants_count": 2
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 1
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Request      

GET api/v1/public/access-requests/sent

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filtrar por estado: pending, accepted, partially_accepted, rejected, cancelled.

per_page   integer  optional    

Resultados por pagina (1-100). Default: 15. Example: 16

Listar solicitudes de acceso recibidas por el cliente.

requires authentication

El cliente ve que integradores le han solicitado acceso a sus certificados digitales. Las solicitudes pendientes aparecen primero.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/access-requests/received?per_page=16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/access-requests/received"
);

const params = {
    "per_page": "16",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/access-requests/received';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'per_page' => '16',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Solicitudes recibidas por el cliente):


{
    "success": true,
    "data": [
        {
            "id": 2,
            "status": "pending",
            "status_label": "Pendiente",
            "status_color": "warning",
            "is_pending": true,
            "is_final": false,
            "resulted_in_grants": false,
            "was_fully_accepted": false,
            "can_be_responded": true,
            "can_be_cancelled": false,
            "requested_environments": [
                "sandbox"
            ],
            "accepted_environments": null,
            "message": "Necesitamos acceso para pruebas de integración.",
            "rejection_reason": null,
            "client": {
                "id": "019d867d-c001-7288-8ece-fd64da756c01",
                "legal_name": "Hotel Las Palmas S.A.",
                "display_name": "Las Palmas",
                "id_type": "02",
                "id_number": "3101456789",
                "commercial_name": "Las Palmas"
            },
            "integrator": {
                "id": "019d867d-0241-7288-8ece-fd64da75616d",
                "legal_name": "SistemasPOS de Costa Rica S.A.",
                "display_name": "SistemasPOS",
                "id_type": "02",
                "id_number": "3101999888",
                "commercial_name": "SistemasPOS"
            },
            "requested_at": "2026-04-15T14:00:00-06:00",
            "responded_at": null,
            "created_at": "2026-04-15T14:00:00-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 1
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Request      

GET api/v1/public/access-requests/received

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

status   string  optional    

Filtrar por estado.

per_page   integer  optional    

Resultados por pagina (1-100). Default: 15. Example: 16

Aceptar una solicitud de acceso (total o parcialmente).

requires authentication

El cliente elige que ambientes autoriza. Se crean los accesos correspondientes con terminal asignada. El integrador recibe notificacion por email y en su portal.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/access-requests/16/accept" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"rejection_reason\": \"Ya tenemos un proveedor asignado para esta tarea.\",
    \"accepted_environments\": [
        \"sandbox\"
    ]
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/access-requests/16/accept"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "rejection_reason": "Ya tenemos un proveedor asignado para esta tarea.",
    "accepted_environments": [
        "sandbox"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/access-requests/16/accept';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'rejection_reason' => 'Ya tenemos un proveedor asignado para esta tarea.',
            'accepted_environments' => [
                'sandbox',
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Solicitud aceptada completamente):


{
    "success": true,
    "data": {
        "id": 1,
        "status": "accepted",
        "status_label": "Aceptada",
        "status_color": "success",
        "is_pending": false,
        "is_final": true,
        "resulted_in_grants": true,
        "was_fully_accepted": true,
        "can_be_responded": false,
        "can_be_cancelled": false,
        "requested_environments": [
            "sandbox",
            "production"
        ],
        "accepted_environments": [
            "sandbox",
            "production"
        ],
        "message": "Solicitamos acceso para integrar su facturación.",
        "rejection_reason": null,
        "client": {
            "id": "019d867d-c001-7288-8ece-fd64da756c01",
            "display_name": "Las Palmas",
            "legal_name": "Hotel Las Palmas S.A.",
            "commercial_name": "Las Palmas",
            "id_type": "02",
            "id_number": "3101456789"
        },
        "integrator": {
            "id": "019d867d-0241-7288-8ece-fd64da75616d",
            "display_name": "SistemasPOS",
            "legal_name": "SistemasPOS de Costa Rica S.A.",
            "commercial_name": "SistemasPOS",
            "id_type": "02",
            "id_number": "3101999888"
        },
        "requested_at": "2026-04-10T10:00:00-06:00",
        "responded_at": "2026-04-16T11:00:00-06:00",
        "created_at": "2026-04-10T10:00:00-06:00",
        "grants_count": 2
    },
    "message": "Solicitud aceptada correctamente. El integrador recibirá notificación y email.",
    "errors": null
}
 

Example response (422, Error de validación):


{
    "success": false,
    "data": null,
    "message": "Solo se pueden responder solicitudes en estado pendiente.",
    "errors": null
}
 

Request      

POST api/v1/public/access-requests/{id}/accept

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

ID de la solicitud a aceptar. Example: 16

Body Parameters

rejection_reason   string  optional    

OPCIONAL en /reject. Motivo del rechazo (texto libre, máx 500 chars). IGNORADO en /accept. El campo value no puede tener más de 500 caracteres. Example: Ya tenemos un proveedor asignado para esta tarea.

accepted_environments   string[]  optional    

Cada ambiente debe ser "sandbox" o "production".

Rechazar una solicitud de acceso.

requires authentication

No se crean accesos. El integrador recibe notificacion con el motivo opcional del rechazo.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/access-requests/16/reject" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"rejection_reason\": \"Ya tenemos un proveedor asignado para esta tarea.\",
    \"accepted_environments\": [
        \"sandbox\"
    ]
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/access-requests/16/reject"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "rejection_reason": "Ya tenemos un proveedor asignado para esta tarea.",
    "accepted_environments": [
        "sandbox"
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/access-requests/16/reject';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'rejection_reason' => 'Ya tenemos un proveedor asignado para esta tarea.',
            'accepted_environments' => [
                'sandbox',
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Solicitud rechazada):


{
    "success": true,
    "data": {
        "id": 3,
        "status": "rejected",
        "status_label": "Rechazada",
        "status_color": "danger",
        "is_pending": false,
        "is_final": true,
        "resulted_in_grants": false,
        "was_fully_accepted": false,
        "can_be_responded": false,
        "can_be_cancelled": false,
        "requested_environments": [
            "sandbox",
            "production"
        ],
        "accepted_environments": null,
        "message": "Solicitamos acceso para integrar su facturación.",
        "rejection_reason": "Ya contamos con otro proveedor de integración.",
        "client": {
            "id": "019d867d-c001-7288-8ece-fd64da756c01",
            "display_name": "Las Palmas",
            "legal_name": "Hotel Las Palmas S.A.",
            "commercial_name": "Las Palmas",
            "id_type": "02",
            "id_number": "3101456789"
        },
        "integrator": {
            "id": "019d867d-0241-7288-8ece-fd64da75616d",
            "display_name": "SistemasPOS",
            "legal_name": "SistemasPOS de Costa Rica S.A.",
            "commercial_name": "SistemasPOS",
            "id_type": "02",
            "id_number": "3101999888"
        },
        "requested_at": "2026-04-10T10:00:00-06:00",
        "responded_at": "2026-04-16T12:00:00-06:00",
        "created_at": "2026-04-10T10:00:00-06:00"
    },
    "message": "Solicitud rechazada. El integrador recibirá notificación.",
    "errors": null
}
 

Request      

POST api/v1/public/access-requests/{id}/reject

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

ID de la solicitud a rechazar. Example: 16

Body Parameters

rejection_reason   string  optional    

OPCIONAL en /reject. Motivo del rechazo (texto libre, máx 500 chars). IGNORADO en /accept. El campo value no puede tener más de 500 caracteres. Example: Ya tenemos un proveedor asignado para esta tarea.

accepted_environments   string[]  optional    

Cada ambiente debe ser "sandbox" o "production".

Cancelar una solicitud de acceso antes de que el cliente responda.

requires authentication

Solo se puede cancelar si la solicitud esta en estado pending. El cliente recibe una notificacion informativa.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/access-requests/16/cancel" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/access-requests/16/cancel"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/access-requests/16/cancel';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Solicitud cancelada):


{
    "success": true,
    "data": {
        "id": 4,
        "status": "cancelled",
        "status_label": "Cancelada",
        "status_color": "secondary",
        "is_pending": false,
        "is_final": true,
        "resulted_in_grants": false,
        "was_fully_accepted": false,
        "can_be_responded": false,
        "can_be_cancelled": false,
        "requested_environments": [
            "sandbox"
        ],
        "accepted_environments": null,
        "message": null,
        "rejection_reason": null,
        "client": {
            "id": "019d867d-c001-7288-8ece-fd64da756c01",
            "display_name": "Las Palmas",
            "legal_name": "Hotel Las Palmas S.A.",
            "commercial_name": "Las Palmas",
            "id_type": "02",
            "id_number": "3101456789"
        },
        "integrator": {
            "id": "019d867d-0241-7288-8ece-fd64da75616d",
            "display_name": "SistemasPOS",
            "legal_name": "SistemasPOS de Costa Rica S.A.",
            "commercial_name": "SistemasPOS",
            "id_type": "02",
            "id_number": "3101999888"
        },
        "requested_at": "2026-04-14T09:00:00-06:00",
        "responded_at": "2026-04-16T13:00:00-06:00",
        "created_at": "2026-04-14T09:00:00-06:00"
    },
    "message": "Solicitud cancelada correctamente.",
    "errors": null
}
 

Example response (404, No encontrada):


{
    "success": false,
    "data": null,
    "message": "Solicitud no encontrada.",
    "errors": null
}
 

Example response (422, Estado no permite cancelación):


{
    "success": false,
    "data": null,
    "message": "La solicitud no puede cancelarse en su estado actual (accepted). Solo se pueden cancelar solicitudes pendientes.",
    "errors": null
}
 

Request      

POST api/v1/public/access-requests/{id}/cancel

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

ID de la solicitud a cancelar. Example: 16

Grants de Certificados

Listar los accesos otorgados sobre los certificados del cliente.

requires authentication

El cliente ve que integradores tienen permiso de firmar con sus certificados digitales y en que ambiente. Incluye accesos activos y revocados (historial completo). Los activos se muestran primero.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/certificate-grants?active=&per_page=16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/certificate-grants"
);

const params = {
    "active": "0",
    "per_page": "16",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/certificate-grants';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'active' => '0',
            'per_page' => '16',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Accesos del cliente):


{
    "success": true,
    "data": [
        {
            "id": 1,
            "assigned_terminal": "00002",
            "is_active": true,
            "is_revoked": false,
            "certificate": {
                "id": "019d867d-d001-7288-8ece-fd64da756d01",
                "environment": "sandbox",
                "is_active": true,
                "is_expired": false,
                "days_remaining": 1440,
                "valid_from": "2026-01-01T00:00:00-06:00",
                "valid_until": "2030-01-01T00:00:00-06:00"
            },
            "client": {
                "id": "019d867d-c001-7288-8ece-fd64da756c01",
                "legal_name": "Hotel Las Palmas S.A.",
                "commercial_name": "Las Palmas",
                "id_type": "02",
                "id_number": "3101456789",
                "display_name": "Las Palmas"
            },
            "integrator": {
                "id": "019d867d-0241-7288-8ece-fd64da75616d",
                "legal_name": "SistemasPOS de Costa Rica S.A.",
                "commercial_name": "SistemasPOS",
                "id_type": "02",
                "id_number": "3101999888",
                "display_name": "SistemasPOS"
            },
            "access_request_id": 1,
            "granted_at": "2026-04-11T08:00:00-06:00",
            "revoked_at": null,
            "revoke_reason": null,
            "revoked_by": null,
            "created_at": "2026-04-11T08:00:00-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 1
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Request      

GET api/v1/public/certificate-grants

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

active   boolean  optional    

Filtrar: true = solo activos, false = solo revocados. Omitir para ver todos. Example: false

environment   string  optional    

Filtrar por ambiente: sandbox o production.

per_page   integer  optional    

Resultados por pagina (1-100). Default: 15. Example: 16

Response

Response Fields

data   object     
id   integer     

ID numérico del grant de acceso.

assigned_terminal   string     

Terminal de 5 dígitos asignada al integrador para el consecutivo del cliente (posiciones 11-15 del NumeroConsecutivo del XSD). Garantiza que múltiples integradores no colisionen en la numeración.

is_active   boolean     

true si el acceso está vigente y el integrador puede firmar con este certificado.

is_revoked   boolean     

true si el acceso fue revocado (por el cliente, el integrador o automáticamente al desactivar el certificado).

certificate   object     

Datos del certificado al que se otorgó acceso.

id   string     

UUID del certificado.

environment   string     

Ambiente: sandbox o production.

is_active   boolean     

true si el certificado está activo para firmar.

is_expired   boolean     

true si el certificado X.509 venció.

days_remaining   integer     

Días restantes de vigencia. 0 si venció.

valid_from   string     

Inicio de vigencia del X.509 (ISO 8601).

valid_until   string     

Fin de vigencia del X.509 (ISO 8601).

client   object     

Datos del cliente (dueño del certificado). Campos: id, legal_name, commercial_name, id_type, id_number, display_name.

integrator   object     

Datos del integrador (receptor del acceso). Misma estructura que client.

access_request_id   integer     

ID de la solicitud de acceso que originó este grant.

granted_at   string     

Fecha/hora en que se otorgó el acceso (ISO 8601).

revoked_at   string     

Fecha/hora de revocación (ISO 8601). null si activo.

revoke_reason   string     

Motivo de la revocación. null si activo o sin motivo.

revoked_by   object     

Contribuyente que revocó el acceso. null si activo. Campos: id, legal_name, display_name.

created_at   string     

Fecha de creación del registro (ISO 8601).

Revocar el acceso de un integrador a un certificado.

requires authentication

El integrador pierde la capacidad de firmar comprobantes con este certificado. Los comprobantes previamente emitidos siguen siendo validos. El integrador recibe notificacion de la revocacion.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/certificate-grants/16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"reason\": \"architecto\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/certificate-grants/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "reason": "architecto"
};

fetch(url, {
    method: "DELETE",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/certificate-grants/16';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'reason' => 'architecto',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Acceso revocado):


{
    "success": true,
    "data": {
        "id": 1,
        "assigned_terminal": "00002",
        "is_active": false,
        "is_revoked": true,
        "certificate": {
            "id": "019d867d-d001-7288-8ece-fd64da756d01",
            "environment": "sandbox",
            "is_active": true,
            "is_expired": false,
            "days_remaining": 1440,
            "valid_from": "2026-01-01T00:00:00-06:00",
            "valid_until": "2030-01-01T00:00:00-06:00"
        },
        "client": {
            "id": "019d867d-c001-7288-8ece-fd64da756c01",
            "legal_name": "Hotel Las Palmas S.A.",
            "commercial_name": "Las Palmas",
            "id_type": "02",
            "id_number": "3101456789",
            "display_name": "Las Palmas"
        },
        "integrator": {
            "id": "019d867d-0241-7288-8ece-fd64da75616d",
            "legal_name": "SistemasPOS de Costa Rica S.A.",
            "commercial_name": "SistemasPOS",
            "id_type": "02",
            "id_number": "3101999888",
            "display_name": "SistemasPOS"
        },
        "access_request_id": 1,
        "granted_at": "2026-04-11T08:00:00-06:00",
        "revoked_at": "2026-04-16T14:00:00-06:00",
        "revoke_reason": "Cambio de proveedor de integración.",
        "revoked_by": {
            "id": "019d867d-c001-7288-8ece-fd64da756c01",
            "legal_name": "Hotel Las Palmas S.A.",
            "display_name": "Las Palmas"
        },
        "created_at": "2026-04-11T08:00:00-06:00"
    },
    "message": "Acceso revocado correctamente. El integrador ya no puede emitir con este certificado.",
    "errors": null
}
 

Example response (422, Grant ya revocado):


{
    "success": false,
    "data": null,
    "message": "Este acceso ya fue revocado anteriormente.",
    "errors": null
}
 

Request      

DELETE api/v1/public/certificate-grants/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

ID del grant a revocar. Example: 16

Body Parameters

reason   string  optional    

Motivo opcional de la revocacion (max 500 caracteres). Example: architecto

Listar los accesos recibidos por el integrador autenticado.

requires authentication

El integrador ve sobre que certificados de que clientes tiene acceso activo (o revocado, según filtro). Cada grant incluye el cert, el cliente, la terminal asignada y el ambiente.

Opcionalmente filtrable por client_id para obtener los grants del integrador sobre un cliente específico | útil en el perfil del managed client para mostrar la sección "Mi acceso".

Requiere plan INTEGRATOR. Retorna 403 para cualquier otro plan. Espeja la semántica de PublicManagedContributorController.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/my-access?active=&per_page=16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-access"
);

const params = {
    "active": "0",
    "per_page": "16",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-access';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'active' => '0',
            'per_page' => '16',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Accesos del integrador):


{
    "success": true,
    "data": [
        {
            "id": 1,
            "assigned_terminal": "00002",
            "is_active": true,
            "is_revoked": false,
            "certificate": {
                "id": "019d867d-d001-7288-8ece-fd64da756d01",
                "environment": "sandbox",
                "is_active": true,
                "is_expired": false,
                "days_remaining": 1440,
                "valid_from": "2026-01-01T00:00:00-06:00",
                "valid_until": "2030-01-01T00:00:00-06:00"
            },
            "client": {
                "id": "019d867d-c001-7288-8ece-fd64da756c01",
                "legal_name": "Hotel Las Palmas S.A.",
                "commercial_name": "Las Palmas",
                "id_type": "02",
                "id_number": "3101456789",
                "display_name": "Las Palmas"
            },
            "integrator": {
                "id": "019d867d-0241-7288-8ece-fd64da75616d",
                "legal_name": "SistemasPOS de Costa Rica S.A.",
                "commercial_name": "SistemasPOS",
                "id_type": "02",
                "id_number": "3101999888",
                "display_name": "SistemasPOS"
            },
            "access_request_id": 1,
            "granted_at": "2026-04-11T08:00:00-06:00",
            "revoked_at": null,
            "revoke_reason": null,
            "revoked_by": null,
            "created_at": "2026-04-11T08:00:00-06:00"
        }
    ],
    "message": "",
    "errors": null,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 1
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Example response (403, Plan no permite gestionar clientes):


{
    "success": false,
    "data": null,
    "message": "Su plan no permite gestionar clientes. Actualice a Contadores o Integrador para gestionar la cartera de clientes.",
    "errors": null
}
 

Request      

GET api/v1/public/my-access

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

client_id   string  optional    

UUID del cliente managed para filtrar grants hacia ese cliente.

active   boolean  optional    

Filtrar: true = solo activos, false = solo revocados. Omitir para ver todos. Example: false

environment   string  optional    

Filtrar por ambiente: sandbox o production.

per_page   integer  optional    

Resultados por pagina (1-100). Default: 15. Example: 16

El integrador renuncia voluntariamente a un acceso que recibio.

requires authentication

Util cuando el integrador deja de gestionar al cliente y quiere limpiar su listado de accesos activos. El cliente es notificado de la renuncia.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/my-access/16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"reason\": \"architecto\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-access/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "reason": "architecto"
};

fetch(url, {
    method: "DELETE",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-access/16';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'reason' => 'architecto',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Renuncia exitosa):


{
    "success": true,
    "data": {
        "id": 3,
        "assigned_terminal": "00003",
        "is_active": false,
        "is_revoked": true,
        "certificate": {
            "id": "019d867d-d003-7288-8ece-fd64da756d03",
            "environment": "production",
            "is_active": true,
            "is_expired": false,
            "days_remaining": 1200,
            "valid_from": "2025-06-01T00:00:00-06:00",
            "valid_until": "2029-08-01T00:00:00-06:00"
        },
        "client": {
            "id": "019d867d-c002-7288-8ece-fd64da756c02",
            "legal_name": "Restaurante El Mango S.A.",
            "commercial_name": "El Mango",
            "id_type": "02",
            "id_number": "3101789012",
            "display_name": "El Mango"
        },
        "integrator": {
            "id": "019d867d-0241-7288-8ece-fd64da75616d",
            "legal_name": "SistemasPOS de Costa Rica S.A.",
            "commercial_name": "SistemasPOS",
            "id_type": "02",
            "id_number": "3101999888",
            "display_name": "SistemasPOS"
        },
        "access_request_id": 2,
        "granted_at": "2026-03-15T10:00:00-06:00",
        "revoked_at": "2026-04-16T15:00:00-06:00",
        "revoke_reason": "Fin de contrato de servicio.",
        "revoked_by": {
            "id": "019d867d-0241-7288-8ece-fd64da75616d",
            "legal_name": "SistemasPOS de Costa Rica S.A.",
            "display_name": "SistemasPOS"
        },
        "created_at": "2026-03-15T10:00:00-06:00"
    },
    "message": "Ha renunciado al acceso correctamente. El cliente será notificado.",
    "errors": null
}
 

Request      

DELETE api/v1/public/my-access/{grantId}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

grantId   integer     

ID del grant al que renuncia. Example: 16

Body Parameters

reason   string  optional    

Motivo opcional de la renuncia (max 500 caracteres). Example: architecto

Notificaciones

Listar notificaciones del usuario autenticado.

requires authentication

Retorna las notificaciones personales del usuario y las notificaciones generales (broadcasts) del contribuyente. Las no leidas aparecen primero, luego por fecha descendente. No incluye notificaciones expiradas.

La respuesta incluye unread_count fuera del array data para actualizar el badge de notificaciones sin una consulta adicional.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/notifications?unread_only=&per_page=16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/notifications"
);

const params = {
    "unread_only": "0",
    "per_page": "16",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/notifications';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'query' => [
            'unread_only' => '0',
            'per_page' => '16',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Lista con notificaciones):


{
    "success": true,
    "data": [
        {
            "id": 42,
            "type": "access_request_received",
            "type_label": "Solicitud de acceso recibida",
            "type_color": "info",
            "type_icon": "key",
            "type_category": "access_control",
            "title": "Nueva solicitud de acceso",
            "message": "SistemasPOS de Costa Rica S.A. solicita acceso a sus certificados digitales.",
            "action_url": "/portal/access-requests/1",
            "action_label": "Revisar solicitud",
            "has_action": true,
            "metadata": {
                "access_request_id": 1,
                "integrator_name": "SistemasPOS"
            },
            "is_read": false,
            "is_unread": true,
            "read_at": null,
            "requires_action": true,
            "is_broadcast": false,
            "is_expired": false,
            "created_at": "2026-04-16T10:00:00-06:00",
            "expires_at": null
        }
    ],
    "message": "",
    "errors": null,
    "unread_count": 3,
    "meta": {
        "current_page": 1,
        "last_page": 1,
        "per_page": 15,
        "total": 5
    },
    "links": {
        "first": "...",
        "last": "...",
        "prev": null,
        "next": null
    }
}
 

Request      

GET api/v1/public/notifications

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

unread_only   boolean  optional    

Solo retornar notificaciones no leidas. Default: false. Example: false

type   string  optional    

Filtrar por tipo de notificacion (ej: access_request_received).

per_page   integer  optional    

Resultados por pagina (1-50). Default: 15. Example: 16

Response

Response Fields

data   object     
id   integer     

ID numérico de la notificación.

type   string     

Código del tipo de notificación (ej. access_request_received, access_request_accepted, grant_revoked, certificate_expiring).

type_label   string     

Nombre legible del tipo en español.

type_color   string     

Color sugerido para la UI: info, success, warning, danger, secondary.

type_icon   string     

Nombre del ícono sugerido para la UI (ej. key, check, alert-triangle).

type_category   string     

Categoría funcional: access_control, voucher, certificate, system.

title   string     

Título corto de la notificación.

message   string     

Mensaje descriptivo completo.

action_url   string     

Ruta del portal donde el usuario puede actuar (ej. /portal/access-requests/1). null si no requiere acción.

action_label   string     

Texto del botón de acción (ej. "Revisar solicitud"). null si no hay acción.

has_action   boolean     

true si la notificación tiene un enlace de acción.

metadata   object     

Datos adicionales específicos del tipo de notificación (IDs, nombres, etc.). Estructura varía por tipo.

is_read   boolean     

true si el usuario ya leyó la notificación.

is_unread   boolean     

Inverso de is_read. Útil para filtros en la UI.

read_at   string     

Fecha/hora en que se marcó como leída (ISO 8601). null si no leída.

requires_action   boolean     

true si la notificación espera una acción del usuario (ej. aceptar/rechazar solicitud).

is_broadcast   boolean     

true si es una notificación general del tenant (visible para todos los usuarios). false si es personal.

is_expired   boolean     

true si la notificación expiró y ya no es relevante.

created_at   string     

Fecha de creación (ISO 8601).

expires_at   string     

Fecha de expiración (ISO 8601). null si no expira.

unread_count   integer     

Total de notificaciones no leídas del usuario. Incluido fuera del array data para actualizar el badge del header sin consulta adicional.

Marcar todas las notificaciones no leidas como leidas.

requires authentication

Operacion en lote que marca todas las notificaciones pendientes del usuario como leidas. Retorna la cantidad de notificaciones que fueron marcadas (0 si ya estaban todas leidas).

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/notifications/read-all" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/notifications/read-all"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/notifications/read-all';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Notificaciones marcadas):


{
    "success": true,
    "data": {
        "marked_count": 5,
        "unread_count": 0
    },
    "message": "5 notificaciones marcadas como leídas.",
    "errors": null
}
 

Example response (200, Sin notificaciones pendientes):


{
    "success": true,
    "data": {
        "marked_count": 0,
        "unread_count": 0
    },
    "message": "No hay notificaciones pendientes.",
    "errors": null
}
 

Request      

POST api/v1/public/notifications/read-all

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Marcar una notificacion como leida.

requires authentication

Operacion idempotente: si la notificacion ya estaba leida, retorna 200 con el mismo estado sin error. La respuesta incluye la notificacion actualizada y el nuevo unread_count.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/notifications/16/read" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/notifications/16/read"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/notifications/16/read';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Notificación marcada como leída):


{
    "success": true,
    "data": {
        "notification": {
            "id": 42,
            "type": "access_request_received",
            "type_label": "Solicitud de acceso recibida",
            "type_color": "info",
            "type_icon": "key",
            "type_category": "access_control",
            "title": "Nueva solicitud de acceso",
            "message": "SistemasPOS solicita acceso a sus certificados.",
            "action_url": "/portal/access-requests/1",
            "action_label": "Revisar solicitud",
            "has_action": true,
            "metadata": {
                "access_request_id": 1
            },
            "is_read": true,
            "is_unread": false,
            "read_at": "2026-04-16T11:00:00-06:00",
            "requires_action": true,
            "is_broadcast": false,
            "is_expired": false,
            "created_at": "2026-04-16T10:00:00-06:00",
            "expires_at": null
        },
        "unread_count": 2
    },
    "message": "Notificación marcada como leída.",
    "errors": null
}
 

Example response (404, No encontrada):


{
    "success": false,
    "data": null,
    "message": "Notificación no encontrada.",
    "errors": null
}
 

Request      

POST api/v1/public/notifications/{id}/read

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

ID de la notificacion a marcar. Example: 16

Suscripción y Facturación

Consultar el estado de la suscripción

requires authentication

Devuelve el estado completo de billing del contribuyente autenticado: el plan vigente, los datos de la suscripción (ciclo y período pagado), el consumo de comprobantes del mes en curso, el catálogo de planes disponibles para cambiar y, si existe, la baja de plan programada.

El consumo del mes (usage) se calcula sobre los comprobantes de producción emitidos por el contribuyente y se compara contra el límite mensual del plan (vouchers_limit puede ser null = ilimitado).

El bloque scheduled_downgrade solo está presente (no null) cuando el contribuyente programó una baja de plan que se aplicará al fin del período pagado (ver POST /subscription/downgrade).

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/subscription" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Estado de la suscripción):


{
    "success": true,
    "data": {
        "plan": {
            "code": "pyme",
            "name": "Pyme",
            "is_free": false,
            "monthly_price_usd": "29.00",
            "annual_price_usd": "290.00",
            "monthly_voucher_limit": 1000,
            "tier": 3,
            "max_clients": 300,
            "max_items": 1000,
            "max_users": 5,
            "max_managed_contributors": 0,
            "max_branches": 3,
            "max_terminals_per_branch": 5,
            "has_api": true
        },
        "subscription": {
            "status": "active",
            "billing_cycle": "monthly",
            "current_period_start": "2026-06-01T00:00:00-06:00",
            "current_period_end": "2026-07-01T00:00:00-06:00",
            "days_remaining": 8,
            "is_past_due": false,
            "is_recurring": true
        },
        "usage": {
            "vouchers_used": 142,
            "vouchers_limit": 1000
        },
        "available_plans": [
            {
                "code": "free",
                "name": "Gratis",
                "is_free": true,
                "monthly_price_usd": "0.00",
                "annual_price_usd": "0.00",
                "monthly_voucher_limit": 10,
                "tier": 1,
                "max_clients": 5,
                "max_items": 10,
                "max_users": 1,
                "max_managed_contributors": 0,
                "max_branches": 1,
                "max_terminals_per_branch": 1,
                "has_api": false
            }
        ],
        "scheduled_downgrade": null
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/subscription

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
plan   object     

Plan vigente del contribuyente. null si no tiene plan asignado.

code   string     

Código del plan (ej. free, pyme). Úselo como plan_code en checkout/subscribe/downgrade.

name   string     

Nombre legible del plan.

is_free   boolean     

true si es el plan gratuito (no requiere pago).

monthly_price_usd   string     

Precio mensual en USD.

annual_price_usd   string     

Precio anual en USD.

monthly_voucher_limit   integer     

Límite mensual de comprobantes. null = ilimitado.

tier   integer     

Rango del plan (mayor = plan superior). Sirve para distinguir upgrade de downgrade.

max_clients   integer     

Máximo de clientes. null = ilimitado.

max_items   integer     

Máximo de productos/servicios. null = ilimitado.

max_users   integer     

Máximo de usuarios. null = ilimitado.

max_managed_contributors   integer     

Máximo de contribuyentes gestionados (planes de contador). null = ilimitado.

max_branches   integer     

Máximo de sucursales. null = ilimitado.

max_terminals_per_branch   integer     

Máximo de terminales por sucursal. null = ilimitado.

has_api   boolean     

true si el plan incluye acceso a la API pública.

subscription   object     

Datos de la suscripción activa. null si no hay suscripción.

status   string     

Estado de la suscripción (ej. active, past_due).

billing_cycle   string     

Ciclo de cobro: monthly o annual.

current_period_start   string     

Inicio del período pagado actual (ISO 8601).

current_period_end   string     

Fin del período pagado actual (ISO 8601).

days_remaining   integer     

Días restantes del período pagado.

is_past_due   boolean     

true si la suscripción está vencida o en mora.

is_recurring   boolean     

true si hay una suscripción recurrente activa en Tilopay (habilita "Cancelar suscripción").

usage   object     

Consumo de comprobantes del mes en curso.

vouchers_used   integer     

Comprobantes de producción emitidos este mes.

vouchers_limit   integer     

Límite mensual del plan. null = ilimitado.

available_plans   object[]     

Catálogo de planes activos disponibles para cambiar (misma forma que data.plan).

scheduled_downgrade   object     

Baja de plan programada al fin del período. null si no hay ninguna.

plan   string     

Código del plan destino de la baja programada.

plan_name   string     

Nombre del plan destino.

at   string     

Fecha en que se aplicará la baja (ISO 8601).

Listar el historial de pagos

requires authentication

Devuelve hasta los 100 pagos más recientes del contribuyente (mensualidades y cargos de suscripción), ordenados del más nuevo al más antiguo. Cada pago incluye, si existe, la factura electrónica emitida por Almendro al cobrar.

El campo data es un arreglo plano de pagos (sin paginación). El bloque invoice es null mientras el comprobante de la mensualidad aún no se ha emitido o vinculado.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/subscription/payments" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/payments"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/payments';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Historial de pagos):


{
    "success": true,
    "data": [
        {
            "id": "019d867d-0009-7288-8ece-fd64da756009",
            "date": "2026-06-01T08:30:00-06:00",
            "type": "subscription",
            "status": "paid",
            "amount_usd": "29.00",
            "currency": "USD",
            "amount_local": "15080.00",
            "description": "Suscripción Pyme (monthly)",
            "period_start": "2026-06-01",
            "period_end": "2026-07-01",
            "gateway": "tilopay",
            "invoice": {
                "voucher_key": "50601062600310112345600100001010000000001100000001",
                "status": "accepted",
                "hacienda_status": "aceptado"
            }
        },
        {
            "id": "019d867d-0008-7288-8ece-fd64da756008",
            "date": "2026-05-01T08:30:00-06:00",
            "type": "subscription",
            "status": "paid",
            "amount_usd": "29.00",
            "currency": "USD",
            "amount_local": "15010.00",
            "description": "Suscripción Pyme (monthly)",
            "period_start": "2026-05-01",
            "period_end": "2026-06-01",
            "gateway": "tilopay",
            "invoice": null
        }
    ],
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/subscription/payments

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object[]     

Lista de pagos (máximo 100, más recientes primero).

id   string     

UUID del pago.

date   string     

Fecha del pago (paid_at o created_at, ISO 8601).

type   string     

Tipo de pago (ej. subscription).

status   string     

Estado del pago (ej. paid, pending, failed).

amount_usd   string     

Monto cobrado en USD.

currency   string     

Moneda del cobro.

amount_local   string     

Monto en moneda local (colones) si aplica. Puede ser null.

description   string     

Descripción legible del cargo.

period_start   string     

Inicio del período cubierto (fecha). Puede ser null.

period_end   string     

Fin del período cubierto (fecha). Puede ser null.

gateway   string     

Pasarela usada para el cobro (ej. tilopay).

invoice   object     

Factura electrónica del cobro. null si aún no se ha emitido o vinculado.

voucher_key   string     

Clave de 50 dígitos del comprobante.

status   string     

Estado interno del comprobante.

hacienda_status   string     

Estado del comprobante en Hacienda.

Cancelar una baja de plan programada

requires authentication

Revierte una baja de plan programada (downgrade o cancelación de suscripción aún no ejecutada) y conserva el plan actual sin cambios ni cobros nuevos. Solo tiene efecto mientras la baja no se haya aplicado.

Por seguridad exige la contraseña del usuario autenticado. Requiere que exista una baja programada; si no, responde 404.

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/subscription/scheduled-downgrade" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"password\": \"mi-contrasena-secreta\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/scheduled-downgrade"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "password": "mi-contrasena-secreta"
};

fetch(url, {
    method: "DELETE",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/scheduled-downgrade';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'password' => 'mi-contrasena-secreta',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Baja cancelada):


{
    "success": true,
    "data": null,
    "message": "Baja de plan cancelada. Mantenés tu plan actual.",
    "errors": null
}
 

Example response (404, Sin baja programada):


{
    "success": false,
    "data": null,
    "message": "No hay una baja de plan programada.",
    "errors": null
}
 

Example response (422, Contraseña incorrecta):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "password": [
            "La contraseña es incorrecta."
        ]
    }
}
 

Request      

DELETE api/v1/public/subscription/scheduled-downgrade

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

password   string     

Contraseña del usuario autenticado, para confirmar el cambio. Example: mi-contrasena-secreta

Iniciar el pago único de un plan (checkout)

requires authentication

Inicia el checkout de Tilopay para contratar un plan de pago por un único período (mensual o anual). Crea una sesión de pago en Tilopay, registra un pago PENDIENTE y devuelve la redirect_url a la que su backend debe redirigir al cliente para completar el pago.

La activación del plan no ocurre en esta respuesta: sucede en el retorno firmado de Tilopay una vez que el pago se confirma.

Pagar no exige contraseña (el cliente entrega dinero, no hay riesgo). El plan gratis no puede contratarse por esta vía.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/subscription/checkout" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"plan_code\": \"pyme\",
    \"billing_cycle\": \"monthly\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/checkout"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "plan_code": "pyme",
    "billing_cycle": "monthly"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/checkout';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'plan_code' => 'pyme',
            'billing_cycle' => 'monthly',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Sesión de pago creada):


{
    "success": true,
    "data": {
        "redirect_url": "https://app.tilopay.com/checkout/abc123",
        "order": "TPAY-019d867d-0001"
    },
    "message": "Redirigiendo a la pasarela de pago.",
    "errors": null
}
 

Example response (422, Plan no disponible):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan_code": [
            "El plan solicitado no existe o no está disponible."
        ]
    }
}
 

Example response (422, Plan gratuito):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan_code": [
            "El plan gratuito no requiere pago."
        ]
    }
}
 

Example response (422, Sin precio para el ciclo):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "billing_cycle": [
            "El plan no tiene precio para el ciclo seleccionado."
        ]
    }
}
 

Example response (502, Fallo de la pasarela):


{
    "success": false,
    "data": null,
    "message": "No se pudo iniciar el pago con Tilopay. Intente de nuevo en unos minutos.",
    "errors": null
}
 

Request      

POST api/v1/public/subscription/checkout

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

plan_code   string     

Código del plan a contratar. Debe existir y estar activo. No puede ser el plan gratuito. Example: pyme

billing_cycle   string     

Ciclo de cobro. Valores: monthly o annual. Example: monthly

Iniciar la suscripción recurrente de un plan

requires authentication

Inicia una suscripción recurrente (cobro automático periódico) de un plan de pago en Tilopay. Genera la URL de registro de la suscripción con el correo del contribuyente pre-cargado y devuelve la redirect_url a la que su backend debe redirigir al cliente.

El plan se activa cuando llega el webhook de suscripción/cobro de Tilopay, no en esta respuesta. Si el contribuyente ya estaba registrado, se devuelve la URL de renovación.

Requiere que exista un plan de pasarela configurado para la combinación plan + ciclo, y que la cuenta tenga al menos un correo registrado. El plan gratis no admite suscripción.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/subscription/subscribe" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"plan_code\": \"pyme\",
    \"billing_cycle\": \"monthly\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/subscribe"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "plan_code": "pyme",
    "billing_cycle": "monthly"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/subscribe';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'plan_code' => 'pyme',
            'billing_cycle' => 'monthly',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Registro de suscripción creado):


{
    "success": true,
    "data": {
        "redirect_url": "https://app.tilopay.com/subscribe/register/xyz789"
    },
    "message": "Redirigiendo al registro de la suscripción.",
    "errors": null
}
 

Example response (409, Suscripción recurrente no disponible para el plan):


{
    "success": false,
    "data": null,
    "message": "La suscripción recurrente para este plan aún no está disponible. Contacte a soporte.",
    "errors": null
}
 

Example response (422, Plan no disponible o gratuito):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan_code": [
            "El plan solicitado no existe, no está disponible o es gratuito."
        ]
    }
}
 

Example response (422, Sin correo registrado):


{
    "success": false,
    "data": null,
    "message": "Su cuenta no tiene un correo registrado para la suscripción.",
    "errors": null
}
 

Example response (502, Fallo de la pasarela):


{
    "success": false,
    "data": null,
    "message": "No se pudo iniciar la suscripción con Tilopay. Intente más tarde.",
    "errors": null
}
 

Request      

POST api/v1/public/subscription/subscribe

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

plan_code   string     

Código del plan a suscribir. Debe existir, estar activo y no ser gratuito. Example: pyme

billing_cycle   string     

Ciclo de cobro. Valores: monthly o annual. Example: monthly

Cancelar la suscripción recurrente

requires authentication

Cancela el cobro automático de la suscripción recurrente en Tilopay y programa la baja al plan gratis al fin del período pagado. El contribuyente conserva su plan actual hasta entonces y no hay reembolso.

Por tratarse de un cambio de plan (no un pago), exige la contraseña del usuario autenticado y un motivo de al menos 100 caracteres (retención y analítica). Requiere una suscripción recurrente activa en Tilopay.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/subscription/cancel-recurring" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"password\": \"mi-contrasena-secreta\",
    \"reason\": \"Cancelo la suscripcion porque por ahora estoy facturando muy poco y prefiero volver al plan gratuito mientras crece mi negocio.\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/cancel-recurring"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "password": "mi-contrasena-secreta",
    "reason": "Cancelo la suscripcion porque por ahora estoy facturando muy poco y prefiero volver al plan gratuito mientras crece mi negocio."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/cancel-recurring';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'password' => 'mi-contrasena-secreta',
            'reason' => 'Cancelo la suscripcion porque por ahora estoy facturando muy poco y prefiero volver al plan gratuito mientras crece mi negocio.',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Suscripción cancelada):


{
    "success": true,
    "data": null,
    "message": "Suscripción cancelada. Mantenés el plan hasta el fin del período pagado.",
    "errors": null
}
 

Example response (404, Sin suscripción recurrente activa):


{
    "success": false,
    "data": null,
    "message": "No hay una suscripción recurrente activa para cancelar.",
    "errors": null
}
 

Example response (422, Contraseña incorrecta):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "password": [
            "La contraseña es incorrecta."
        ]
    }
}
 

Example response (422, Motivo demasiado corto):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "reason": [
            "El motivo debe tener al menos 100 caracteres."
        ]
    }
}
 

Example response (502, Fallo de la pasarela):


{
    "success": false,
    "data": null,
    "message": "No se pudo cancelar la suscripción con Tilopay. Intente más tarde.",
    "errors": null
}
 

Request      

POST api/v1/public/subscription/cancel-recurring

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

password   string     

Contraseña del usuario autenticado, para confirmar el cambio. Example: mi-contrasena-secreta

reason   string     

Motivo de la cancelación (mínimo 100 caracteres). Example: Cancelo la suscripcion porque por ahora estoy facturando muy poco y prefiero volver al plan gratuito mientras crece mi negocio.

Programar una baja de plan (downgrade)

requires authentication

Programa una baja a un plan inferior que se aplicará al fin del período pagado. Si existe una suscripción recurrente en Tilopay, se cancela el cobro automático para que no haya cargos futuros del plan superior. El contribuyente conserva su plan actual hasta que venza el período pagado y entonces pasa al plan destino. No hay reembolso.

Por tratarse de un cambio de plan (no un pago), exige la contraseña del usuario y un motivo de al menos 100 caracteres.

El plan destino debe ser de menor rango (tier/display_order) que el actual y no puede ser el plan que ya tiene activo. Para subir de plan use la suscripción (POST /subscription/subscribe).

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/subscription/downgrade" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"plan_code\": \"emprendedor\",
    \"password\": \"mi-contrasena-secreta\",
    \"reason\": \"Bajo de plan porque mi volumen de facturacion disminuyo bastante este trimestre y el plan inferior cubre de sobra mis necesidades actuales.\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/downgrade"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "plan_code": "emprendedor",
    "password": "mi-contrasena-secreta",
    "reason": "Bajo de plan porque mi volumen de facturacion disminuyo bastante este trimestre y el plan inferior cubre de sobra mis necesidades actuales."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/downgrade';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'plan_code' => 'emprendedor',
            'password' => 'mi-contrasena-secreta',
            'reason' => 'Bajo de plan porque mi volumen de facturacion disminuyo bastante este trimestre y el plan inferior cubre de sobra mis necesidades actuales.',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Baja programada):


{
    "success": true,
    "data": null,
    "message": "Baja de plan programada. Mantenés tu plan actual hasta el fin del período pagado.",
    "errors": null
}
 

Example response (422, Contraseña incorrecta):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "password": [
            "La contraseña es incorrecta."
        ]
    }
}
 

Example response (422, Motivo demasiado corto):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "reason": [
            "El motivo debe tener al menos 100 caracteres."
        ]
    }
}
 

Example response (422, Plan no disponible):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan_code": [
            "El plan solicitado no existe o no está disponible."
        ]
    }
}
 

Example response (422, Ya tiene este plan):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan_code": [
            "Ya tenés este plan activo."
        ]
    }
}
 

Example response (422, No es una baja real):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "plan_code": [
            "Para subir de plan, usá la suscripción (cobro automático)."
        ]
    }
}
 

Example response (502, Fallo de la pasarela):


{
    "success": false,
    "data": null,
    "message": "No se pudo procesar la baja con Tilopay. Intente más tarde.",
    "errors": null
}
 

Request      

POST api/v1/public/subscription/downgrade

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

plan_code   string     

Código del plan destino. Debe existir, estar activo y ser de menor rango que el actual. Example: emprendedor

password   string     

Contraseña del usuario autenticado, para confirmar el cambio. Example: mi-contrasena-secreta

reason   string     

Motivo de la baja (mínimo 100 caracteres). Example: Bajo de plan porque mi volumen de facturacion disminuyo bastante este trimestre y el plan inferior cubre de sobra mis necesidades actuales.

Soporte

Listar conversaciones de soporte

requires authentication

Devuelve todas las conversaciones de soporte abiertas por el contribuyente autenticado, ordenadas con las más recientes primero (por última actividad). Cada conversación incluye el conteo de mensajes, pero no el hilo completo | para ver los mensajes use GET /support/conversations/{id}.

Las conversaciones son tipo chat in-app entre el cliente y el equipo de soporte, dentro de la plataforma (sin correos). El alcance está limitado al contribuyente del token (multi-tenant).

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/support/conversations" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/support/conversations"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/support/conversations';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Listado de conversaciones):


{
    "success": true,
    "data": [
        {
            "id": "019d867d-0001-7288-8ece-fd64da756001",
            "subject": "No me llega el correo al cliente",
            "status": "open",
            "priority": "normal",
            "unread_for_client": false,
            "messages_count": 3,
            "last_message_at": "2026-06-23T10:00:00-06:00",
            "created_at": "2026-06-22T09:00:00-06:00"
        }
    ],
    "message": "Conversaciones de soporte.",
    "errors": null
}
 

Example response (200, Sin conversaciones):


{
    "success": true,
    "data": [],
    "message": "Conversaciones de soporte.",
    "errors": null
}
 

Request      

GET api/v1/public/support/conversations

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Abrir una conversación de soporte

requires authentication

Crea una nueva conversación de soporte con su primer mensaje. La conversación nace en estado open y con la prioridad correspondiente al plan del contribuyente (priority si tiene soporte prioritario, normal en caso contrario).

Al crearla se envía un correo de acuse al cliente, pero el hilo sigue siendo in-app.

Límite: el cliente solo puede enviar un mensaje de soporte por hora. Si el último mensaje del cliente (en cualquiera de sus conversaciones) tiene menos de una hora y el equipo aún no lo respondió, la API responde HTTP 429 con el tiempo restante.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/support/conversations" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"subject\": \"No me llega el correo al cliente\",
    \"message\": \"Hola, emití una factura pero el cliente dice que no le llegó el correo.\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/support/conversations"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "subject": "No me llega el correo al cliente",
    "message": "Hola, emití una factura pero el cliente dice que no le llegó el correo."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/support/conversations';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'subject' => 'No me llega el correo al cliente',
            'message' => 'Hola, emití una factura pero el cliente dice que no le llegó el correo.',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Conversación creada):


{
    "success": true,
    "data": {
        "id": "019d867d-0001-7288-8ece-fd64da756001",
        "subject": "No me llega el correo al cliente",
        "status": "open",
        "priority": "normal",
        "unread_for_client": false,
        "messages_count": 1,
        "last_message_at": "2026-06-23T10:00:00-06:00",
        "created_at": "2026-06-23T10:00:00-06:00",
        "client_can_write": false,
        "cooldown_seconds": 3600,
        "messages": [
            {
                "id": "019d867d-0002-7288-8ece-fd64da756002",
                "sender_type": "client",
                "body": "Hola, emití una factura pero el cliente dice que no le llegó el correo.",
                "created_at": "2026-06-23T10:00:00-06:00"
            }
        ]
    },
    "message": "Conversación creada.",
    "errors": null
}
 

Example response (422, Mensaje requerido):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "message": [
            "El campo message es obligatorio."
        ]
    }
}
 

Example response (429, Límite de un mensaje por hora):


{
    "success": false,
    "data": {
        "cooldown_seconds": 2400
    },
    "message": "Solo puedes enviar un mensaje de soporte cada hora. Podrás escribir de nuevo en 40 min, o cuando el equipo responda.",
    "errors": null
}
 

Request      

POST api/v1/public/support/conversations

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

subject   string  optional    

Asunto de la conversación (máximo 160 caracteres). Opcional. Example: No me llega el correo al cliente

message   string     

Primer mensaje de la conversación (2 a 5000 caracteres). Example: Hola, emití una factura pero el cliente dice que no le llegó el correo.

Consultar una conversación de soporte

requires authentication

Devuelve el detalle de una conversación con su hilo completo de mensajes ordenados cronológicamente. Solo se retornan conversaciones que pertenecen al contribuyente autenticado.

Al consultar el detalle, la conversación se marca como leída por el cliente (unread_for_client pasa a false).

La respuesta incluye además client_can_write y cooldown_seconds, que reflejan el límite de un mensaje por hora para que el frontend habilite o deshabilite el cuadro de respuesta.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/support/conversations/019d867d-0001-7288-8ece-fd64da756001" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/support/conversations/019d867d-0001-7288-8ece-fd64da756001"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/support/conversations/019d867d-0001-7288-8ece-fd64da756001';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Detalle con hilo de mensajes):


{
    "success": true,
    "data": {
        "id": "019d867d-0001-7288-8ece-fd64da756001",
        "subject": "No me llega el correo al cliente",
        "status": "open",
        "priority": "normal",
        "unread_for_client": false,
        "messages_count": 2,
        "last_message_at": "2026-06-23T11:00:00-06:00",
        "created_at": "2026-06-23T10:00:00-06:00",
        "client_can_write": true,
        "cooldown_seconds": 0,
        "messages": [
            {
                "id": "019d867d-0002-7288-8ece-fd64da756002",
                "sender_type": "client",
                "body": "Hola, emití una factura pero el cliente dice que no le llegó el correo.",
                "created_at": "2026-06-23T10:00:00-06:00"
            },
            {
                "id": "019d867d-0003-7288-8ece-fd64da756003",
                "sender_type": "admin",
                "body": "Hola, ya revisamos y el correo salió correctamente. Pídale al cliente revisar spam.",
                "created_at": "2026-06-23T11:00:00-06:00"
            }
        ]
    },
    "message": "Detalle de la conversación.",
    "errors": null
}
 

Example response (404, No encontrada):


{
    "success": false,
    "data": null,
    "message": "Conversación no encontrada.",
    "errors": null
}
 

Request      

GET api/v1/public/support/conversations/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID de la conversación. Example: 019d867d-0001-7288-8ece-fd64da756001

Responder en una conversación de soporte

requires authentication

Añade un nuevo mensaje del cliente al hilo de una conversación existente. Si la conversación estaba resuelta o cerrada, se reabre automáticamente (status vuelve a open) y se notifica al equipo.

Devuelve la conversación con el hilo completo de mensajes actualizado.

Límite: aplica el mismo límite de un mensaje por hora que al abrir una conversación. Si el último mensaje del cliente tiene menos de una hora y el equipo aún no respondió, la API responde HTTP 429 con el tiempo restante.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/support/conversations/019d867d-0001-7288-8ece-fd64da756001/reply" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"message\": \"Listo, el cliente lo encontró en spam. Muchas gracias.\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/support/conversations/019d867d-0001-7288-8ece-fd64da756001/reply"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "message": "Listo, el cliente lo encontró en spam. Muchas gracias."
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/support/conversations/019d867d-0001-7288-8ece-fd64da756001/reply';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'message' => 'Listo, el cliente lo encontró en spam. Muchas gracias.',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Mensaje enviado):


{
    "success": true,
    "data": {
        "id": "019d867d-0001-7288-8ece-fd64da756001",
        "subject": "No me llega el correo al cliente",
        "status": "open",
        "priority": "normal",
        "unread_for_client": false,
        "messages_count": 3,
        "last_message_at": "2026-06-23T11:30:00-06:00",
        "created_at": "2026-06-23T10:00:00-06:00",
        "client_can_write": false,
        "cooldown_seconds": 3600,
        "messages": [
            {
                "id": "019d867d-0004-7288-8ece-fd64da756004",
                "sender_type": "client",
                "body": "Listo, el cliente lo encontró en spam. Muchas gracias.",
                "created_at": "2026-06-23T11:30:00-06:00"
            }
        ]
    },
    "message": "Mensaje enviado.",
    "errors": null
}
 

Example response (404, No encontrada):


{
    "success": false,
    "data": null,
    "message": "Conversación no encontrada.",
    "errors": null
}
 

Example response (422, Mensaje requerido):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "message": [
            "El campo message es obligatorio."
        ]
    }
}
 

Example response (429, Límite de un mensaje por hora):


{
    "success": false,
    "data": {
        "cooldown_seconds": 1800
    },
    "message": "Solo puedes enviar un mensaje de soporte cada hora. Podrás escribir de nuevo en 30 min, o cuando el equipo responda.",
    "errors": null
}
 

Request      

POST api/v1/public/support/conversations/{id}/reply

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

UUID de la conversación. Example: 019d867d-0001-7288-8ece-fd64da756001

Body Parameters

message   string     

Mensaje a agregar al hilo (2 a 5000 caracteres). Example: Listo, el cliente lo encontró en spam. Muchas gracias.

Soporte Prioritario

Consultar el estado del soporte prioritario

requires authentication

Devuelve el tier de soporte actual del contribuyente, si tiene una suscripción recurrente activa, los precios vigentes de cada tier y la disponibilidad de cada uno para contratación en línea.

Los precios provienen de la configuración del sistema y están expresados en dólares estadounidenses (USD). La disponibilidad depende de que exista un plan de pasarela (Tilopay) configurado para el addon correspondiente con ciclo mensual.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/support-tier" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/support-tier"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/support-tier';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Estado del soporte):


{
    "success": true,
    "data": {
        "current_tier": "standard",
        "has_subscription": false,
        "premium_usd": 500,
        "premium_247_usd": 2400,
        "premium_available": true,
        "premium_247_available": true
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/support-tier

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
current_tier   string     

Tier de soporte actual del contribuyente: standard, premium o premium_247.

has_subscription   boolean     

true si el contribuyente tiene una suscripción de soporte recurrente activa.

premium_usd   number     

Precio mensual del tier premium en dólares (USD).

premium_247_usd   number     

Precio mensual del tier premium 24/7 en dólares (USD).

premium_available   boolean     

true si el tier premium se puede contratar en línea actualmente.

premium_247_available   boolean     

true si el tier premium 24/7 se puede contratar en línea actualmente.

Contratar soporte prioritario

requires authentication

Inicia la suscripción recurrente del tier elegido y devuelve una URL de redirección a la pasarela Tilopay, donde el contribuyente registra su medio de pago para el cobro mensual.

El tier solo se activa de forma efectiva cuando la pasarela confirma el registro mediante sus webhooks recurrentes | esta llamada solo inicia el proceso.

Requisitos:

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/support-tier/subscribe" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"tier\": \"premium\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/support-tier/subscribe"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "tier": "premium"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/support-tier/subscribe';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'tier' => 'premium',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Redirección a la pasarela):


{
    "success": true,
    "data": {
        "redirect_url": "https://app.tilopay.com/recurrent/register/abc123"
    },
    "message": "Redirigiendo a la pasarela de pago.",
    "errors": null
}
 

Example response (409, Tier no disponible para contratar en línea):


{
    "success": false,
    "data": null,
    "message": "El soporte prioritario aún no está disponible para contratar en línea. Escríbenos y lo activamos.",
    "errors": null
}
 

Example response (422, Sin correo en el perfil):


{
    "success": false,
    "data": null,
    "message": "Necesitas un correo en tu perfil para contratar el soporte.",
    "errors": null
}
 

Example response (422, Tier inválido):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "tier": [
            "El tier seleccionado no es válido."
        ]
    }
}
 

Example response (502, Error al iniciar la suscripción en la pasarela):


{
    "success": false,
    "data": null,
    "message": "No se pudo iniciar la suscripción. Intenta de nuevo en unos minutos.",
    "errors": null
}
 

Request      

POST api/v1/public/support-tier/subscribe

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

tier   string     

Tier a contratar. Valores permitidos: premium, premium_247. Example: premium

Cancelar soporte prioritario

requires authentication

Cancela la suscripción recurrente de soporte: elimina el suscriptor en la pasarela Tilopay y devuelve al contribuyente al tier standard, limpiando su identificador de suscripción.

Si el contribuyente no tiene una suscripción de soporte activa, responde HTTP 404.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/support-tier/cancel" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/support-tier/cancel"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/support-tier/cancel';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Soporte cancelado):


{
    "success": true,
    "data": null,
    "message": "Soporte prioritario cancelado.",
    "errors": null
}
 

Example response (404, Sin suscripción activa):


{
    "success": false,
    "data": null,
    "message": "No tienes una suscripción de soporte activa para cancelar.",
    "errors": null
}
 

Example response (502, Error al cancelar en la pasarela):


{
    "success": false,
    "data": null,
    "message": "No se pudo cancelar la suscripción. Intenta de nuevo.",
    "errors": null
}
 

Request      

POST api/v1/public/support-tier/cancel

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Onboarding

Estado del onboarding del contribuyente

requires authentication

Devuelve un resumen del onboarding del contribuyente autenticado: sus créditos disponibles (gratuito anual + comprados), si puede autoagendar en este momento, el precio vigente de la hora y la próxima cita futura agendada (si existe).

El campo can_schedule es true solo cuando hay al menos un crédito disponible y hay agentes activos en la agenda. Úselo para habilitar o deshabilitar el botón de agendar en el portal.

El bloque appointment es null cuando no hay una cita de onboarding o pagada futura con estado agendado; de lo contrario trae los datos de la próxima sesión.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/onboarding" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/onboarding"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/onboarding';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Con crédito y cita agendada):


{
    "success": true,
    "data": {
        "credits": {
            "free": 1,
            "purchased": 0,
            "total": 1
        },
        "can_schedule": true,
        "price_usd": 100,
        "appointment": {
            "id": "019d867d-0001-7288-8ece-fd64da756001",
            "starts_at": "2026-07-01T09:00:00-06:00",
            "ends_at": "2026-07-01T09:50:00-06:00",
            "agent_name": "Ana Soporte",
            "meeting_url": "https://meet.google.com/abc-defg-hij",
            "status": "scheduled"
        }
    },
    "message": "",
    "errors": null
}
 

Example response (200, Sin créditos ni cita):


{
    "success": true,
    "data": {
        "credits": {
            "free": 0,
            "purchased": 0,
            "total": 0
        },
        "can_schedule": false,
        "price_usd": 100,
        "appointment": null
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/onboarding

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
credits   object     

Resumen de créditos de onboarding disponibles.

free   integer     

Créditos gratuitos anuales disponibles.

purchased   integer     

Créditos comprados disponibles.

total   integer     

Suma de créditos disponibles (free + purchased).

can_schedule   boolean     

true si tiene al menos un crédito y hay agentes activos en la agenda.

price_usd   number     

Precio vigente de la hora de onboarding en USD.

appointment   object     

Próxima cita futura agendada, o null si no hay ninguna.

id   string     

UUID de la cita.

starts_at   string     

Inicio de la cita (ISO 8601).

ends_at   string     

Fin de la cita (ISO 8601).

agent_name   string     

Nombre del agente asignado. Puede ser null.

meeting_url   string     

Enlace de la reunión (Google Meet). Puede ser null.

status   string     

Estado de la cita (por ejemplo scheduled).

Huecos disponibles para autoagendar

requires authentication

Devuelve los horarios libres en los que el contribuyente puede agendar su sesión de onboarding, calculados a partir de las agendas de los agentes activos. La ventana de búsqueda va desde ahora hasta 120 días hacia adelante y respeta la antelación mínima configurada.

Por privacidad, la respuesta no incluye el agente de cada hueco; solo el inicio y fin del horario. El agente se asigna internamente al confirmar la cita en POST /onboarding/appointments.

Use el valor starts_at de un hueco tal cual al agendar.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/onboarding/slots" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/onboarding/slots"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/onboarding/slots';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Huecos disponibles):


{
    "success": true,
    "data": {
        "slots": [
            {
                "starts_at": "2026-07-01T09:00:00-06:00",
                "ends_at": "2026-07-01T09:50:00-06:00"
            },
            {
                "starts_at": "2026-07-01T10:00:00-06:00",
                "ends_at": "2026-07-01T10:50:00-06:00"
            }
        ]
    },
    "message": "",
    "errors": null
}
 

Example response (200, Sin huecos disponibles):


{
    "success": true,
    "data": {
        "slots": []
    },
    "message": "",
    "errors": null
}
 

Request      

GET api/v1/public/onboarding/slots

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
slots   object[]     

Lista de horarios libres disponibles para agendar.

starts_at   string     

Inicio del hueco (ISO 8601). Envíelo como starts_at al agendar.

ends_at   string     

Fin del hueco (ISO 8601).

Agendar la sesión de onboarding

requires authentication

Reserva un hueco para la sesión de onboarding y consume 1 crédito disponible (se usa primero el crédito gratuito anual; si no hay, se usa uno comprado). El starts_at enviado debe coincidir exactamente con un hueco devuelto por GET /onboarding/slots.

Antes de crear la cita, el sistema reconfirma la disponibilidad del horario contra la agenda actual. Si el hueco ya fue tomado o el agente tiene un conflicto, responde HTTP 422 y debe elegir otro horario. Si no tiene créditos disponibles, también responde 422.

Al agendar correctamente se crea el evento (y enlace de Google Meet, si la integración está activa) y se envía un correo de confirmación al contribuyente cuando tiene un email registrado.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/onboarding/appointments" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"starts_at\": \"2026-07-01T09:00:00-06:00\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/onboarding/appointments"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "starts_at": "2026-07-01T09:00:00-06:00"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/onboarding/appointments';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'starts_at' => '2026-07-01T09:00:00-06:00',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Cita agendada):


{
    "success": true,
    "data": {
        "id": "019d867d-0001-7288-8ece-fd64da756001",
        "starts_at": "2026-07-01T09:00:00-06:00",
        "ends_at": "2026-07-01T09:50:00-06:00",
        "agent_name": "Ana Soporte",
        "meeting_url": "https://meet.google.com/abc-defg-hij",
        "status": "scheduled"
    },
    "message": "Cita de onboarding agendada.",
    "errors": null
}
 

Example response (422, Sin créditos disponibles):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "credit": [
            "No tienes créditos de onboarding disponibles."
        ]
    }
}
 

Example response (422, Horario ya no disponible):


{
    "success": false,
    "data": null,
    "message": "Los datos proporcionados no son válidos.",
    "errors": {
        "starts_at": [
            "Ese horario ya no está disponible. Elige otro."
        ]
    }
}
 

Request      

POST api/v1/public/onboarding/appointments

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

starts_at   string     

Inicio del hueco a reservar (fecha/hora válida, ISO 8601). Debe coincidir con un starts_at devuelto por GET /onboarding/slots. Example: 2026-07-01T09:00:00-06:00

Response

Response Fields

data   object     
id   string     

UUID de la cita creada.

starts_at   string     

Inicio de la cita (ISO 8601).

ends_at   string     

Fin de la cita (ISO 8601).

agent_name   string     

Nombre del agente asignado. Puede ser null.

meeting_url   string     

Enlace de la reunión (Google Meet). Puede ser null si la integración no está activa.

status   string     

Estado de la cita (por ejemplo scheduled).

Comprar una hora de onboarding

requires authentication

Inicia el checkout de Tilopay para comprar 1 hora de onboarding a la tarifa vigente (USD). Disponible para todos los planes. Devuelve la URL de redirección a la pasarela de pago y el número de orden.

Importante: el crédito no se otorga en este endpoint. Solo se inicia el cobro y se registra un pago en estado pendiente. El crédito purchased se crea cuando el pago se confirma mediante el retorno firmado de Tilopay (procesado por el webhook de retorno).

Si la pasarela falla al iniciar el checkout, responde HTTP 502.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/onboarding/purchase" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/onboarding/purchase"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/onboarding/purchase';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Checkout iniciado):


{
    "success": true,
    "data": {
        "redirect_url": "https://app.tilopay.com/checkout/abc123",
        "order": "TPAY-0001"
    },
    "message": "Redirigiendo a la pasarela de pago.",
    "errors": null
}
 

Example response (502, Pasarela no disponible):


{
    "success": false,
    "data": null,
    "message": "No se pudo iniciar el pago con Tilopay. Intente de nuevo en unos minutos.",
    "errors": null
}
 

Request      

POST api/v1/public/onboarding/purchase

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
redirect_url   string     

URL de la pasarela de Tilopay a la que debe redirigir al usuario.

order   string     

Identificador de la orden/pago en la pasarela.

Token API

Consultar metadatos del token API de integración

requires authentication

Devuelve información del token API actualmente activo del contribuyente (fecha de creación, último uso, permisos) sin exponer el valor del token. Si aún no ha generado un token API, retorna data: null.

Útil para:

Recuerde: este endpoint solo muestra metadatos. El valor del token solo se devuelve una vez, en la respuesta de POST /public/tokens.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/tokens/current" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/tokens/current"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/tokens/current';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Token API existente):


{
    "success": true,
    "data": {
        "name": "api-integration",
        "created_at": "2026-04-01T10:00:00-06:00",
        "last_used_at": "2026-04-16T14:30:00-06:00",
        "abilities": [
            "*"
        ],
        "token_hint": "••••••••••••••••••••••••••••••••"
    },
    "message": "",
    "errors": null
}
 

Example response (200, Sin token API generado aún):


{
    "success": true,
    "data": null,
    "message": "No tiene un token API generado aún.",
    "errors": null
}
 

Example response (401, No autenticado):


{
    "success": false,
    "data": null,
    "message": "Unauthenticated.",
    "errors": null
}
 

Request      

GET api/v1/public/tokens/current

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Generar o regenerar el token API Bearer

requires authentication

Crea un nuevo token API permanente para uso desde sistemas externos (POS, ERP, e-commerce, CRM). Si el contribuyente ya tenía un token API, el anterior se revoca automáticamente al crear el nuevo.

Importante: el valor del token se devuelve en texto plano una única vez en esta respuesta, en el campo bearer_token. Guárdelo inmediatamente | no hay forma de recuperarlo después. Para saber si ya existe un token (sin exponerlo), use GET /public/tokens/current.

Aislamiento de sesiones: regenerar el token API no afecta a su sesión actual del portal. Puede regenerar el token sin cerrar la ventana del navegador.

Impacto en sistemas productivos: al regenerar, el token anterior deja de funcionar inmediatamente. Todas sus integraciones que usaban el token viejo empezarán a recibir HTTP 401 hasta que actualice la configuración con el nuevo token.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/tokens" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/tokens"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/tokens';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (201, Token generado exitosamente):


{
    "success": true,
    "data": {
        "bearer_token": "3|a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
        "created_at": "2026-04-16T15:00:00-06:00"
    },
    "message": "Token generado. Guárdelo ahora | no se mostrará nuevamente.",
    "errors": null
}
 

Example response (401, No autenticado):


{
    "success": false,
    "data": null,
    "message": "Unauthenticated.",
    "errors": null
}
 

Request      

POST api/v1/public/tokens

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
bearer_token   string     

Token API de integración en texto plano. Se muestra una única vez. Guárdelo de forma segura | no hay forma de recuperarlo. Use este token en el header Authorization: Bearer {token} para todas las operaciones automatizadas (POS, ERP, e-commerce).

created_at   string     

Fecha de creación del token (ISO 8601).

Solicitud de Upgrade

Solicitar upgrade al plan Integrador.

requires authentication

Envia un correo al usuario con las instrucciones de pago y notifica al equipo de Almendro para procesar la solicitud. No requiere cuerpo en el request | la identidad del contribuyente se obtiene del token de autenticacion.

Si el contribuyente ya tiene el plan Integrador activo, retorna 422.

Este endpoint tiene rate limit de 1 solicitud por hora para evitar envios duplicados.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/upgrade-request" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/upgrade-request"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/upgrade-request';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Solicitud enviada exitosamente):


{
    "success": true,
    "data": {
        "email_sent_to": "usuario@empresa.cr"
    },
    "message": "Solicitud enviada. Revisá tu correo para las instrucciones de pago.",
    "errors": null
}
 

Example response (404, Sin contribuyente asociado):


{
    "success": false,
    "data": null,
    "message": "No se encontró un contribuyente asociado a esta cuenta.",
    "errors": null
}
 

Example response (422, Ya tiene plan Integrador):


{
    "success": false,
    "data": null,
    "message": "Tu cuenta ya tiene el plan Integrador activo.",
    "errors": null
}
 

Request      

POST api/v1/public/upgrade-request

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Otros

POST api/v1/public/item-shares

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/item-shares" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"to_contributor_id\": \"6ff8f7f6-1eb3-3525-be4a-3932c805afed\",
    \"item_ids\": [
        \"6b72fe4a-5b40-307c-bc24-f79acf9a1bb9\"
    ],
    \"message\": \"m\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/item-shares"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "to_contributor_id": "6ff8f7f6-1eb3-3525-be4a-3932c805afed",
    "item_ids": [
        "6b72fe4a-5b40-307c-bc24-f79acf9a1bb9"
    ],
    "message": "m"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/item-shares';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'to_contributor_id' => '6ff8f7f6-1eb3-3525-be4a-3932c805afed',
            'item_ids' => [
                '6b72fe4a-5b40-307c-bc24-f79acf9a1bb9',
            ],
            'message' => 'm',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/item-shares

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

to_contributor_id   string     

El campo value debe ser un UUID válido. Example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed

item_ids   string[]  optional    

El campo value debe ser un UUID válido.

message   string  optional    

El campo value no puede tener más de 500 caracteres. Example: m

GET api/v1/public/item-shares/sent

requires authentication

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/item-shares/sent" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/item-shares/sent"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/item-shares/sent';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/item-shares/sent

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/public/item-shares/received

requires authentication

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/item-shares/received" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/item-shares/received"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/item-shares/received';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/item-shares/received

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET api/v1/public/item-shares/{id}

requires authentication

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/item-shares/architecto" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/item-shares/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/item-shares/architecto';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/item-shares/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the item share. Example: architecto

GET api/v1/public/item-shares/{id}/accept-preview

requires authentication

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/item-shares/architecto/accept-preview" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/item-shares/architecto/accept-preview"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/item-shares/architecto/accept-preview';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/item-shares/{id}/accept-preview

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the item share. Example: architecto

POST api/v1/public/item-shares/{id}/accept

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/item-shares/architecto/accept" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"lines\": [
        {
            \"line_id\": 16,
            \"action\": \"architecto\",
            \"internal_code\": \"ngzmiyvdljnikhwa\"
        }
    ]
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/item-shares/architecto/accept"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "lines": [
        {
            "line_id": 16,
            "action": "architecto",
            "internal_code": "ngzmiyvdljnikhwa"
        }
    ]
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/item-shares/architecto/accept';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'lines' => [
                [
                    'line_id' => 16,
                    'action' => 'architecto',
                    'internal_code' => 'ngzmiyvdljnikhwa',
                ],
            ],
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/item-shares/{id}/accept

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the item share. Example: architecto

Body Parameters

lines   object[]  optional    
line_id   integer     

Example: 16

action   string     

Example: architecto

internal_code   string  optional    

El campo value no puede tener más de 20 caracteres. Example: ngzmiyvdljnikhwa

POST api/v1/public/item-shares/{id}/reject

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/item-shares/architecto/reject" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/item-shares/architecto/reject"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/item-shares/architecto/reject';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/item-shares/{id}/reject

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the item share. Example: architecto

POST api/v1/public/item-shares/{id}/cancel

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/item-shares/architecto/cancel" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/item-shares/architecto/cancel"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/item-shares/architecto/cancel';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/item-shares/{id}/cancel

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the item share. Example: architecto

Lista los contribuyentes cuyo buzón puede ver/operar el autenticado: él mismo más los clientes sobre los que tiene un grant de certificado activo. Alimenta el selector de contribuyente del buzón y de la carga.

requires authentication

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/inbox/accessible-contributors" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/inbox/accessible-contributors"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/inbox/accessible-contributors';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/inbox/accessible-contributors

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Estado de la recepción automática de facturas por correo (F2).

requires authentication

enabled: si los XML dirigidos a su cédula que llegan a facturas@almendro.cr se cargan solos a su buzón. quarantined: cuántos comprobantes esperan en cuarentena (72 horas) por tener la recepción apagada; se cargan si la reactiva a tiempo.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/inbox/email-reception" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/inbox/email-reception"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/inbox/email-reception';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/inbox/email-reception

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
enabled   boolean     

Recepción automática encendida.

quarantined   integer     

Comprobantes vigentes en cuarentena.

Enciende o apaga la recepción automática de facturas por correo (F2).

requires authentication

Al APAGARLA, los comprobantes que lleguen dirigidos a su cédula quedan en cuarentena 72 horas en lugar de cargarse (no consumen su pozo). Al ENCENDERLA, lo que esté en cuarentena vigente se carga automáticamente.

Example request:
curl --request PATCH \
    "https://fe.almendro.cr/api/v1/public/inbox/email-reception" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"enabled\": true
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/inbox/email-reception"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "enabled": true
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/inbox/email-reception';
$response = $client->patch(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'enabled' => true,
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

PATCH api/v1/public/inbox/email-reception

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

enabled   boolean     

Encender (true) o apagar (false). Example: true

PATCH api/v1/public/inbox/{key}/annotation

requires authentication

Example request:
curl --request PATCH \
    "https://fe.almendro.cr/api/v1/public/inbox/56425593142326822820492192294472586671521743076933/annotation" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"alias\": \"b\",
    \"notes\": \"n\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/inbox/56425593142326822820492192294472586671521743076933/annotation"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "alias": "b",
    "notes": "n"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/inbox/56425593142326822820492192294472586671521743076933/annotation';
$response = $client->patch(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'alias' => 'b',
            'notes' => 'n',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

PATCH api/v1/public/inbox/{key}/annotation

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

key   string     

Example: 56425593142326822820492192294472586671521743076933

Body Parameters

alias   string  optional    

El campo value no puede tener más de 120 caracteres. Example: b

notes   string  optional    

El campo value no puede tener más de 1000 caracteres. Example: n

"No mostrar más" | posponer un recordatorio de puesta a punto por 6 meses.

requires authentication

Solo disponible para integradores que gestionan al menos un contribuyente con certificado de producción vigente (facturan por sus clientes y no necesitan su propio .p12/perfil). Para el resto responde 422.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/reminders/architecto/snooze" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/reminders/architecto/snooze"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/reminders/architecto/snooze';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/reminders/{type}/snooze

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

type   string     

Example: architecto

Descarga el PDF de la factura (FE) asociada a un pago.

requires authentication

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/subscription/payments/e507fb2b94-c9e62d073743f8140a658fd5f/pdf" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/payments/e507fb2b94-c9e62d073743f8140a658fd5f/pdf"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/payments/e507fb2b94-c9e62d073743f8140a658fd5f/pdf';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/subscription/payments/{id}/pdf

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the payment. Example: e507fb2b94-c9e62d073743f8140a658fd5f

Descarga el XML firmado de la factura (FE) asociada a un pago.

requires authentication

La FE la emite Almendro HACIA el contribuyente, así que el voucher es del emisor; se autoriza por el PAGO (que sí es del contribuyente).

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/subscription/payments/e507fb2b94-c9e62d073743f8140a658fd5f/xml" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/payments/e507fb2b94-c9e62d073743f8140a658fd5f/xml"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/payments/e507fb2b94-c9e62d073743f8140a658fd5f/xml';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/subscription/payments/{id}/xml

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the payment. Example: e507fb2b94-c9e62d073743f8140a658fd5f

POST api/v1/public/discount-codes/validate

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/discount-codes/validate" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"code\": \"b\",
    \"plan_code\": \"architecto\",
    \"billing_cycle\": \"architecto\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/discount-codes/validate"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "code": "b",
    "plan_code": "architecto",
    "billing_cycle": "architecto"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/discount-codes/validate';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'code' => 'b',
            'plan_code' => 'architecto',
            'billing_cycle' => 'architecto',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/discount-codes/validate

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

code   string  optional    

El campo value no puede tener más de 40 caracteres. Example: b

plan_code   string     

The code of an existing record in the plans table. Example: architecto

billing_cycle   string     

Example: architecto

Combinaciones plan/ciclo que tienen una promoción activa para este contribuyente. El checkout muestra el campo de descuento solo ahí.

requires authentication

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/discount-codes/availability" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/discount-codes/availability"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/discount-codes/availability';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/discount-codes/availability

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Suscribirse / subir de plan COBRANDO la tarjeta tokenizada (server-to-server).

requires authentication

Requiere tarjeta guardada (si no, el front manda primero a "agregar tarjeta"). Si el contribuyente ya está en un plan de pago vigente del mismo ciclo, cobra solo la diferencia prorrateada por lo que resta del período y MANTIENE la fecha de aniversario; si viene de Gratis/sin plan, cobra el precio completo y arranca el período desde hoy. Las renovaciones siguen con subscription:charge-due.

Solo SUBIDAS: para bajar de plan se usa la baja programada (downgrade).

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/subscription/subscribe-tokenized" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"plan_code\": \"architecto\",
    \"billing_cycle\": \"architecto\",
    \"discount_code\": \"n\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/subscribe-tokenized"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "plan_code": "architecto",
    "billing_cycle": "architecto",
    "discount_code": "n"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/subscribe-tokenized';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'plan_code' => 'architecto',
            'billing_cycle' => 'architecto',
            'discount_code' => 'n',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/subscription/subscribe-tokenized

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

plan_code   string     

The code of an existing record in the plans table. Example: architecto

billing_cycle   string     

Example: architecto

discount_code   string  optional    

El campo value no puede tener más de 40 caracteres. Example: n

Sesión para el SDK V2 (alta de tarjeta embebida).

requires authentication

Devuelve el token del SDK (loginSdk) y los parámetros para inicializar Tilopay.InitTokenize() en el frontend. El token de la tarjeta vuelve del lado cliente y se guarda con saveCard(). El email es el del contribuyente: el cobro posterior se hace con ese mismo par email + token.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/subscription/card-session" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/card-session"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/card-session';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/subscription/card-session

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Guarda el token de la tarjeta tokenizada por el SDK contra el contribuyente.

requires authentication

El email almacenado es el mismo que usó cardSession() (Tilopay rutea el cobro por email + token).

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/subscription/save-card" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"token\": \"b\",
    \"last4\": \"ngzm\",
    \"brand\": \"iyvdljnikhwaykcm\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/save-card"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "token": "b",
    "last4": "ngzm",
    "brand": "iyvdljnikhwaykcm"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/save-card';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'token' => 'b',
            'last4' => 'ngzm',
            'brand' => 'iyvdljnikhwaykcm',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/subscription/save-card

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

token   string     

El campo value no puede tener más de 191 caracteres. Example: b

last4   string  optional    

El campo value no puede tener más de 4 caracteres. Example: ngzm

brand   string  optional    

El campo value no puede tener más de 20 caracteres. Example: iyvdljnikhwaykcm

Sesión de PAGO con el SDK (cobro único que guarda la tarjeta, `Init({subscription:1})`).

requires authentication

Se usa cuando el contribuyente NO tiene tarjeta guardada: el primer pago cobra y guarda la tarjeta en un solo cargo (modelo Uber). Crea un Payment pendiente con la intención (suscribirse o retención) y devuelve los params del SDK.

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/subscription/sdk-pay-session" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"purpose\": \"architecto\",
    \"plan_code\": \"architecto\",
    \"billing_cycle\": \"architecto\",
    \"discount_code\": \"n\",
    \"beneficiary_contributor_id\": \"6b72fe4a-5b40-307c-bc24-f79acf9a1bb9\",
    \"tier_code\": \"miyvdl\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/sdk-pay-session"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "purpose": "architecto",
    "plan_code": "architecto",
    "billing_cycle": "architecto",
    "discount_code": "n",
    "beneficiary_contributor_id": "6b72fe4a-5b40-307c-bc24-f79acf9a1bb9",
    "tier_code": "miyvdl"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/sdk-pay-session';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'purpose' => 'architecto',
            'plan_code' => 'architecto',
            'billing_cycle' => 'architecto',
            'discount_code' => 'n',
            'beneficiary_contributor_id' => '6b72fe4a-5b40-307c-bc24-f79acf9a1bb9',
            'tier_code' => 'miyvdl',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/subscription/sdk-pay-session

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

purpose   string     

Example: architecto

plan_code   string  optional    

The code of an existing record in the plans table. Example: architecto

billing_cycle   string  optional    

Example: architecto

discount_code   string  optional    

El campo value no puede tener más de 40 caracteres. Example: n

beneficiary_contributor_id   string  optional    

Retención por un cliente gestionado (opcional). Default: uno mismo. El campo value debe ser un UUID válido. Example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9

tier_code   string  optional    

Pozo del programa Distribuidor Autorizado. El campo value no puede tener más de 10 caracteres. Example: miyvdl

Confirma un pago hecho con el SDK: verifica con `consult`, guarda la tarjeta y ejecuta la compra (activar plan o crear la extensión de retención). No cobra de nuevo (el SDK ya cobró).

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/subscription/sdk-pay-confirm" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"order\": \"b\",
    \"token\": \"n\",
    \"last4\": \"gzmi\",
    \"brand\": \"yvdljnikhwaykcmy\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/sdk-pay-confirm"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "order": "b",
    "token": "n",
    "last4": "gzmi",
    "brand": "yvdljnikhwaykcmy"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/sdk-pay-confirm';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'order' => 'b',
            'token' => 'n',
            'last4' => 'gzmi',
            'brand' => 'yvdljnikhwaykcmy',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/subscription/sdk-pay-confirm

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

order   string     

El campo value no puede tener más de 191 caracteres. Example: b

token   string     

El campo value no puede tener más de 191 caracteres. Example: n

last4   string  optional    

El campo value no puede tener más de 4 caracteres. Example: gzmi

brand   string  optional    

El campo value no puede tener más de 20 caracteres. Example: yvdljnikhwaykcmy

Elimina la tarjeta guardada. Intenta el `card-remove` en Tilopay (si el comercio tuviera el permiso) y, pase o no, suelta el token de nuestro lado: sin token no se cobra más.

requires authentication

Example request:
curl --request DELETE \
    "https://fe.almendro.cr/api/v1/public/subscription/card" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/subscription/card"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/subscription/card';
$response = $client->delete(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

DELETE api/v1/public/subscription/card

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Compra la hora de onboarding COBRANDO la tarjeta guardada (sin salir del portal). Mismo patrón que la retención tokenizada: chargeToken + crédito inmediato + FE. Sin tarjeta guardada responde 409 (el front abre el SDK).

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/onboarding/purchase-tokenized" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/onboarding/purchase-tokenized"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/onboarding/purchase-tokenized';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/onboarding/purchase-tokenized

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Inicia el checkout de la retención para el contribuyente autenticado.

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/retention-extension/purchase" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/retention-extension/purchase"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/retention-extension/purchase';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/retention-extension/purchase

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

El contribuyente cobra su propia retención con la tarjeta guardada.

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/retention-extension/charge-tokenized" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/retention-extension/charge-tokenized"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/retention-extension/charge-tokenized';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/retention-extension/charge-tokenized

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Desactiva la auto-renovación: la retención sigue ACTIVA hasta el fin de su período y luego no se renueva (charge-due la omite).

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/retention-extension/cancel" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/retention-extension/cancel"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/retention-extension/cancel';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/retention-extension/cancel

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Reactiva la auto-renovación de una retención cancelada pero aún vigente.

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/retention-extension/resume" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/retention-extension/resume"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/retention-extension/resume';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/retention-extension/resume

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/v1/public/api-access/purchase

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/api-access/purchase" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/api-access/purchase"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/api-access/purchase';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/api-access/purchase

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/v1/public/api-access/charge-tokenized

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/api-access/charge-tokenized" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/api-access/charge-tokenized"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/api-access/charge-tokenized';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/api-access/charge-tokenized

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Desactiva la auto-renovación: el acceso sigue ACTIVO hasta el fin del período pagado y luego no se renueva (charge-due lo omite).

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/api-access/cancel" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/api-access/cancel"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/api-access/cancel';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/api-access/cancel

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Reactiva la auto-renovación de un add-on cancelado pero aún vigente.

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/api-access/resume" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/api-access/resume"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/api-access/resume';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/api-access/resume

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

GET /distributor/info

requires authentication

Estado del programa para la sección de Mi Plan: inscripción, niveles a la venta con el precio aplicable (reposición incluida si califica), y el resumen de pozos propios si ya es distribuidor.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/distributor/info" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/distributor/info"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/distributor/info';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/distributor/info

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

POST api/v1/public/distributor/purchase

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/distributor/purchase" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"tier_code\": \"bngzmi\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/distributor/purchase"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "tier_code": "bngzmi"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/distributor/purchase';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'tier_code' => 'bngzmi',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/distributor/purchase

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

tier_code   string     

El campo value no puede tener más de 10 caracteres. Example: bngzmi

POST api/v1/public/distributor/charge-tokenized

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/distributor/charge-tokenized" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"tier_code\": \"bngzmi\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/distributor/charge-tokenized"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "tier_code": "bngzmi"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/distributor/charge-tokenized';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'tier_code' => 'bngzmi',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/distributor/charge-tokenized

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

tier_code   string     

El campo value no puede tener más de 10 caracteres. Example: bngzmi

GET api/v1/public/managed-sales

requires authentication

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/managed-sales" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/managed-sales"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/managed-sales';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

GET api/v1/public/managed-sales

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Registro PRIVADO de la venta del gestor (lo que él cobra a su cliente): monto, moneda y modo de pago. El admin nunca lo ve.

requires authentication

Example request:
curl --request PATCH \
    "https://fe.almendro.cr/api/v1/public/managed-sales/564" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"sale_amount\": 1,
    \"sale_currency\": \"USD\",
    \"sale_payment_method\": \"architecto\"
}"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/managed-sales/564"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "sale_amount": 1,
    "sale_currency": "USD",
    "sale_payment_method": "architecto"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/managed-sales/564';
$response = $client->patch(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
        'json' => [
            'sale_amount' => 1,
            'sale_currency' => 'USD',
            'sale_payment_method' => 'architecto',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

PATCH api/v1/public/managed-sales/{activationId}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

activationId   string     

Example: 564

Body Parameters

sale_amount   number  optional    

El campo value debe ser al menos 0. El campo value no puede ser mayor a 99999999. Example: 1

sale_currency   string  optional    

Example: USD

Must be one of:
  • CRC
  • USD
sale_payment_method   string  optional    

Example: architecto

Inicia el checkout de la retención que un gestor paga para un cliente gestionado. El gestor autenticado paga; el cliente es el beneficiario.

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/my-contributors/e507fb2b94-c9e62d073743f8140a658fd5f/retention-extension" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/e507fb2b94-c9e62d073743f8140a658fd5f/retention-extension"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/e507fb2b94-c9e62d073743f8140a658fd5f/retention-extension';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/my-contributors/{id}/retention-extension

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the my contributor. Example: e507fb2b94-c9e62d073743f8140a658fd5f

Un gestor cobra la retención de un cliente gestionado con SU tarjeta guardada.

requires authentication

Example request:
curl --request POST \
    "https://fe.almendro.cr/api/v1/public/my-contributors/e507fb2b94-c9e62d073743f8140a658fd5f/retention-extension/charge-tokenized" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/my-contributors/e507fb2b94-c9e62d073743f8140a658fd5f/retention-extension/charge-tokenized"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/my-contributors/e507fb2b94-c9e62d073743f8140a658fd5f/retention-extension/charge-tokenized';
$response = $client->post(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Request      

POST api/v1/public/my-contributors/{id}/retention-extension/charge-tokenized

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

The ID of the my contributor. Example: e507fb2b94-c9e62d073743f8140a658fd5f

Perfil del contribuyente

Entitlement del plan

requires authentication

Devuelve el plan vigente del contribuyente autenticado con TODOS sus límites resueltos (override por contribuyente > valor del plan; null = sin tope). Pensado para que integraciones (POS, canal e-commerce) lo cacheen y apliquen los límites localmente.

Example request:
curl --request GET \
    --get "https://fe.almendro.cr/api/v1/public/entitlement" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://fe.almendro.cr/api/v1/public/entitlement"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());
$client = new \GuzzleHttp\Client();
$url = 'https://fe.almendro.cr/api/v1/public/entitlement';
$response = $client->get(
    $url,
    [
        'headers' => [
            'Authorization' => 'Bearer {YOUR_AUTH_KEY}',
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ],
    ]
);
$body = $response->getBody();
print_r(json_decode((string) $body));

Example response (200, Entitlement resuelto):


{
    "success": true,
    "data": {
        "plan": {
            "code": "professional",
            "name": "Profesional",
            "billing_mode": "annual_or_monthly"
        },
        "features": [
            "api_access",
            "receiver_module",
            "webhooks"
        ],
        "limits": {
            "monthly_voucher_limit": 500,
            "max_users": 5,
            "max_clients": 1500,
            "max_items": 3000,
            "max_branches": 5,
            "max_terminals_per_branch": 5,
            "max_pos_items": 15000,
            "max_pos_cashiers_per_terminal": 10,
            "max_channel_pages": 15
        }
    },
    "message": "Entitlement del plan (límites resueltos con overrides).",
    "errors": null
}
 

Request      

GET api/v1/public/entitlement

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Response

Response Fields

data   object     
limits   object     

Cada límite YA resuelto: override del contribuyente sobre el valor del plan; null = sin tope.