Errores
Códigos de error de la API de Simplo, clases de error del SDK por status HTTP y las reglas de reintento automático.
Toda falla del SDK lanza una subclase de SimploError. Captura estrecho
cuando puedas actuar sobre el caso, amplio en el resto:
import Simplo, { RateLimitError, SimploError, ValidationError } from '@simplohq/sdk';
const simplo = new Simplo();
try { await simplo.dtes.emit(params);} catch (error) { if (error instanceof ValidationError) { // Input malo — corrige la request, no la reintentes tal cual. for (const issue of error.details ?? []) { console.error(`${issue.field}: ${issue.code} — ${issue.message}`); } } else if (error instanceof RateLimitError) { // El SDK ya reintentó; frena al llamador. console.error(`Rate limit. Reintenta en ${error.retryAfter ?? '?'}s`); } else if (error instanceof SimploError) { console.error(error.status, error.code, error.message, error.requestId); } else { throw error; // error de programación — no lo tragues }}Propiedades del error
Sección titulada «Propiedades del error»| Propiedad | Tipo | Descripción |
|---|---|---|
status |
number | undefined |
Status HTTP (ausente en fallas de conexión) |
code |
string | undefined |
Código de la API legible por máquina, ej. "VALIDATION_ERROR" |
message |
string |
Descripción legible por humanos |
requestId |
string | undefined |
ID de la request — inclúyelo al contactar soporte |
details |
ValidationIssue[] |
Issues a nivel de campo (field, code, message) en errores de validación |
action |
string | undefined |
Siguiente paso recomendado por la API, cuando existe |
docUrl |
string |
Link a la documentación relevante |
Clases de error por status
Sección titulada «Clases de error por status»| Status | Clase | ¿Reintentar? | Causas típicas |
|---|---|---|---|
| 400/422 | ValidationError |
No — corrige la request | Dígito verificador de RUT inválido, campos requeridos ausentes |
| 401 | AuthenticationError |
No — corrige credenciales | API key errónea o revocada (Autenticación) |
| 403 | PermissionError |
No | La key no tiene el permiso o rol requerido |
| 404 | NotFoundError |
No | ID equivocado, o recurso de otra empresa |
| 409 | ConflictError |
Depende (ver abajo) | Folio duplicado; idempotency key aún en vuelo |
| 429 | RateLimitError |
Automático | Demasiadas requests (los límites son por empresa) |
| 5xx | ServerError |
Automático (si es seguro) | Problema transitorio de la API |
| — | ConnectionError |
Automático (si es seguro) | Falla de red, DNS, timeout |
| — | WebhookVerificationError |
No — rechaza la entrega | Firma mala, timestamp vencido (Webhooks) |
El sobre de error, capa por capa
Sección titulada «El sobre de error, capa por capa»Los errores vienen de dos capas y sus sobres no son idénticos:
- Plataforma (auth, allowlist, rate limit, idempotencia requerida):
{ code, message, error, severity, action?, context? }. - Motor de facturación (validaciones de emisión, folios, estados): pasa
por el proxy con su propio shape — trae
code,message,actionyseverity/contextcuando aplica.
Reglas de manejo robustas: decide por status HTTP + code cuando existe,
tolera campos desconocidos, y nunca parsees message como si fuera un
contrato. Dos comportamientos reales a tener presentes:
- Algunas validaciones del motor responden hoy con mensaje crudo y código
genérico — p. ej. omitir
ind_servicioen una boleta da400con"IndServicio is required for boleta", no un código de catálogo. Y en producción algunos errores tipados del motor pueden llegar como texto plano donde el sandbox devuelve el código estructurado (RUT_INVALIDO,CAF_NO_DISPONIBLE) — divergencia conocida, fix pendiente. Diseña tu manejo para funcionar aunque solo tengas el status. - Los largos de
razon_socialtienen dos capas: el motor valida el tope del SII (100 caracteres, receptor y emisor) y responde400 RAZON_SOCIAL_INVALIDAnombrando el campo y el límite; strings todavía más largos (> 200) los corta antes la validación de shape de la plataforma con un400genérico{"code":"BAD_REQUEST","message":"invalid request"}sin nombrar el campo — si ves ese, revisa primero los largos de tus strings.
Códigos que vas a ver como integrador
Sección titulada «Códigos que vas a ver como integrador»| Código | Descripción |
|---|---|
401 |
Credencial inválida, revocada o expirada. La respuesta no distingue el motivo (anti-enumeración): revisa el header y el prefijo sk_simplo_. |
409 IDEMPOTENCY_IN_PROGRESS |
La misma Idempotency-Key aún se está procesando. Espera y consulta el estado en vez de reintentar de inmediato. |
409 (folio duplicado) |
Conflicto de folio al emitir. |
422 IDEMPOTENCY_BODY_MISMATCH |
Reutilizaste una Idempotency-Key con un body distinto al original. Usa una key nueva por cada emisión distinta. |
422 BILLING_METADATA_NOT_SUPPORTED |
Mandaste metadata con contenido en la emisión. El campo todavía no se persiste, así que lo rechazamos en vez de descartarlo en silencio — quítalo y correlaciona contra el id que devuelve la emisión. {} y null sí pasan. Ver Emisión. |
429 |
Rate limit: 60 llamadas/min por empresa en la facturación por API key (120/min en MCP). Espera y reintenta — ver Límites. |
503 (idempotencia) |
Servicio de idempotencia temporalmente no disponible al emitir. Reintenta con la misma key. |
Códigos de validación de emisión (motor de facturación)
Sección titulada «Códigos de validación de emisión (motor de facturación)»Validaciones que corren antes de asignar folio — el mismo camino de
emisión en sandbox y producción, así que un 400 de estos en sandbox es
exactamente lo que verías en producción (donde además evitan quemar un folio
real en un documento que el SII rechazaría):
| Código | Status | Qué hacer |
|---|---|---|
EMISOR_INCOMPLETO |
400 | A tu empresa le falta giro, dirección o comuna (el context.missing_fields lista cuáles). El SII exige estos datos del emisor en el XML (GiroEmis/DirOrigen/CmnaOrigen) — complétalos en la app o vía API y reintenta. Aplica a todos los tipos, boletas incluidas. |
RAZON_SOCIAL_INVALIDA |
400 | razon_social (del receptor o de tu empresa) supera los 100 caracteres del esquema del SII (RznSocLargaType). Acórtala; el mensaje trae el largo recibido. |
MONTO_INVALIDO |
400 | Cantidad/precio negativos, o monto_total final ≤ 0 en un documento con montos. Corrige las líneas — el campo exacto viene en context.field. |
REFERENCIA_INVALIDA |
400 | Una NC/ND (56/61 y también las de exportación 111/112) referencia un tipo+folio que no existe para tu empresa. Verifica tipo_doc_ref/folio_ref contra el documento original. |
RECEPTOR_INCOMPLETO |
400 | En documentos tipo factura (33/34/43/46/56/61) el receptor necesita direccion y comuna — el SII real rechaza sin ellas (HED-3-845/HED-3-846). El context.missing_fields lista cuáles faltan. Boletas (39/41) y exportación (110/111/112) no las exigen. |
DETALLE_DESCUADRADO |
400 | Enviaste un monto_item que no cuadra con cantidad × precio (± descuentos/recargos, tolerancia ±1 peso de redondeo). El error trae el valor esperado y el recibido por línea. Si quieres fijar el monto final directo, omite precio y manda solo monto_item. |
IND_TRASLADO_REQUERIDO |
400 | Las guías de despacho (52) requieren ind_traslado según la tabla del SII (1=operación constituye venta … 9=venta para exportación). Nota: una guía de venta (1 o 9) además debe llevar montos — con precio 0 responde MONTO_INVALIDO. |
EXPORT_TPO_CAMBIO_REQUERIDO |
400 | Documento de exportación en moneda extranjera sin tpo_cambio: el SII real rechaza el fallback 1:1 («Los Valores del Encabezado No Cuadran»). Manda tpo_cambio (> 0); mnt_export explícito o forma_pago_exp: 21 (sin pago) siguen siendo válidos sin tasa. |
Códigos de la plataforma (API keys y sandbox)
Sección titulada «Códigos de la plataforma (API keys y sandbox)»Estos códigos vienen de la plataforma Simplo (no del motor de facturación) y tienen siempre una salida clara:
| Código | Status | Qué hacer |
|---|---|---|
BILLING_API_KEY_ROUTE_NOT_ALLOWED |
403 | La ruta es solo-sesión. Una key billing:dte solo alcanza las 6 rutas DTE — ver la tabla del quickstart. No es una cuenta sin habilitar. |
BILLING_API_KEY_IDEMPOTENCY_KEY_REQUIRED |
400 | Agrega el header Idempotency-Key a tu POST por key (/billing/dte y /billing/dte/send-boletas). |
BILLING_API_KEY_NOT_ENABLED |
403 | Tu empresa no tiene habilitada la emisión en producción por key — escribe a [email protected]. El sandbox no necesita esto. |
BILLING_SANDBOX_API_KEY_NOT_ENABLED |
403 | Tu plan no incluye el sandbox de facturación. |
COMPANY_RUT_REQUIRED_FOR_SANDBOX |
422 | Al crear una key sandbox: tu empresa aún no tiene RUT — complétalo en la app y reintenta. |
COMPANY_RAZON_SOCIAL_REQUIRED_FOR_SANDBOX |
422 | Al crear una key sandbox: falta la razón social — complétala en la app y reintenta. |
SANDBOX_COMPANY_FLOW_REMOVED |
410 | Llamaste el flujo antiguo POST /api/v1/sandbox/companies, que ya no existe: el sandbox es un modo de tu propia empresa — crea una key sandbox en Desarrolladores. |
SANDBOX_NOT_AVAILABLE |
503 | El sandbox no está disponible en este momento — reintenta más tarde. |
Reintentos automáticos
Sección titulada «Reintentos automáticos»El SDK reintenta en 429, 5xx y errores de red con backoff exponencial y
jitter, respetando el header Retry-After. Por defecto: 2 reintentos;
sobrescríbelo con maxRetries en el cliente o por request.
Regla de seguridad: un POST solo se reintenta cuando lleva una
Idempotency-Key. dtes.emit genera una automáticamente (UUID v4), así que
los reintentos de emisión nunca pueden duplicar un documento. Otros POSTs
(folios.upload, webhooks.create) no se reintentan automáticamente —
vuelve a llamarlos tú si hace falta; ambos son seguros de re-ejecutar (un CAF
duplicado devuelve ConflictError).
const simplo = new Simplo({ maxRetries: 3, timeout: 30_000 });await simplo.dtes.list({}, { maxRetries: 0 }); // override por request