Ir al contenido

Anulación de Boletas (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.


  1. Un Emisor Tributario activo, con clave tributaria vigente (la misma que usas para emitir)
  2. Una API Key con uno de estos roles:
    • BHE: ADMIN, SUPER-ADMIN, SII-EMISOR-BHE o FULL-API
    • BHET: ADMIN, SUPER-ADMIN, SII-EMISOR-BHET o FULL-API
  3. El servicio correspondiente (SII_BHE / SII_BHET) habilitado en tu cuenta

Según dónde se emitió la boleta, cambia el endpoint que usas:

VíaEndpointCuándo
InternaPOST /{tenantSlug}/bhe/{id}/anularLa boleta la emitiste con nosotros y tienes su id
ExternaPOST /{tenantSlug}/bhe/anulacionesLa 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_fallida

Para BHET son las mismas rutas bajo /{tenantSlug}/bhet.


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.

causaSiiSignificado
"1"No pago de honorarios
"2"Prestación de servicios no realizada
"3"Error en la digitación
causaSiiSignificado
"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.


Ventana de terminal
POST /{tenantSlug}/bhe/{id}/anular
Authorization: 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
}
CampoTipoRequeridoDescripción
causaSiistringSíCódigo de causa ("1", "2" o "3")
motivoInternostringNoTu registro interno. Máx. 1.000 caracteres. No se envía al SII
correlationIdstringNoTu identificador para correlacionar. Vuelve en el webhook. Máx. 255
enviarComprobantebooleanNoEnviar comprobante al destinatario. Default true
{
"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.

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.

Ventana de terminal
POST /{tenantSlug}/bhe/anulaciones
Authorization: Bearer {api_key}
Content-Type: application/json
{
"folioSii": "500",
"emisorTributarioId": "tu-emisor-tributario-id",
"causaSii": "1",
"motivoInterno": "El cliente nunca pagó",
"notificarEmail": "cliente@ejemplo.cl"
}

Acepta los mismos campos que la vía interna, más:

CampoTipoRequeridoDescripción
folioSiistringSíFolio de la boleta ante el SII. Solo dígitos, o un folio del modo de prueba
emisorTributarioIdstringSíID del emisor tributario dueño de la boleta
notificarEmailstringNoDestinatario del comprobante de anulación

EventoCuándo se dispara
bhe.anulada / bhet.anuladaEl SII aceptó la anulación
bhe.anulacion_fallida / bhet.anulacion_fallidaEl SII la rechazó de forma permanente, o se agotaron los reintentos
{
"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"
}
{
"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.

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:

CapaComportamiento
Reintentos de la colaHasta 8 intentos con espera exponencial, que llega a ~64 minutos entre uno y otro
Recuperación automáticaUn proceso cada 10 minutos rescata las anulaciones que quedaron sin procesar
TechoHasta 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.

Ventana de terminal
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": "tu-emisor-tributario-id",
"emisorRut": "12345678-9",
"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.


EstadoTerminalDescripción
PENDIENTENoSolicitud aceptada, esperando su turno en la cola
PROCESANDONoHablando con el SII
ANULADASíEl SII aceptó la anulación
YA_ANULADASíEl SII informa que la boleta ya estaba anulada (se anuló fuera de la plataforma)
NO_ANULABLESíEl SII rechazó de forma permanente: fuera de plazo, sobre el monto, folio inexistente
FALLIDA_DEFINITIVASí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.


El campo codigoResultadoSii trae el código crudo que devolvió el SII, sin traducir. Así se interpreta en BHE:

CódigoEstado resultante¿Se cobra?Significado
SANULADASíAnulada
VANULADASíEl SII aceptó y espera que el receptor confirme u objete
vNO_ANULABLENoReceptor extranjero: el SII no puede pedir la confirmación. La anulación no se ejecutó
AYA_ANULADANoLa boleta ya estaba anulada
ENO_ANULABLENoEl folio no existe
C / M / R / LNO_ANULABLENoRechazo por límites o reglas del SII
XreintentableNoError transitorio: se reintenta automáticamente

Un código que no esté en la tabla se trata como transitorio y se reintenta.


Los límites que puede aplicar el SII no son los mismos para los dos tipos de boleta.

LímiteValor
Antigüedad máxima10 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.


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ónA quién se le envía
BHE con destinatarioAl email del destinatario de la boleta
BHETAl email del tercero
Vía externaSolo 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.


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.

Ventana de terminal
POST /{tenantSlug}/bhe/anulaciones/{anulacionId}/reintentar
Authorization: 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, también el modo: una key sólo reintenta anulaciones de su modo. Ver Modo de prueba.


Ventana de terminal
GET /{tenantSlug}/bhe/anulaciones?estado=NO_ANULABLE&desde=2026-07-01&limit=50&offset=0
Authorization: Bearer {api_key}
ParámetroTipoDescripción
estadoenumPENDIENTE, PROCESANDO, ANULADA, YA_ANULADA, NO_ANULABLE, FALLIDA_DEFINITIVA
folioSiistringFiltrar por folio
emisorTributarioIdstringFiltrar por emisor
desde / hastaISO 8601Rango sobre la fecha de solicitud
limitnumberRegistros por página. Default 20, máximo 300 (PAGINATION_MAX_LIMIT)
offsetnumberRegistros 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.


Estas condiciones se rechazan antes de encolar nada:

SituaciónStatus
causaSii ausente o fuera del catálogo400
La boleta no tiene folio del SII (nunca llegó a emitirse)400
La boleta está en un estado distinto de EMITIDA400
El emisor está inactivo, revocado o sin credenciales400
BHET: fuera de los 10 días o sobre $1.000.000 netos400
API Key real declarando un folio del modo de prueba — FOLIO_SANDBOX_EN_PRODUCCION400
simular-no-anulable con una API Key real — OPERACION_SOLO_EN_MODO_DE_PRUEBA400
El emisor o la boleta no existen en tu cuenta, o son del otro modo que tu API Key404
Ya hay una anulación en curso o terminada para ese folio409
Reintento desde un estado que no lo admite409
API Key inválida, expirada o revocada401
El rol de tu API Key no alcanza para el recurso403
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 el modo de prueba

La emisión de la boleta ya se cobró en su momento: anularla no la reembolsa. Ver los precios.


Con una API Key de prueba, la anulación de una boleta de prueba se simula completa: no se llama al SII y no se cobra, pero todo lo demás ocurre igual que en producción, con los mismos plazos y montos máximos. La anulación queda ANULADA, la boleta pasa a ANULADA, se genera su comprobante y su PDF con la marca de anulada y la de documento de prueba, y el webhook bhe.anulada / bhet.anulada te llega al endpoint de prueba de tu empresa. El comprobante no se envía al destinatario: queda en la bandeja de mensajes simulados (GET /{tenantSlug}/modo-prueba/bandeja).

Lo que decide si una anulación se simula es el modo de la boleta, que queda guardado con ella al emitirse. No lo decide el prefijo del folio ni la credencial con la que la pides, y cada key sólo ve las boletas de su modo:

Tu API KeyLa boletaResultado
PruebaDe pruebaSe simula
PruebaRealResponde lo mismo que una boleta inexistente
RealDe pruebaResponde lo mismo que una boleta inexistente
RealRealAnulación real ante el SII

Así una key de prueba no puede anular una boleta real, y una boleta de prueba nunca llega al SII.

En la vía externa la boleta no está en la plataforma: con una key de prueba anulas sobre el emisor de prueba de tu empresa, y el folio se valida con la misma forma que en real. Con una key real, un folio con el prefijo reservado para prueba se rechaza, porque ese folio nunca lo asignó el SII:

{
"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"
}

Las mismas reglas aplican a POST …/anulaciones/{anulacionId}/reintentar.

La anulación simulada termina aceptada. Para ejercitar la rama en que el SII no deja anular, lleva una anulación de prueba a ese estado con la acción de simulación:

Ventana de terminal
POST /{tenantSlug}/bhe/anulaciones/{anulacionId}/simular-no-anulable
POST /{tenantSlug}/bhet/anulaciones/{anulacionId}/simular-no-anulable
Authorization: Bearer <tu API Key de prueba>

La anulación queda NO_ANULABLE con el mismo mensaje que un «no anulable» real —la boleta excede el plazo para anular por internet y debe presentarse el Formulario 2117—, se publica el mismo webhook bhe.anulacion_fallida / bhet.anulacion_fallida, y desde ahí puedes reintentar como en real. No introduce ningún estado nuevo y no cuenta para el tope diario.

Esta acción no existe en real: con una API Key real responde 400 con el código OPERACION_SOLO_EN_MODO_DE_PRUEBA y no produce ningún efecto.


Para el detalle técnico completo de cada endpoint:

👉 Anulación de BHE en Swagger

👉 Anulación de BHET en Swagger

Guías relacionadas: