Ir al contenido

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.


  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
causaSiistringCó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": "cmp4grg1o0001jw0j5nyf4gao",
"causaSii": "1",
"motivoInterno": "El cliente nunca pagó",
"notificarEmail": "cliente@ejemplo.cl"
}

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

CampoTipoRequeridoDescripción
folioSiistringFolio de la boleta ante el SII. Solo dígitos, o un folio de sandbox
emisorTributarioIdstringID 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": "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.


EstadoTerminalDescripción
PENDIENTENoSolicitud aceptada, esperando su turno en la cola
PROCESANDONoHablando con el SII
ANULADAEl SII aceptó la anulación
YA_ANULADAEl SII informa que la boleta ya estaba anulada (se anuló fuera de la plataforma)
NO_ANULABLEEl SII rechazó de forma permanente: fuera de plazo, sobre el monto, folio inexistente
FALLIDA_DEFINITIVANo 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ó nadacodigoResultadoSii 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
SANULADAAnulada
VANULADAEl 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: con una API Key de sandbox sobre una boleta real, responde 400 SANDBOX_BOLETA_REAL. Ver Sandbox.


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 50, máximo 300
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 de sandbox sobre una boleta real — SANDBOX_BOLETA_REAL400
API Key de producción declarando un folio de sandbox — FOLIO_SANDBOX_EN_PRODUCCION400
El emisor o la boleta no existen en tu cuenta404
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 sandbox

La emisión de la boleta ya se cobró en su momento: anularla no la reembolsa. Ver la página de Tarifas.


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.

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 KeyEl folioResultado
SandboxReal400 SANDBOX_BOLETA_REAL
SandboxDe sandboxSe simula
ProducciónDe sandbox, vía externa400 FOLIO_SANDBOX_EN_PRODUCCION
ProducciónDe sandbox, vía internaSe simula igual
ProducciónRealAnulació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).


Para el detalle técnico completo de cada endpoint:

👉 Anulación de BHE en Swagger

👉 Anulación de BHET en Swagger

Guías relacionadas: