Ir al contenido

Boletas de Honorarios de Terceros (BHET)

Las Boletas de Honorarios de Terceros son documentos tributarios que emiten las empresas cuando contratan servicios de terceros (personas naturales o jurídicas). A diferencia de las BHE donde el emisor presta el servicio, en las BHET el emisor es quien contrata y paga, mientras que el tercero es quien presta el servicio.


AspectoBHEBHET
EmisorPersona natural (presta servicio)Empresa (contrata servicio)
ReceptorDestinatario (recibe servicio)Tercero (presta servicio)
RetenciónPPM — tasa vigente en GET /global/ppmRetención — tasa vigente en GET /global/bhet-retencion
Sin receptorsinDestinatario soportadoSiempre requiere tercero
Campo montomontoLiquidomontoNeto
Campo impuestoppmimpuesto

Antes de emitir una BHET necesitas:

  1. Un Emisor Tributario activo con clave tributaria delegada y servicio BOLETAS_TERCEROS habilitado
  2. El emisorTributarioId de ese emisor — va en el cuerpo de toda emisión
  3. Una API Key con uno de estos roles: ADMIN, SUPER-ADMIN, SII-EMISOR-BHET, o FULL-API
  4. Los datos del tercero (siempre requerido, no existe opción sin tercero)

El sistema soporta 3 modos de emisión que permiten balancear entre respuesta inmediata y tolerancia a fallas del SII.

ModoDescripciónRespuestaRecomendado
SINC_COMPLETOTodo síncrono: emitir + PDF interno + PDF SIIBHET completa con ambos PDFsCuando necesitas todo inmediato
SINC_PARCIALEmitir + PDF interno síncronos, PDF SII en backgroundBHET con folio + PDF internoDefault recomendado
ASINCRONOSolo valida y encola. Todo en backgroundID de intento para consultaAlta tolerancia a fallas

Tu App Redcumbre SII
│ │ │
│── POST /bhet ────────────▶│ │
│ (modo: SINC_PARCIAL) │ │
│ │── Emitir BHET ────────▶│
│ │◀── folioSii ──────────│
│ │ │
│ │── Genera PDF interno │
│ │ │
│◀── Response ──────────────│ │
│ (folioSii + pdfInternoUrl) │
│ (estadoPdfSii: PENDIENTE) │
│ │ │
│ [Background] │
│ │── Descarga PDF SII ───▶│
│ │◀── PDF ───────────────│
│ │ │
│◀── Webhook ───────────────│ │
│ (bhet.emision_terminada)│ │

Ventajas:

  • Respuesta rápida con folio y PDF personalizado
  • PDF SII se descarga en background con reintentos automáticos (hasta 7 días)
  • Si el SII tiene problemas, ya tienes el folio y un PDF válido

Tu App Redcumbre SII
│ │ │
│── POST /bhet ────────────▶│ │
│ (modo: ASINCRONO) │ │
│ │── Valida datos │
│ │── Encola emisión │
│ │ │
│◀── Response ──────────────│ │
│ (intentoId, estado: PENDIENTE) │
│ │ │
│ [Background] │
│ │── Emitir BHET ────────▶│
│ │◀── folioSii ──────────│
│ │── Genera PDF interno │
│ │ │
│◀── Webhook ───────────────│ │
│ (bhet.emitida) │ │

Ventajas:

  • Respuesta instantánea (solo valida y encola)
  • Reintentos automáticos si el SII falla, durante 18–22,6 horas (10 intentos con espaciado creciente desde 5 min, máximo 6 h entre uno y otro). Ver la curva completa
  • Ideal para procesos batch o cuando no necesitas el resultado inmediato

Consultar estado:

Ventana de terminal
GET /{tenantSlug}/bhet/intento/{intentoId}

Existen 4 formas mutuamente excluyentes de especificar el tercero:

ModoCampoDescripciónCobra SII Lookup
1terceroIdID o RUT de contribuyente guardado en sistemaNo
2terceroDatos completos on-the-flyNo
3siiLookupObjeto lookup SII ya obtenidoNo
4lookupRutSolo RUT, sistema ejecuta lookup al SIISí

¿Tienes datos completos del tercero?
├── SÍ → ¿Están guardados en el sistema?
│ ├── SÍ → Usar Modo 1 (terceroId)
│ └── NO → Usar Modo 2 (tercero)
└── NO → ¿Ya hiciste lookup al SII previamente?
├── SÍ → Usar Modo 3 (siiLookup)
└── NO → Usar Modo 4 (lookupRut) ⚠️ Cobra SII Lookup

El tercero ya está guardado en el sistema (ContribuyenteMaestro). Puedes usar el ID o el RUT.

{
"emisorTributarioId": "tu-emisor-tributario-id",
"terceroId": "78012039-8",
"prestaciones": [
{ "descripcion": "Servicio de consultoría", "valor": "150000" }
]
}

Pasas todos los datos manualmente sin necesidad de lookup.

{
"emisorTributarioId": "tu-emisor-tributario-id",
"tercero": {
"rut": "78012039-8",
"nombres": "FIRERAISE SPA",
"domicilio": "Av. Providencia 1234, Of. 501",
"codigoRegion": "13",
"codigoComuna": "13101",
"email": "contacto@fireraise.cl"
},
"prestaciones": [
{ "descripcion": "Desarrollo de software", "valor": "250000" }
]
}

Campos del objeto tercero:

CampoTipoRequeridoDescripción
rutstringSíRUT del tercero (formato 12345678-9)
nombresstringSíRazón social o nombre completo
domiciliostringSíDirección completa
codigoRegionstringSíCódigo de región SII (ej: "13")
codigoComunastringSíCódigo de comuna SII (ej: "13101")
emailstringNoEmail para envío de boleta

Ya tienes el resultado de un lookup previo al SII.

{
"emisorTributarioId": "tu-emisor-tributario-id",
"siiLookup": {
"rut": "78012039-8",
"razonSocial": "FIRERAISE SPA",
"direcciones": [
{
"direccion": "AV PROVIDENCIA 1234 OF 501",
"codigoComuna": "13101",
"comuna": "SANTIAGO"
}
],
"email": "contacto@fireraise.cl"
},
"prestaciones": [
{ "descripcion": "Consultoría estratégica", "valor": "180000" }
]
}

Solo pasas el RUT y el sistema ejecuta el lookup al SII.

{
"emisorTributarioId": "tu-emisor-tributario-id",
"lookupRut": "78012039-8",
"prestaciones": [
{ "descripcion": "Asesoría legal", "valor": "200000" }
]
}

Ventana de terminal
POST /{tenantSlug}/bhet
Content-Type: application/json
Authorization: Bearer tu-api-key
{
"emisorTributarioId": "tu-emisor-tributario-id",
"tercero": {
"rut": "78012039-8",
"nombres": "FIRERAISE SPA",
"domicilio": "Av. Providencia 1234, Of. 501",
"codigoRegion": "13",
"codigoComuna": "13101",
"email": "contacto@fireraise.cl"
},
"prestaciones": [
{ "descripcion": "Servicio de consultoría en sistemas", "valor": "150000" },
{ "descripcion": "Desarrollo de software", "valor": "75000" }
],
"modo": "SINC_PARCIAL",
"enviarBoletaPorEmail": true,
"correlationId": "mi-referencia-123"
}

Campos del Request:

CampoTipoRequeridoDescripción
emisorTributarioIdstringSíID del emisor tributario
prestacionesarraySíLista de servicios (mín 1)
modoenumNoSINC_COMPLETO, SINC_PARCIAL (default), ASINCRONO
enviarBoletaPorEmailbooleanNoEnviar PDF al email del tercero
templateIdstringNoTemplate con que se representa el PDF interno de esta boleta. Si no lo envías, se usa el de tu empresa (ver Elegir el template del PDF interno)
metadataAdicionalobjectNoPares clave-valor (valores string) que se guardan junto a la boleta e imprime el PDF interno. No viaja al SII (ver Metadata para la representación impresa)
correlationIdstringNoID de correlación para tracking. No es una clave: puedes repetirlo entre boletas distintas
idempotencyKeystringNoClave de idempotencia, máx. 128 caracteres. Protege contra emisiones duplicadas por reintentos (ver Idempotencia)
fechaEmisionstringNoFecha de emisión personalizada (ver sección siguiente)

Si tu sistema puede reintentar una emisión —por timeout, error de red o doble click— usa idempotencyKey para que un reintento no genere una segunda boleta ante el SII.

{
"emisorTributarioId": "tu-emisor-tributario-id",
"idempotencyKey": "orden-compra-4471",
"tercero": { "rut": "12345678-9", "nombres": "Juan Pérez", "domicilio": "Av. Principal 123", "codigoRegion": "13", "codigoComuna": "13101" },
"prestaciones": [{ "descripcion": "Servicio de diseño", "valor": "150000" }]
}

Cómo funciona:

  • El alcance de la clave es por emisor tributario: dos emisores pueden usar el mismo valor sin colisionar.
  • Si repites la clave y ya hay una boleta emitida con ella, se retorna esa misma boleta —mismo id, mismo folioSii— sin volver a emitir ante el SII ni consumir un folio nuevo.
  • En ese caso se vuelve a disparar el webhook bhet.emitida, porque si repetiste la clave lo más probable es que no hayas recibido la notificación anterior.
  • Si la emisión falló, la clave no queda reservada: puedes corregir el dato que el SII rechazó y reenviar con la misma clave.
  • Si la emisión quedó con resultado incierto (SII_CONFIRMACION_INCIERTA: el SII no respondió al paso que emite y no sabemos si la boleta salió), la clave sí queda reservada. Un reintento con la misma clave te devuelve esa misma boleta en ERROR, sin volver a emitir ni disparar el webhook bhet.emitida. Verifica en el SII si la boleta existe: si no salió, reintenta con una clave nueva.
  • Dos solicitudes con la misma clave que llegan al mismo tiempo —por ejemplo, tu reintento mientras la primera sigue esperando al SII— se resuelven en orden: la segunda espera el turno del emisor, encuentra la boleta de la primera y te la devuelve en vez de emitir otra.
  • Si la boleta se anula, la clave se libera y puede volver a usarse.
  • En modo ASINCRONO la protección cubre también la emisión en vuelo: si reintentas mientras la primera todavía está en la cola o entre reintentos, recibes el intentoId original en vez de encolar una segunda. Con la ventana de reintentos de 18–22,6 h, esa protección puede estar activa durante horas — usa idempotencyKey si tu sistema reintenta por su cuenta.

Por defecto, las boletas se emiten con la fecha actual del servidor. Sin embargo, en casos excepcionales puedes especificar una fecha anterior usando el campo fechaEmision.

El campo acepta formato ISO YYYY-MM-DD:

{
"emisorTributarioId": "tu-emisor-tributario-id",
"terceroId": "78012039-8",
"prestaciones": [
{ "descripcion": "Servicio de consultoría", "valor": "150000" }
],
"fechaEmision": "2025-12-01"
}
RestricciónDescripción
No futurasLa fecha no puede ser posterior a hoy
Sin validación de plazosNo se valida contra plazos legales del SII
Fecha oficialEl SII registra esta fecha como fecha oficial del documento
Campo en PDFValor
Fecha de EmisiónLa fecha especificada (o fecha actual si no se especificó)
Fecha de ImpresiónSiempre la fecha actual al generar/regenerar el PDF

Esto permite identificar cuándo se prestó el servicio vs cuándo se generó el documento físico.

Campos de prestaciones[]:

Cada prestación puede especificarse de dos formas mutuamente excluyentes:

ModoCampos PermitidosCampos Prohibidos
Item libredescripcion, valorproductoId
Producto del catálogoproductoId, cantidad, descuentoPorcentajedescripcion, valor, precioUnitario, unidadMedida, codigoSku

Cuándo se permite precioUnitario con productos del catálogo:

Condición del Producto¿Permite precioUnitario?
esquemaPrecio: SIN_PRECIOSí (obligatorio)
permisoEdicion: LIBRESí
permisoEdicion: DENTRO_RANGO_DESCUENTOSí (dentro del rango)
permisoEdicion: NO_PERMITIDANo

Especifica descripcion y valor directamente. Ideal para servicios únicos o personalizados.

{
"prestaciones": [
{ "descripcion": "Servicio de consultoría", "valor": "150000" }
]
}

Usa productoId para referenciar un producto configurado en el catálogo. El sistema calcula precios, descuentos y cantidades automáticamente.

{
"prestaciones": [
{
"productoId": "clxyz123abc",
"cantidad": 3,
"descuentoPorcentaje": 10
}
]
}

Response: Modo Síncrono (SINC_COMPLETO / SINC_PARCIAL)

Sección titulada «Response: Modo Síncrono (SINC_COMPLETO / SINC_PARCIAL)»
{
"success": true,
"data": {
"modo": "SINC_PARCIAL",
"descargas": {
"pdfInterno": "https://app.redcumbre.cl/acme/tributario/boletas-terceros/cm5abc123def456/descargar?tipo=interno",
"pdfSii": "https://app.redcumbre.cl/acme/tributario/boletas-terceros/cm5abc123def456/descargar?tipo=sii"
},
"bhet": {
"id": "cm5abc123def456",
"folioSii": "12345678",
"codigoBarras": "1234567800388FB4702C",
"estado": "EMITIDA",
"estadoPdfSii": "PENDIENTE",
"modoEmision": "SINC_PARCIAL",
"pdfInternoUrl": "https://app.redcumbre.cl/p/boleta/eyJhbGciOiJIUzI1NiJ9...",
"montoBruto": 150000,
"tasaImpuesto": 15.25,
"impuesto": 22875,
"montoNeto": 127125,
"terceroNombreSii": "FIRERAISE SPA"
},
"correlationId": "mi-referencia-123",
"mensaje": "BHET emitida exitosamente",
"pdfInternoUrl": "https://app.redcumbre.cl/p/boleta/eyJhbGciOiJIUzI1NiJ9..."
},
"sandbox": false
}

El tasaImpuesto del ejemplo —y por lo tanto el impuesto y el montoNeto— corresponde a la tasa vigente en 2026 (15,25%). No la copies como constante: viene en cada respuesta, y también puedes pedirla por adelantado en GET /global/bhet-retencion (ver Cálculo de montos).

SINC_COMPLETO responde lo mismo, con cuatro diferencias: modo y bhet.modoEmision valen SINC_COMPLETO; mensaje deja de ser "BHET emitida exitosamente" y pasa a describir el estado del respaldo ("BHET emitida exitosamente con respaldo SII disponible" si alcanzó a quedar listo, "BHET emitida. El respaldo SII se está generando (se notificará via webhook)" si no); y si alcanzó a quedar listo dentro del request aparece data.pdfSiiUrl (también en data.bhet.pdfSiiUrl) con bhet.estadoPdfSii: "DISPONIBLE". Si no alcanzó, queda en PENDIENTE y se genera en background, igual que en SINC_PARCIAL.


El modo ASINCRONO valida y encola: la respuesta llega antes de que la boleta exista. Trae un intentoId para consultar el avance.

{
"success": true,
"data": {
"modo": "ASINCRONO",
"intentoId": "cm5xyz789abc123",
"correlationId": "mi-referencia-123",
"estado": "PENDIENTE",
"mensaje": "Emisión encolada para procesamiento. Consulte el estado con GET /bhet/intento/{intentoId}"
},
"sandbox": false
}

El bloque descargas no viene en esta respuesta: todavía no existe el id de la boleta, y las URLs se calculan a partir de él. Llega en GET /bhet/intento/{intentoId} una vez emitida, y en el webhook bhet.emitida.


Cuando el SII no acepta la boleta, la BHET igual se persiste en estado ERROR con su motivo, para que quede consultable y auditable. Cómo te lo comunicamos depende de quién llama:

Quien emiteQué recibe
La aplicación web de Redcumbre (sesión de usuario)201 con success: false — el formulario conserva lo escrito y muestra el motivo
Tu integración (API key)Un status de error HTTP según la causa: 422, 502 o 503

Tu integración nunca recibe un 2xx por una boleta rechazada. Si la respuesta es 2xx, se emitió.

{
"statusCode": 422,
"message": "El tercero no es válido para recibir una boleta de terceros…",
"error": "Unprocessable Entity",
"errorCode": "SII_TERCERO_INVALIDO",
"boletaId": "cm5abc123def456"
}
CampoPara qué sirve
errorCodeLo único que conviene programar: es estable y clasifica la causa
messageTexto legible, apto para mostrarle a tu usuario
boletaIdEl id de la fila que sí se persistió: GET /{tenantSlug}/bhet/{boletaId} responde 200 con la boleta en ERROR
StatusSignificaQué hacer
422El SII entendió la solicitud y la rechazó por su contenido o por el estado del contribuyenteNo reintentar sin corregir
502El SII respondió algo que no se puede usar (error interno suyo, sesión caída, servicio degradado)Reintentar con backoff exponencial
503El emisor tiene demasiadas emisiones en curso ante el SII y hay que esperar turnoReintentar después de los segundos del header Retry-After
500Un defecto nuestro. Dos formas, según el errorCodeVer abajo
El 500 tiene dos formas, y una de ellas no se reintenta
Sección titulada «El 500 tiene dos formas, y una de ellas no se reintenta»
errorCodeQué pasóQué hacer
(ausente)La emisión falló con un motivo que la plataforma no tiene clasificado. La boleta quedó persistida en ERRORReportarlo con el boletaId
BHET_EMITIDA_NO_PERSISTIDALa boleta SÍ se emitió ante el SII y la plataforma no pudo guardarla. El cuerpo trae folioSii y codigoBarras en vez de boletaIdNo reintentar: emitirías una segunda boleta real. Reportar el folioSii a soporte
{
"statusCode": 500,
"message": "La boleta se emitió ante el SII con el folio 489, pero la plataforma no pudo guardarla…",
"error": "Internal Server Error",
"errorCode": "BHET_EMITIDA_NO_PERSISTIDA",
"folioSii": "489",
"codigoBarras": "77438768004898E5E12E"
}

Es el único caso en que un error no significa “no se emitió”. Por eso el cuerpo trae el folio: es el documento que ya existe ante el SII.


Para el modo ASINCRONO, consulta el estado del intento:

Ventana de terminal
GET /{tenantSlug}/bhet/intento/{intentoId}
Authorization: Bearer tu-api-key
{
"success": true,
"data": {
"id": "cm5xyz789abc123",
"estado": "EMITIDA",
"bhet": {
"id": "cm5abc123xyz",
"folioSii": "123456789",
"codigoBarras": "ABC123...",
"estado": "EMITIDA",
"estadoPdfSii": "DISPONIBLE",
"pdfInternoUrl": "https://...",
"pdfSiiUrl": "https://...",
"descargas": {
"pdfInterno": "https://app.redcumbre.cl/acme/tributario/boletas-terceros/cm5abc123xyz/descargar?tipo=interno",
"pdfSii": "https://app.redcumbre.cl/acme/tributario/boletas-terceros/cm5abc123xyz/descargar?tipo=sii"
}
},
"intentos": 1,
"ultimoIntento": "2025-12-08T15:30:00Z",
"correlationId": "mi-referencia-123",
"createdAt": "2025-12-08T15:00:00Z",
"updatedAt": "2025-12-08T15:30:00Z"
},
"sandbox": false
}

Acá el intento va en data y la boleta en data.bhet, igual que en la emisión síncrona. Mientras el intento no llegue a EMITIDA, data.bhet no viene.

Estados del intento:

EstadoDescripción
PENDIENTERegistrado en cola, pendiente de procesamiento
PROCESANDOEn proceso de emisión
EMITIDAEmitido exitosamente (tiene BHET asociada)
REINTENTANDOUn intento falló y el sistema va a volver a intentarlo. No es un desenlace: todavía hay algo que esperar
FALLIDAFalló definitivamente: se agotaron los 10 intentos (18–22,6 h) o el error del SII no se recupera reintentando

El sistema genera dos tipos de PDF:

TipoDescripciónDisponibilidad
PDF InternoGenerado con template personalizado del tenantInmediato (modo síncrono)
PDF SIIRespaldo oficial descargado del SIIDepende de disponibilidad del SII

El PDF interno se representa con un template. Si no eliges ninguno, se usa el que tenga configurado tu empresa y, en su defecto, el estándar de la plataforma — que es lo que ocurre hoy si nunca tocaste esto.

Para elegirlo explícitamente, manda templateId en la emisión:

{
"emisorTributarioId": "tu-emisor-tributario-id",
"prestaciones": [{ "descripcion": "Servicio prestado", "valor": "150000" }],
"templateId": "tpl_bhet_002"
}

El identificador queda fijo en la boleta. Eso significa que toda representación posterior de ese documento usa el mismo template, incluido el comprobante con la marca de anulación que se genera después. Un cambio en la configuración de tu empresa NO altera boletas ya emitidas.

Un templateId que no exista, o que pertenezca a otro tipo de documento, responde 400 y la boleta no se emite. No se resuelve en silencio a otro template.

Templates disponibles:

IdentificadorFormatoUso típico
tpl_bhet_001CartaDefault de la plataforma
tpl_bhet_002Rollo 80 mmImpresora térmica de mostrador
tpl_bhet_003Rollo 57 mmImpresora POS

El template con que se generó cada archivo viene en templateId dentro de GET /{tenantSlug}/bhet/{id}/archivos. Tu empresa puede tener templates propios además de estos.

GET /{tenantSlug}/bhet/templates lista los templates que puedes usar en tu empresa —los de la plataforma y los propios—, cuál es el default y qué claves de metadataAdicional imprime cada uno:

{
"success": true,
"data": {
"templates": [
{
"id": "tpl_bhet_001",
"name": "BHET Estándar (Carta)",
"paperFormat": "letter",
"isDefault": true,
"clavesMetadataAdicional": []
}
],
"defaultTemplateId": "tpl_bhet_001"
}
}

Responde lo mismo con la sesión de la interfaz web y con una llave de API. Los templates de BHE (tpl_bhe_*) no sirven para una boleta de terceros: un templateId de BHE responde 400.

metadataAdicional es un objeto de pares clave-valor que se guarda junto a la boleta. No viaja al SII: no forma parte de lo que se emite ni de lo que el SII devuelve. Sirve para que el PDF interno imprima datos que el formato de la boleta no tiene dónde poner y para dejarlos consultables después.

{
"emisorTributarioId": "tu-emisor-tributario-id",
"tercero": { "rut": "13830230-K", "nombres": "JUAN PEREZ GONZALEZ", "domicilio": "LOS PEUMOS 3", "codigoRegion": "13", "codigoComuna": "13123" },
"prestaciones": [{ "descripcion": "Servicio prestado", "valor": "150000" }],
"metadataAdicional": {
"NumOrdenCompra": "OC-12345",
"CentroCosto": "CC-001"
}
}

Los templates de la plataforma (tpl_bhet_001 a tpl_bhet_003) no imprimen ninguna clave: su clavesMetadataAdicional viene vacío. La metadata se imprime con un template propio de tu empresa que declare sus claves; con los de la plataforma queda guardada y consultable.

Lo que conviene saber antes de usarlo:

  • Las claves las define el template, no la API. No hay lista de nombres válidos: lo que mandes se guarda tal cual. Una clave que el template no declara queda guardada, pero no se imprime.
  • Ninguna clave es obligatoria, y mandar metadata nunca hace fallar una emisión que sin ella pasaría.
  • Manda un objeto con valores string. Si metadataAdicional no es un objeto (un texto, un número, un arreglo), se trata como si no la hubieras mandado: la emisión sigue y la respuesta trae null. Dentro del objeto, un valor que no es string no se rechaza ni se convierte: se guarda y vuelve en las respuestas tal como llegó, y el template lo imprime así (un 0 o un false no se imprimen, y un objeto anidado sale ilegible). Convierte tú los valores a texto.
  • Por API, va en la emisión: síncrona y asíncrona, con el mismo campo. En el panel web también acompaña a los borradores, a la emisión de un borrador y al clonado de una boleta o un borrador, que la copia al borrador nuevo. Borradores y clonado son operaciones del panel: no están disponibles con una llave de API.
  • Vuelve en las respuestas. La emisión la devuelve en data.bhet.metadataAdicional, y GET /{tenantSlug}/bhet/{id} en metadataAdicional, con null si no mandaste nada.
  • No reemplaza campos con efecto tributario. Un dato que el SII debe ver, puesto acá, no llega a la boleta.

EstadoDescripción
NO_APLICAModo SINC_COMPLETO exitoso o sandbox
PENDIENTEEn cola para descarga
DISPONIBLEDescargado exitosamente
FALLIDOAgotó reintentos después de 7 días

Ventana de terminal
# PDF Interno (template personalizado)
GET /{tenantSlug}/bhet/{id}/pdf/interno
# PDF SII (respaldo oficial)
GET /{tenantSlug}/bhet/{id}/pdf/sii
# Archivo específico del panel de archivos
GET /{tenantSlug}/bhet/{id}/archivos/{archivoId}/download
# PDF generado al momento con otro template (no se guarda)
GET /{tenantSlug}/bhet/{id}/pdf/custom?templateId=tpl_bhet_002

Todos streamean el archivo como application/pdf (Content-Disposition: attachment). No son redirects y nunca entregan una URL de bucket.

Si el documento pedido no existe todavía, responden 404 con el motivo:

{
"success": false,
"error": {
"code": "PDF_SII_NOT_AVAILABLE",
"message": "El PDF SII de esta boleta no está disponible. Estado: PENDIENTE",
"estadoPdfSii": "PENDIENTE"
}
}

Usa error.estadoPdfSii para distinguir “todavía se está generando” (PENDIENTE, reintenta más tarde) de “falló definitivamente” (FALLIDO, usa el PDF interno).


Si tu sistema tiene una interfaz propia y tus usuarios también son usuarios de Redcumbre, el bloque descargas te da un enlace listo para pintar:

<a href="{{ descargas.pdfInterno }}" target="_blank">Descargar boleta</a>
<a v-if="estadoPdfSii === 'DISPONIBLE'" :href="descargas.pdfSii" target="_blank">Respaldo SII</a>
CampoPara qué sirve
descargas.pdfInternoEnlace al PDF interno. Descarga directa, sin RUT ni CAPTCHA
descargas.pdfSiiÍdem para el respaldo SII. Viene siempre, aunque el documento aún no exista
estadoPdfSiiSemáforo: con DISPONIBLE el respaldo ya está; con PENDIENTE el enlace muestra “se está generando”

Cómo funcionan:

  • Son enlaces de navegación (<a href>, window.location). No funcionan con fetch ni axios desde tu dominio: la cookie de sesión de Redcumbre es SameSite=Lax y no viaja en peticiones XHR cross-site.
  • Requieren un usuario con sesión en Redcumbre y rol del módulo (SUPER-ADMIN, ADMIN, SII-EMISOR-BHET o FULL-API). Si no hay sesión, el enlace pasa por el login y vuelve solo al documento.
  • La descarga queda registrada en el historial de actividad de la boleta.
  • Son estables: se calculan desde el id y sobreviven a la regeneración del PDF.

Ventana de terminal
GET /{tenantSlug}/bhet/{id}/archivos

El campo url de cada archivo es el enlace para el destinatario de la boleta: abre la página pública de descarga, que le pide su RUT antes de entregar el archivo, y es el mismo enlace que recibe por correo. No es una URL de bucket. Para descargar el archivo desde tu integración usa GET /{tenantSlug}/bhet/{id}/archivos/{archivoId}/download.

Response:

{
"success": true,
"data": [
{
"id": "archivo-001",
"tipo": "INTERNO",
"templateId": "template-001",
"templateName": "Template Carta",
"fileName": "bhet_12345678_interno.pdf",
"url": "https://...",
"sizeBytes": 125000,
"createdAt": "2025-12-05T15:30:00Z",
"createdBy": "Sistema"
},
{
"id": "archivo-002",
"tipo": "SII",
"templateId": null,
"templateName": null,
"fileName": "bhet_12345678_sii.pdf",
"url": "https://...",
"sizeBytes": 98000,
"createdAt": "2025-12-05T15:35:00Z",
"createdBy": "SII"
}
],
"sandbox": false
}

Ventana de terminal
GET /{tenantSlug}/bhet?fechaDesde=2025-01-01&estado=EMITIDA&limit=50
Authorization: Bearer tu-api-key

Filtros disponibles:

ParámetroTipoDescripción
fechaDesdeISO 8601Fecha de inicio del rango
fechaHastaISO 8601Fecha de fin del rango
emisorTributarioIdstringFiltrar por emisor
terceroRutstringFiltrar por RUT del tercero
estadoenumEMITIDA, ERROR
canalEmisionenumUI, API_SYNC, API_ASYNC
folioSiistringBuscar por folio (substring)
limitnumberRegistros por página (default: 50, max: 100)
offsetnumberRegistros a saltar (default: 0)

Ventana de terminal
GET /{tenantSlug}/bhet/{id}
Authorization: Bearer tu-api-key

Es el documento completo, y es más de lo que devuelve la emisión: emisor, tercero, prestaciones con su desglose de descuentos, tasaImpuesto, fechas, descargas y el estado de una eventual anulación. Todo eso llega en data (acá sí, plano — la boleta es el recurso). El listado de campos con sus tipos está en Swagger.


El sistema envía webhooks para 4 eventos relacionados con BHET:

EventoDescripciónCuándo se dispara
bhet.emitidaBHET emitida exitosamenteInmediatamente después de emitir
bhet.emision_terminadaPDF SII disponibleCuando se descarga el PDF SII en background
bhet.pdf_sii_fallidoDescarga PDF SII fallóDespués de 7 días de reintentos fallidos
bhet.emision_reintentandoUn intento falló y va a reintentarseEn cada intento intermedio — informativo, no reemitas
bhet.emision_fallidaEmisión asíncrona falló definitivamenteUna sola vez: al agotar los 10 intentos, o en el primero si el error es permanente

{
"event": "bhet.emitida",
"tenantId": "8",
"bhetId": "cm5abc123",
"folioSii": "12345678",
"emisor": {
"rut": "76123456-7",
"razonSocial": "EMPRESA CONTRATANTE SPA"
},
"tercero": {
"rut": "78012039-8",
"nombre": "FIRERAISE SPA"
},
"montos": {
"bruto": 150000,
"impuesto": 22875,
"neto": 127125
},
"canalEmision": "API_SYNC",
"correlationId": "mi-referencia-123",
"sandbox": false,
"timestamp": "2025-12-05T15:30:00Z"
}

El sistema calcula automáticamente los montos:

Monto Bruto = Suma de todas las prestaciones
Impuesto = Monto Bruto × tasa de retención (redondeado al peso)
Monto Neto = Monto Bruto - Impuesto

CódigoDescripción
MULTIPLE_TERCERO_MODESSe especificó más de un modo de tercero
TERCERO_REQUIREDNo se especificó ningún modo de tercero
CONTRIBUYENTE_NOT_FOUNDID/RUT no existe en ContribuyenteMaestro (modo 1)
INVALID_COMUNA_CODEcodigoComuna no existe en catálogo (modo 2)
SII_LOOKUP_FAILEDLookup al SII falló (modo 4)
EMISOR_NO_HABILITADOEl emisor no tiene el servicio BHET habilitado

El errorCode llega en el cuerpo del error (canal de máquina) y en data.bhet.errorCode (canal web). El status te dice si conviene reintentar sin tener que interpretar el código.

No reintentables — corrige antes de volver a intentar (422)

CódigoDescripción
SII_RUT_INVALIDORUT del emisor o tercero no válido
SII_TERCERO_INVALIDOEl tercero no es válido para recibir una BHET
SII_DATOS_INVALIDOSDatos de la boleta incorrectos
SII_FORM_ERROREl formulario del SII rechazó los datos enviados
SII_NO_HABILITADOEl emisor no está habilitado ante el SII
SII_CERTIFICADO_ERRORError con certificado digital
SII_EMISION_FAILEDEl SII rechazó la emisión con un mensaje propio (viene en message)

Reintentables con backoff — el problema es del SII (502)

CódigoDescripción
SII_AUTH_FAILEDEl SII no devolvió TOKEN y no explicó por qué. Suele ser un problema pasajero de su lado
SII_ERRORError genérico del SII durante la emisión
SII_RESPUESTA_INESPERADAEl SII respondió algo que no se pudo interpretar
SII_ERROR_INTERNOError interno declarado por el SII (código IMT…)
SII_SOLICITUD_FALLIDA”No ha sido posible completar su solicitud”
SII_SESION_EXPIRADALa sesión con el SII expiró durante la operación
SII_SERVICIO_NO_DISPONIBLEEl servicio del SII no está disponible

No reintentables aunque lleguen con 502 — el paso que emite la boleta falló

CódigoDescripción
SII_CONFIRMACION_RECHAZADAEl SII respondió con un error HTTP al paso que emite la boleta. No se reintenta en automático; el extracto de la respuesta del SII viene en message
SII_CONFIRMACION_INCIERTAEl SII no respondió al paso que emite la boleta: no sabemos si se emitió. No reintentes sin verificar en el SII si la boleta existe: si reintentas y sí salió, quedan dos boletas reales. La clave de idempotencia queda reservada (ver Idempotencia)

Reintentable con espera — hay turno por delante (503)

CódigoDescripción
SII_EMISOR_BUSYDemasiadas emisiones en curso para ese emisor. Respeta el header Retry-After

EstadoDescripción
EMITIDAEmitida correctamente en el SII (tiene folio y código de barras)
ERRORError inmediato no reintentable

Con una API Key de prueba (isSandbox: true) la BHET se emite de punta a punta: la plataforma corre las mismas validaciones, guarda la boleta, genera su PDF y te manda los mismos webhooks que en real. Lo que no ocurre es lo que tiene efecto fuera: no hay ninguna llamada al SII, no se consume un folio real, no se cobra y no sale ningún correo a una persona. El resumen de qué ocurre y qué no, para todas las boletas, está en Entornos y modo de prueba.

Tu empresa ya tiene un emisor de prueba para las BHET: el RUT 88888888-8 (persona jurídica, emisor de boletas de terceros). En el modo de prueba no hace falta clave del SII ni tener contratado el servicio BHET: el emisor de prueba emite sin credenciales, porque ninguna operación de prueba llega al SII. Con tu propio emisor real y una key de prueba no puedes emitir: cada key sólo ve los emisores de su modo.

Su emisorTributarioId es un identificador opaco de tu empresa, como cualquier otro: en los ejemplos va el placeholder tu-emisor-tributario-id, nunca un valor con pinta de real. El verdadero lo copias desde el panel web: activa el modo de prueba, entra a Configuración → Emisores y abre el detalle del emisor de prueba, igual que harías con un emisor real. No hay endpoint con API key que lo liste.

Los RUT de prueba son fijos y no cambian después de publicados. Para el tercero:

RUTQué produce
22222222-2, 33333333-3, 12345678-5Emisión exitosa (personas naturales del catálogo de prueba)
44444444-4El SII simulado rechaza la emisión. La boleta queda ERROR con errorCode: "SII_TERCERO_INVALIDO", el mismo status HTTP y el mismo webhook que un rechazo real. No cuenta para el tope diario
99999999-9lookupRut responde «no encontrado», como un RUT que el SII no conoce

El resultado por defecto es el exitoso; el rechazo lo provocas tú con 44444444-4. Ningún error del modo de prueba tiene un código propio: son los mismos que ya conoces de producción.

Ejemplo:

Ventana de terminal
POST /{tenantSlug}/bhet
Authorization: Bearer <tu API Key de prueba>
{
"emisorTributarioId": "tu-emisor-tributario-id",
"lookupRut": "22222222-2",
"prestaciones": [
{ "descripcion": "Servicio de prueba", "valor": "100000" }
]
}

Response:

{
"success": true,
"data": {
"id": "...",
"folioSii": "SBX1790790355066-vpc6vlsv",
"estado": "EMITIDA",
...
},
"sandbox": true
}

El folio simulado lleva el prefijo SBX como marca visible, pero lo que decide cómo se trata la boleta después —anulación, correo, descarga— es la marca de modo que queda guardada con ella, no el folio.

  • PDF: se genera de verdad y lleva en cada página la marca «DOCUMENTO DE PRUEBA — SIN VALIDEZ TRIBUTARIA». Lo descargas por los mismos endpoints de siempre. El respaldo del SII no existe en prueba: estadoPdfSii queda NO_APLICA y pedirlo responde lo mismo que cuando no está disponible.
  • Enlace público: la respuesta trae el bloque descargas como siempre, pero ningún documento de prueba abre por un enlace público: el enlace responde lo mismo que uno inexistente.
  • Correo: no sale ningún correo al tercero al emitir con enviarBoletaPorEmail: true. Una boleta de prueba tampoco se puede reenviar por correo desde la aplicación web: con el modo de prueba desactivado no aparece, y con el modo de prueba activado el panel es de sólo lectura. La supresión se decide por el modo de la boleta, así que una boleta de prueba no despacha correo nunca. Lo que habría salido queda en la bandeja de mensajes simulados de tu empresa, con su destinatario y su contenido renderizado: GET /{tenantSlug}/modo-prueba/bandeja.
  • Webhooks: se emiten los mismos eventos que en real, con sandbox: true, al endpoint de prueba que configures en tu empresa. Si no configuras un endpoint de prueba, los eventos de prueba no llegan a ese endpoint: no se desvían a tu endpoint real. Ver Modo de prueba.
  • Anulación: la anulación se simula completa, y una acción de simulación te deja ensayar el «no anulable». Ver Anulación de boletas.
  • Emisión masiva: un lote por Procesos batch con tu key de prueba corre de verdad, fila por fila, y cada fila emitida cuenta una en el tope.
  • Otro modo: una key sólo ve las boletas de su modo. Pedir por su id una boleta del otro modo responde lo mismo que un id que no existe.

Cada empresa puede emitir hasta 200 BHET de prueba al día, contadas aparte de las BHE. Una emisión que termina en error no cuenta. La que excede el tope responde 400 con el código MODO_PRUEBA_TOPE_DIARIO, cuánto te queda y que el tope se renueva a las 00:00 en hora de Chile. Cuánto te queda, sin consumirlo: GET /{tenantSlug}/modo-prueba/tope-diario/{servicio} con servicio = bhet.

En una emisión ASINCRONO el remanente se comprueba al aceptar y se consume al emitir: si entre las dos otra operación agotó el tope, el intento termina fallido con el motivo del tope y lo lees al consultar su estado.


Para detalles técnicos completos y especificaciones de todos los endpoints:

👉 Ver endpoints de BHET en Swagger


La anulación ante el SII tiene su propia guía: causas, seguimiento por webhook, comprobante al tercero y los límites de 10 días / $1.000.000.

👉 Anulación de Boletas