Anulación de Boletas
Anulación de Boletas de Honorarios (BHE y BHET)
Sección titulada «Anulación de Boletas de Honorarios (BHE y BHET)»La API permite anular ante el SII boletas que emitiste con nosotros y también boletas que el emisor
emitió directamente en el portal del SII. El trabajo es asíncrono: la solicitud responde
202 Accepted de inmediato y el resultado llega por webhook o consultando el estado.
Requisitos Previos
Sección titulada «Requisitos Previos»- Un Emisor Tributario activo, con clave tributaria vigente (la misma que usas para emitir)
- Una API Key con uno de estos roles:
- BHE:
ADMIN,SUPER-ADMIN,SII-EMISOR-BHEoFULL-API - BHET:
ADMIN,SUPER-ADMIN,SII-EMISOR-BHEToFULL-API
- BHE:
- El servicio correspondiente (
SII_BHE/SII_BHET) habilitado en tu cuenta
Las dos vías
Sección titulada «Las dos vías»Según dónde se emitió la boleta, cambia el endpoint que usas:
| Vía | Endpoint | Cuándo |
|---|---|---|
| Interna | POST /{tenantSlug}/bhe/{id}/anular | La boleta la emitiste con nosotros y tienes su id |
| Externa | POST /{tenantSlug}/bhe/anulaciones | La boleta se emitió en el portal del SII: no existe en la plataforma |
¿La boleta la emitiste con nuestra API? │ ┌─────────────┴─────────────┐ │ Sí │ No ▼ ▼ POST /bhe/{id}/anular POST /bhe/anulaciones { causaSii } { causaSii, folioSii, emisorTributarioId } │ │ └─────────────┬─────────────┘ ▼ 202 { anulacionId } │ [Cola → SII] │ ┌─────────────┴─────────────┐ ▼ ▼ webhook bhe.anulada webhook bhe.anulacion_fallidaPara BHET son las mismas rutas bajo /{tenantSlug}/bhet.
Causas de Anulación
Sección titulada «Causas de Anulación»La causa se declara al SII y es obligatoria. Los códigos son distintos catálogos según el tipo de boleta — el texto es el que muestra el propio portal del SII.
causaSii | Significado |
|---|---|
"1" | No pago de honorarios |
"2" | Prestación de servicios no realizada |
"3" | Error en la digitación |
causaSii | Significado |
|---|---|
"1" | No pago |
"2" | Prestación de servicios no realizada |
"3" | Error en la digitación |
Además puedes registrar un motivoInterno (hasta 1.000 caracteres) para tu propia trazabilidad.
Ese texto nunca se envía al SII ni aparece en el comprobante que recibe el destinatario.
Anular una boleta emitida en la plataforma
Sección titulada «Anular una boleta emitida en la plataforma»POST /{tenantSlug}/bhe/{id}/anularAuthorization: Bearer {api_key}Content-Type: application/json
{ "causaSii": "3", "motivoInterno": "Se digitó mal el monto de la prestación", "correlationId": "mi-referencia-123", "enviarComprobante": true}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
causaSii | string | Sí | Código de causa ("1", "2" o "3") |
motivoInterno | string | No | Tu registro interno. Máx. 1.000 caracteres. No se envía al SII |
correlationId | string | No | Tu identificador para correlacionar. Vuelve en el webhook. Máx. 255 |
enviarComprobante | boolean | No | Enviar comprobante al destinatario. Default true |
Response 202 Accepted
Sección titulada «Response 202 Accepted»{ "anulacionId": "cmp7x2k9a0001jw0h4nqz8bcd", "estado": "PENDIENTE", "folioSii": "500", "tipoBoleta": "BHE", "mensaje": "Solicitud de anulación registrada. El resultado llegará en unos segundos."}El 202 confirma que la solicitud quedó registrada y encolada, no que el SII haya anulado nada.
Guarda el anulacionId: es lo que usas para consultar el estado y para reintentar.
Response 409 Conflict
Sección titulada «Response 409 Conflict»Si ya existe una anulación en curso o terminada para ese folio, el body incluye el anulacionId
existente para que puedas hacer seguimiento en vez de reintentar a ciegas:
{ "statusCode": 409, "message": "Ya hay una anulación en curso para el folio 500.", "anulacionId": "cmp7x2k9a0001jw0h4nqz8bcd", "estado": "PROCESANDO"}Anular una boleta emitida fuera de la plataforma
Sección titulada «Anular una boleta emitida fuera de la plataforma»Sirve para boletas que el emisor tributario emitió directamente en el portal del SII. Como no hay boleta local que referenciar, la identificas por folio + emisor.
POST /{tenantSlug}/bhe/anulacionesAuthorization: Bearer {api_key}Content-Type: application/json
{ "folioSii": "500", "emisorTributarioId": "cmp4grg1o0001jw0j5nyf4gao", "causaSii": "1", "motivoInterno": "El cliente nunca pagó", "notificarEmail": "cliente@ejemplo.cl"}Acepta los mismos campos que la vía interna, más:
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
folioSii | string | Sí | Folio de la boleta ante el SII. Solo dígitos, o un folio de sandbox |
emisorTributarioId | string | Sí | ID del emisor tributario dueño de la boleta |
notificarEmail | string | No | Destinatario del comprobante de anulación |
Seguir el resultado
Sección titulada «Seguir el resultado»Opción 1: Webhooks (recomendado)
Sección titulada «Opción 1: Webhooks (recomendado)»| Evento | Cuándo se dispara |
|---|---|
bhe.anulada / bhet.anulada | El SII aceptó la anulación |
bhe.anulacion_fallida / bhet.anulacion_fallida | El SII la rechazó de forma permanente, o se agotaron los reintentos |
Payload: bhe.anulada
Sección titulada «Payload: bhe.anulada»{ "event": "bhe.anulada", "tenantId": "8", "anulacionId": "cmp7x2k9a0001jw0h4nqz8bcd", "tipoBoleta": "BHE", "folioSii": "500", "bheId": "cms7xg4tr000kjw12kmxw8fej", "causaSii": "3", "codigoResultadoSii": "S", "fechaAnulacionSii": "2026-07-30T15:42:00.000Z", "correlationId": "mi-referencia-123"}Payload: bhe.anulacion_fallida
Sección titulada «Payload: bhe.anulacion_fallida»{ "event": "bhe.anulacion_fallida", "tenantId": "8", "anulacionId": "cmp7x2k9a0001jw0h4nqz8bcd", "tipoBoleta": "BHE", "folioSii": "500", "bheId": "cms7xg4tr000kjw12kmxw8fej", "causaSii": "3", "codigoResultadoSii": "C", "mensajeSii": "La boleta excede el monto máximo de anulación", "correlationId": "mi-referencia-123"}En BHET los campos son idénticos, con bhetId en vez de bheId.
Revisa la guía de Webhooks para la configuración, firma y reintentos.
Cuánto puede tardar
Sección titulada «Cuánto puede tardar»La mayoría de las anulaciones se resuelven en segundos. Pero cuando el SII no responde, la plataforma reintenta sola con espera creciente, y el desenlace puede tardar horas:
| Capa | Comportamiento |
|---|---|
| Reintentos de la cola | Hasta 8 intentos con espera exponencial, que llega a ~64 minutos entre uno y otro |
| Recuperación automática | Un proceso cada 10 minutos rescata las anulaciones que quedaron sin procesar |
| Techo | Hasta 3 recuperaciones; después se cierra en FALLIDA_DEFINITIVA |
Toda anulación alcanza un estado final: siempre vas a recibir el webhook de desenlace o ver un estado terminal al consultar. Si haces polling, no pongas un timeout de minutos — usa webhooks, o consulta con una frecuencia baja y sin límite de tiempo.
Opción 2: Consultar el estado
Sección titulada «Opción 2: Consultar el estado»GET /{tenantSlug}/bhe/anulaciones/{anulacionId}Authorization: Bearer {api_key}{ "id": "cmp7x2k9a0001jw0h4nqz8bcd", "estado": "ANULADA", "tipoBoleta": "BHE", "origenBoleta": "INTERNA", "folioSii": "500", "bheId": "cms7xg4tr000kjw12kmxw8fej", "causaSii": "3", "motivoInterno": "Se digitó mal el monto de la prestación", "emisorTributarioId": "cmp4grg1o0001jw0j5nyf4gao", "emisorRut": "18015551-1", "emisorRazonSocial": "JUAN PÉREZ", "codigoResultadoSii": "S", "mensajeSii": null, "montoMaximoAnulacion": 1000000, "minutosMaximosAnulacion": 43200, "intentos": 1, "procesandoAt": "2026-07-30T15:41:58.000Z", "resueltaAt": "2026-07-30T15:42:03.000Z", "fechaAnulacionSii": "2026-07-30T15:42:00.000Z", "canal": "API_SYNC", "correlationId": "mi-referencia-123", "comprobanteSolicitado": true, "notificarEmail": null, "emailEnviado": true, "emailEnviadoAt": "2026-07-30T15:42:11.000Z", "intentosEmail": 1, "errorEmail": null, "createdAt": "2026-07-30T15:41:55.000Z", "updatedAt": "2026-07-30T15:42:11.000Z"}montoMaximoAnulacion y minutosMaximosAnulacion los informa el SII en su respuesta y solo
aparecen en BHE. Son los umbrales que el SII aplicó a esa solicitud puntual.
Estados de la Anulación
Sección titulada «Estados de la Anulación»| Estado | Terminal | Descripción |
|---|---|---|
PENDIENTE | No | Solicitud aceptada, esperando su turno en la cola |
PROCESANDO | No | Hablando con el SII |
ANULADA | Sí | El SII aceptó la anulación |
YA_ANULADA | Sí | El SII informa que la boleta ya estaba anulada (se anuló fuera de la plataforma) |
NO_ANULABLE | Sí | El SII rechazó de forma permanente: fuera de plazo, sobre el monto, folio inexistente |
FALLIDA_DEFINITIVA | Sí | No se pudo completar: se agotaron los reintentos por errores transitorios (red, caída del SII), o el techo de recuperación automática. El SII no rechazó nada — codigoResultadoSii viene null |
NO_ANULABLE y FALLIDA_DEFINITIVA admiten reintento manual.
Códigos del SII
Sección titulada «Códigos del SII»El campo codigoResultadoSii trae el código crudo que devolvió el SII, sin traducir. Así se
interpreta en BHE:
| Código | Estado resultante | ¿Se cobra? | Significado |
|---|---|---|---|
S | ANULADA | Sí | Anulada |
V | ANULADA | Sí | El SII aceptó y espera que el receptor confirme u objete |
v | NO_ANULABLE | No | Receptor extranjero: el SII no puede pedir la confirmación. La anulación no se ejecutó |
A | YA_ANULADA | No | La boleta ya estaba anulada |
E | NO_ANULABLE | No | El folio no existe |
C / M / R / L | NO_ANULABLE | No | Rechazo por límites o reglas del SII |
X | reintentable | No | Error transitorio: se reintenta automáticamente |
Un código que no esté en la tabla se trata como transitorio y se reintenta.
Plazos y Límites
Sección titulada «Plazos y Límites»Los límites que puede aplicar el SII no son los mismos para los dos tipos de boleta.
BHET — se validan antes de llamar al SII
Sección titulada «BHET — se validan antes de llamar al SII»| Límite | Valor |
|---|---|
| Antigüedad máxima | 10 días corridos desde la fecha de emisión |
| Monto neto máximo | $1.000.000 (líquido a pagar al tercero) |
Si la boleta está fuera de estos límites, la API responde 400 de inmediato, sin gastar una llamada
al SII. Ahí la anulación electrónica ya no está disponible: hay que presentar el Formulario 2117
en una oficina del SII.
BHE — el SII decide, no se puede anticipar
Sección titulada «BHE — el SII decide, no se puede anticipar»El SII no publica el plazo máximo para anular una boleta de honorarios, y el umbral llega recién
en su respuesta (minutosMaximosAnulacion). No hay prevalidación posible: se intenta y se
interpreta el rechazo. Diseña tu integración asumiendo que una anulación de BHE puede volver
NO_ANULABLE sin aviso previo.
Comprobante al Destinatario
Sección titulada «Comprobante al Destinatario»Cuando el SII confirma la anulación, la plataforma envía por correo un comprobante de anulación al destinatario de la boleta, con el PDF marcado como anulado adjunto por enlace.
| Situación | A quién se le envía |
|---|---|
| BHE con destinatario | Al email del destinatario de la boleta |
| BHET | Al email del tercero |
| Vía externa | Solo a notificarEmail, si lo indicaste |
Para desactivarlo —por ejemplo, si tú manejas tu propia comunicación con el cliente— manda
enviarComprobante: false en la solicitud:
{ "causaSii": "3", "enviarComprobante": false}El default es true: la opción segura es que el destinatario se entere.
Los campos emailEnviado, emailEnviadoAt, intentosEmail y errorEmail del estado te dejan
verificar el envío. El correo se despacha con reintentos automáticos; si el PDF marcado no se puede
generar, el correo sale igual, sin el enlace.
Reintentar una Anulación
Sección titulada «Reintentar una Anulación»Solo desde NO_ANULABLE o FALLIDA_DEFINITIVA. Sirve para corregir la causa declarada o para
volver a intentar después de una caída del SII.
POST /{tenantSlug}/bhe/anulaciones/{anulacionId}/reintentarAuthorization: Bearer {api_key}Content-Type: application/json
{ "causaSii": "1"}causaSii es opcional: si lo omites, se reintenta con la causa original. Responde 202 con el mismo
contrato de la solicitud inicial.
Reintentar desde cualquier otro estado responde 409.
El reintento valida lo mismo que la solicitud inicial: con una API Key de sandbox sobre una boleta
real, responde 400 SANDBOX_BOLETA_REAL. Ver Sandbox.
Listar Anulaciones
Sección titulada «Listar Anulaciones»GET /{tenantSlug}/bhe/anulaciones?estado=NO_ANULABLE&desde=2026-07-01&limit=50&offset=0Authorization: Bearer {api_key}| Parámetro | Tipo | Descripción |
|---|---|---|
estado | enum | PENDIENTE, PROCESANDO, ANULADA, YA_ANULADA, NO_ANULABLE, FALLIDA_DEFINITIVA |
folioSii | string | Filtrar por folio |
emisorTributarioId | string | Filtrar por emisor |
desde / hasta | ISO 8601 | Rango sobre la fecha de solicitud |
limit | number | Registros por página. Default 50, máximo 300 |
offset | number | Registros a saltar. Default 0 |
{ "data": [ { "id": "cmp7x2k9a0001jw0h4nqz8bcd", "estado": "ANULADA", "...": "..." } ], "total": 137, "offset": 0, "limit": 50}El listado está acotado al tipo de boleta del endpoint: /bhe/anulaciones nunca devuelve anulaciones
de BHET, ni al revés.
El estado también viaja en el listado de boletas
Sección titulada «El estado también viaja en el listado de boletas»GET /{tenantSlug}/bhe incluye en cada boleta un objeto liviano con su anulación, si tiene. Te evita
una request extra para pintar un listado:
{ "id": "cms7xg4tr000kjw12kmxw8fej", "folioSii": "500", "estado": "ANULADA", "anulacion": { "id": "cmp7x2k9a0001jw0h4nqz8bcd", "estado": "ANULADA", "causaSii": "3", "resueltaAt": "2026-07-30T15:42:03.000Z" }}Es null cuando nunca se intentó anular. Sirve para distinguir una anulación rechazada de una que
nunca se pidió — cosa que el estado de la boleta por sí solo no te dice.
Errores
Sección titulada «Errores»Estas condiciones se rechazan antes de encolar nada:
| Situación | Status |
|---|---|
causaSii ausente o fuera del catálogo | 400 |
| La boleta no tiene folio del SII (nunca llegó a emitirse) | 400 |
La boleta está en un estado distinto de EMITIDA | 400 |
| El emisor está inactivo, revocado o sin credenciales | 400 |
| BHET: fuera de los 10 días o sobre $1.000.000 netos | 400 |
API Key de sandbox sobre una boleta real — SANDBOX_BOLETA_REAL | 400 |
API Key de producción declarando un folio de sandbox — FOLIO_SANDBOX_EN_PRODUCCION | 400 |
| El emisor o la boleta no existen en tu cuenta | 404 |
| Ya hay una anulación en curso o terminada para ese folio | 409 |
| Reintento desde un estado que no lo admite | 409 |
| API Key inválida, expirada o revocada | 401 |
| El rol de tu API Key no alcanza para el recurso | 403 |
| No se pudo verificar tu credencial (transitorio) | 503 |
Los rechazos del propio SII no son errores HTTP: la solicitud se aceptó con 202 y el rechazo
llega como estado terminal (NO_ANULABLE) más el webhook *.anulacion_fallida.
CLP 35 por anulación efectiva, mismo valor para BHE y BHET. Se cobra una sola vez y solo
cuando el SII aceptó la anulación (ANULADA). No se cobra por:
- Boletas que ya estaban anuladas (
YA_ANULADA) - Rechazos del SII (
NO_ANULABLE), incluido el caso del receptor extranjero (v) - Reintentos por errores transitorios
- Reintentos manuales de una anulación ya cobrada
- Anulaciones simuladas en sandbox
La emisión de la boleta ya se cobró en su momento: anularla no la reembolsa. Ver la página de Tarifas.
Sandbox
Sección titulada «Sandbox»Con una API Key de sandbox, la anulación se simula: no se llama al SII, no se cobra y no se
envía el comprobante al destinatario. Todo lo demás ocurre igual que en producción — la anulación
queda ANULADA, la boleta pasa a ANULADA, y el webhook bhe.anulada / bhet.anulada te llega
como siempre. Es el flujo completo para probar tu integración de punta a punta.
Qué se simula y qué se rechaza
Sección titulada «Qué se simula y qué se rechaza»Lo que decide si la anulación se simula es el folio de la boleta, no la API Key con la que la pides:
| Tu API Key | El folio | Resultado |
|---|---|---|
| Sandbox | Real | 400 SANDBOX_BOLETA_REAL |
| Sandbox | De sandbox | Se simula |
| Producción | De sandbox, vía externa | 400 FOLIO_SANDBOX_EN_PRODUCCION |
| Producción | De sandbox, vía interna | Se simula igual |
| Producción | Real | Anulación real ante el SII |
Las dos últimas filas no son una inconsistencia. Una boleta emitida en sandbox no existe en el SII: intentar anularla de verdad fallaría igual, así que se simula sin importar con qué credencial la pidas. En cambio, en la vía externa el folio lo escribes tú y no hay boleta local que lo respalde — por eso ahí sí se rechaza:
{ "statusCode": 400, "error": "FOLIO_SANDBOX_EN_PRODUCCION", "message": "El folio SANDBOX-1765567603056 tiene el prefijo reservado para boletas de sandbox y esta API Key es de producción. Envía el folio real que el SII asignó a la boleta.", "folioSii": "SANDBOX-1765567603056"}Sin ese corte, cualquiera obtendría una anulación aceptada — sin cobro y sin tocar al SII — inventando un folio con el prefijo.
Las mismas reglas aplican a POST …/anulaciones/{anulacionId}/reintentar.
El sandbox resuelve siempre con una anulación aceptada. Los rechazos del SII (NO_ANULABLE,
YA_ANULADA) no se simulan: para ejercitar esas ramas, usa las validaciones previas que sí
responden sin tocar al SII (400 / 404 / 409).
API Reference
Sección titulada «API Reference»Para el detalle técnico completo de cada endpoint:
👉 Anulación de BHET en Swagger
Guías relacionadas:
- Boletas de Honorarios — emisión de BHE
- Boletas de Terceros — emisión de BHET
- Webhooks — configuración, firma y reintentos
- Emisores — alta y credenciales del emisor tributario