B Boletitas

Documentación de integración

Emite boletas electrónicas del SII desde tu software. Dos formas de integrar, ambas en el plan Pro:

Antes de empezar (común a ambas vías)

Base URL: https://api.boletitas.cl/v1  ·  Formato: JSON (UTF-8)  ·  Fechas: ISO 8601 (UTC)  ·  Montos: pesos chilenos, enteros.

La API está disponible en el plan Pro. Genera tu API key en tu panel: Mi plan → 🔌 API (solo el dueño). La emisión usa las credenciales del SII ya cifradas de tu cuenta — tu software nunca toca la clave del SII.

🔑 Tienes dos keys independientes: producción bol_live_… y sandbox bol_test_…. Con la de sandbox integras y pruebas sin emitir en el SII real, sin gastar folios ni cupo.

Endpoints

MétodoRutaQué hace
POST/v1/boletasEmite una boleta.
POST/v1/boletas/loteEmite hasta 100 boletas en una llamada.
GET/v1/boletas/{id}Estado y datos de una boleta.
GET/v1/boletas/{id}/pdfDescarga el PDF de la boleta.
POST/mcpServidor MCP (JSON-RPC) para agentes de IA.

Autenticación

Manda tu API key en cada request, por header. Dos formas equivalentes:

Authorization: Bearer bol_live_tu_api_key
# — o bien —
X-API-Key: bol_live_tu_api_key

Sin una key válida → 401 no_autorizado. Trata tu bol_live_ como una contraseña: nunca la pongas en código de front-end ni en repos públicos. ¿Se filtró? Revócala y genera otra desde el panel.

Quickstart

De cero a una boleta emitida, con curl (usa tu key de sandbox para probar):

  1. Genera tu API key en el panel (Mi plan → 🔌 API).
  2. Emite una boleta:
    curl -X POST https://api.boletitas.cl/v1/boletas \
      -H "Authorization: Bearer bol_test_tu_api_key" \
      -H "Content-Type: application/json" \
      -d '{ "tipo": "afecta", "items": [{ "descripcion": "Corte de pelo", "precio": 15000 }] }'
    → responde 202 con { "id": "a1b2c3…", "estado": "en_proceso" }
  3. Consulta el resultado (o recíbelo por webhook):
    curl https://api.boletitas.cl/v1/boletas/a1b2c3… \
      -H "Authorization: Bearer bol_test_tu_api_key"
VÍA 1 · API REST

Emitir una boleta

POST/v1/boletas — asíncrono: responde 202 con un id; el resultado lo consultas por GET o te llega por webhook. La boleta admite varias líneas.

Lo mínimo

Solo necesitas tipo y las líneas. Igual que por WhatsApp, la boleta se emite sin receptor (consumidor final) — no hace falta identificar al cliente:

{
  "tipo": "afecta",
  "items": [
    { "descripcion": "Corte de pelo", "precio": 15000 }
  ]
}
🧾 El receptor es opcional. Si no lo mandas, la boleta va sin cliente identificado (como las de WhatsApp). Inclúyelo solo si el cliente pidió su boleta con datos (su RUT, su correo). En boletas afectas el precio ya incluye IVA (es lo que paga el cliente).

Con receptor (opcional) y todos los campos

{
  "tipo": "afecta",              // "afecta" (39, con IVA) | "exenta" (41)
  "items": [
    { "descripcion": "Corte de pelo", "cantidad": 1, "precio": 15000 }
  ],
  "receptor": { "rut": "12345678-9", "razon_social": "Cliente", "correo": "c@correo.cl" },  // opcional
  "referencia_externa": "venta-8842"   // opcional, tu id interno
}

Campos

Campo¿Obligatorio?Detalle
tipoSíafecta (con IVA) o exenta.
itemsSíAl menos una línea. Admite varias.
items[].descripcionSíNombre del producto o servicio.
items[].precioSíPrecio unitario en CLP (entero > 0). En afectas incluye IVA.
items[].cantidadNoPor defecto 1.
receptorNorut, razon_social, correo (todos opcionales). Sin receptor = consumidor final.
referencia_externaNoTu id interno. Vuelve en la respuesta y el webhook, y sirve de clave de idempotencia.

Header opcional Idempotency-Key: <uuid> → un reintento no duplica la boleta (o usa referencia_externa, que cumple el mismo rol).

Response 202

{ "id": "a1b2c3…", "estado": "en_proceso", "referencia_externa": "venta-8842", "creado": "…" }

Estados de una boleta

La emisión es asíncrona: el 202 confirma que la recibimos, no que ya está en el SII. Una boleta pasa por:

en_proceso  →  emitida (con folio + PDF)  ·  o  ·  error (con motivo)

Te enteras del resultado de dos formas: por webhook (te avisamos — recomendado) o por GET (consultas tú por el id). Normalmente tarda unos segundos.

Emisión masiva (lote)

POST/v1/boletas/lote — emite muchas boletas en una sola llamada (ideal para automatizar desde una planilla/Excel). Máximo 100 por llamada; cuenta como 1 request contra el rate limit. Devuelve un resultado por boleta (éxito parcial).

Cada boleta del arreglo acepta los mismos campos que el POST individual — incluido el receptor (opcional): puedes ponerlo en algunas y omitirlo en otras.

{
  "boletas": [
    { "tipo": "afecta", "items": [{ "descripcion": "Producto A", "precio": 9990 }], "referencia_externa": "fila-1" },
    { "tipo": "afecta", "items": [{ "descripcion": "Producto B", "precio": 15000 }],
      "receptor": { "rut": "12345678-9", "correo": "cliente@correo.cl" },   // opcional, por boleta
      "referencia_externa": "fila-2" }
  ]
}
💡 Dale una referencia_externa única a cada boleta: identifica la fila y actúa como clave de idempotencia — si reintentas el lote, las que ya se crearon no se duplican.

Response 200

{
  "total": 2, "aceptadas": 2, "con_error": 0,
  "resultados": [
    { "referencia_externa": "fila-1", "ok": true, "id": "a1b2…", "estado": "en_proceso" },
    { "referencia_externa": "fila-2", "ok": true, "id": "c3d4…", "estado": "en_proceso" }
  ]
}

Si superas tu cupo a mitad del lote, las que exceden vuelven con ok:false / cupo_agotado — el resto se emite igual.

Consultar el resultado

GET/v1/boletas/{id} — el estado y los datos de la boleta en cualquier momento.

{
  "id": "a1b2c3…",
  "estado": "emitida",           // en_proceso | emitida | error
  "tipo": "afecta",
  "folio": 1234,
  "total": 15000,
  "pdf_url": "https://api.boletitas.cl/v1/boletas/a1b2c3…/pdf",
  "referencia_externa": "venta-8842",
  "motivo": null,                // el detalle del fallo cuando estado = "error"
  "emitida_en": "2026-08-13T14:05:00Z"
}
CampoDetalle
estadoen_proceso, emitida o error.
folioFolio del SII (cuando está emitida).
totalMonto total de la boleta en CLP.
pdf_urlLink al PDF (disponible cuando está emitida).
motivoPor qué falló, cuando estado = "error".
emitida_enCuándo se resolvió (ISO 8601 UTC).

GET/v1/boletas/{id}/pdf — te redirige al PDF (link temporal firmado). Úsalo cuando la boleta esté emitida.

Idempotencia

Reintentar una emisión que "pareció fallar" (un timeout de red, por ejemplo) no debe duplicar la boleta. Dos formas de garantizarlo:

Si repites el mismo request con la misma clave, te devolvemos la misma boleta (mismo id), sin re-emitir. En el lote, la clave de cada boleta es su referencia_externa — por eso conviene que sea única por fila.

Webhook (recomendado)

En vez de estar consultando, deja que te avisemos. Configura tu webhook_url en el panel (🔌 API → Webhook). Cuando una boleta queda emitida o falla, te hacemos un POST con el mismo cuerpo del GET de consulta.

Verifica la firma

Al guardar tu URL te damos un webhook_secret. Firmamos cada envío con él; verifícalo antes de confiar en el evento:

X-Boletitas-Signature: HMAC-SHA256(cuerpo_crudo, webhook_secret)
// Node.js — en tu endpoint de webhook
const crypto = require("crypto");
const esperada = crypto.createHmac("sha256", WEBHOOK_SECRET)
  .update(cuerpoCrudo)                 // el body tal cual llegó, sin re-serializar
  .digest("hex");
if (esperada !== req.headers["x-boletitas-signature"]) {
  return res.status(401).end();        // firma inválida → ignóralo
}

Reintentos

Responde 2xx apenas lo recibas (idealmente encola y procesa después). Si no respondes 2xx, reintentamos con backoff hasta 6 veces. Haz tu endpoint idempotente (dedupe por el id de la boleta): un evento podría llegarte más de una vez.

Límites de uso

Hasta ~120 requests por minuto por API key (el lote cuenta como 1 request, así que emitir en lotes rinde mucho más). Al pasarte respondemos 429 rate_limit con un header Retry-After (segundos a esperar). ¿Tu volumen necesita más? Escríbenos y lo subimos.


VÍA 2 · PARA AGENTES DE IA

🤖 MCP · emisión por lenguaje natural

Boletitas expone un servidor MCP (Model Context Protocol) para que asistentes de IA (Claude, ChatGPT y otros compatibles) emitan boletas por lenguaje natural — «emite una boleta afecta de $15.000 por un corte de pelo», o «toma este Excel y emite una boleta por cada fila». El agente interpreta la orden y llama a las herramientas; por debajo es la misma API (mismo plan Pro, mismo cupo, mismo sandbox, misma idempotencia).

Endpoint: https://api.boletitas.cl/mcp   (JSON-RPC 2.0 sobre HTTP, POST)
Auth:     Authorization: Bearer bol_live_tu_api_key

Conecta tu cliente

Config típica de un cliente MCP (Claude Desktop y compatibles):

{
  "mcpServers": {
    "boletitas": {
      "url": "https://api.boletitas.cl/mcp",
      "headers": { "Authorization": "Bearer bol_live_tu_api_key" }
    }
  }
}

Herramientas

HerramientaQué hace
emitir_boletaEmite una boleta (tipo + líneas; receptor opcional).
emitir_boletasEmite hasta 100 en una llamada (desde una planilla/Excel).
consultar_boletaEstado, folio y PDF de una boleta por su id.

Ejemplo (JSON-RPC)

Listar las herramientas disponibles:

{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }

Emitir una boleta llamando a la herramienta:

{
  "jsonrpc": "2.0", "id": 2, "method": "tools/call",
  "params": {
    "name": "emitir_boleta",
    "arguments": {
      "tipo": "afecta",
      "items": [{ "descripcion": "Corte de pelo", "precio": 15000 }]
    }
  }
}
🤖 Costo de IA = $0, para ti y para nosotros: el modelo que interpreta el lenguaje natural es el de tu cliente (Claude, ChatGPT…). Nosotros solo recibimos la llamada ya estructurada — no consumimos tokens ni te cobramos por IA.

REFERENCIA

Cupo, sandbox y planes

Pro incluye 500 documentos/mes por API (se resetean cada mes; no se acumulan). ¿Necesitas más? Compra paquetes desde el panel (pago único, vencen a 12 meses; se consumen recién después de agotar la base del mes).

Al agotar el cupo, la API responde 429 cupo_agotado. Importante: tu canal WhatsApp sigue ilimitado — el tope es solo del canal API, la operación humana nunca se corta.

Sandbox (bol_test_): marca la boleta como emitida con un folio ficticio, sin tocar el SII, sin gastar folios ni cupo. Perfecto para integrar y probar tu webhook de punta a punta antes de pasar a bol_live_.

Errores

Todos los errores llegan con esta forma; el code es estable (programa contra él, no contra el mensaje):

{ "error": { "code": "datos_invalidos", "message": "Datos inválidos.", "detalles": ["items no puede estar vacío…"] } }
HTTPcodeQué pasó
401no_autorizadoAPI key inválida, ausente o revocada.
403plan_no_incluye_apiTu plan no es Pro.
404no_encontradaNo existe una boleta con ese id.
422datos_invalidosFaltan campos o son inválidos (ver detalles).
422lote_muy_grandeMás de 100 boletas en un lote. Divídelo.
429cupo_agotadoSin documentos disponibles. Compra un paquete.
429rate_limitMuchas solicitudes por minuto (ver Retry-After).
500error_internoAlgo falló de nuestro lado. Reintenta; si persiste, escríbenos.

Referencia rápida

Base URLhttps://api.boletitas.cl/v1
AuthAuthorization: Bearer bol_live_… o X-API-Key
Tiposafecta (39, con IVA) · exenta (41)
Máx. por lote100 boletas por llamada
Rate limit~120 req/min por API key
Cupo Pro500 documentos/mes + paquetes
Webhookfirmado (HMAC-SHA256), hasta 6 reintentos con backoff

Boletitas · API v1 · ¿Dudas o necesitas más volumen? Escríbenos desde boletitas.cl. Referencia OpenAPI interactiva en camino.