Skip to content
SimploSimplo Docs

Errors

Simplo API error codes, SDK error classes by HTTP status, and the automatic retry rules.

Every SDK failure throws a subclass of SimploError. Catch narrow when you can act on the case, broad otherwise:

import Simplo, { RateLimitError, SimploError, ValidationError } from '@simplohq/sdk';
const simplo = new Simplo();
try {
await simplo.dtes.emit(params);
} catch (error) {
if (error instanceof ValidationError) {
// Bad input — fix the request, don't retry it as-is.
for (const issue of error.details ?? []) {
console.error(`${issue.field}: ${issue.code} — ${issue.message}`);
}
} else if (error instanceof RateLimitError) {
// The SDK already retried; back off the caller.
console.error(`Rate limited. Retry in ${error.retryAfter ?? '?'}s`);
} else if (error instanceof SimploError) {
console.error(error.status, error.code, error.message, error.requestId);
} else {
throw error; // programming error — don't swallow it
}
}
Property Type Description
status number | undefined HTTP status (absent on connection failures)
code string | undefined Machine-readable API code, e.g. "VALIDATION_ERROR"
message string Human-readable description
requestId string | undefined Request ID — include it when contacting support
details ValidationIssue[] Field-level issues (field, code, message) on validation errors
action string | undefined Recommended next step from the API, when available
docUrl string Link to the relevant documentation
Status Class Retry? Typical causes
400/422 ValidationError No — fix the request Invalid RUT check digit, missing required fields
401 AuthenticationError No — fix credentials Wrong or revoked API key (Authentication)
403 PermissionError No The key lacks the required permission or role
404 NotFoundError No Wrong ID, or a resource from another company
409 ConflictError Depends (see below) Duplicate folio; idempotency key still in flight
429 RateLimitError Automatic Too many requests (limits are per company)
5xx ServerError Automatic (when safe) Transient API issue
— ConnectionError Automatic (when safe) Network failure, DNS, timeout
— WebhookVerificationError No — reject delivery Bad signature, stale timestamp (Webhooks)

Errors come from two layers, and their envelopes are not identical:

  • Platform (auth, allowlist, rate limit, required idempotency): { code, message, error, severity, action?, context? }.
  • Invoicing engine (emission validation, folios, states): proxied through with its own shape — code, message, action, plus severity/context where applicable.

Robust handling rules: branch on HTTP status + code when present, tolerate unknown fields, and never parse message as if it were a contract. Two real behaviors to keep in mind:

  • Some engine validations currently answer with a raw message and a generic code — e.g. omitting ind_servicio on a boleta yields a 400 with "IndServicio is required for boleta", not a catalog code. And in production some typed engine errors may arrive as plain text where the sandbox returns the structured code (RUT_INVALIDO, CAF_NO_DISPONIBLE) — a known divergence, fix pending. Design your handling to work from the status alone.
  • razon_social lengths have two layers: the engine enforces the SII cap (100 characters, receptor and emitter) and answers 400 RAZON_SOCIAL_INVALIDA naming the field and the limit; even longer strings (> 200) are cut earlier by the platform’s shape validation with a generic 400 {"code":"BAD_REQUEST","message":"invalid request"} without naming the field — if you see that one, check your string lengths first.
Code Description
401 Invalid, revoked or expired credential. The response doesn’t distinguish why (anti-enumeration): check the header and the sk_simplo_ prefix.
409 IDEMPOTENCY_IN_PROGRESS The same Idempotency-Key is still being processed. Wait and check the status instead of retrying immediately.
409 (duplicate folio) Folio conflict on emission.
422 IDEMPOTENCY_BODY_MISMATCH You reused an Idempotency-Key with a body different from the original. Use a new key per distinct emission.
422 BILLING_METADATA_NOT_SUPPORTED You sent metadata with content on emission. The field is not persisted yet, so we reject it instead of dropping it silently — remove it and correlate against the id the emission returns. {} and null do pass. See Emission.
429 Rate limit: 60 calls/min per company on API-key billing (120/min on MCP). Wait and retry — see Limits.
503 (idempotency) The idempotency service was temporarily unavailable during emission. Retry with the same key.

Emission validation codes (invoicing engine)

Section titled “Emission validation codes (invoicing engine)”

Validations that run before a folio is allocated — the same emission path in sandbox and production, so one of these 400s in sandbox is exactly what you would see in production (where they additionally prevent burning a real folio on a document the SII would reject):

Code Status What to do
EMISOR_INCOMPLETO 400 Your company is missing giro, address or comuna (context.missing_fields lists which). The SII requires these emitter fields in the XML (GiroEmis/DirOrigen/CmnaOrigen) — complete them in the app or via API and retry. Applies to every type, boletas included.
RAZON_SOCIAL_INVALIDA 400 razon_social (the receptor’s or your company’s) exceeds the SII schema’s 100-character cap (RznSocLargaType). Shorten it; the message carries the received length.
MONTO_INVALIDO 400 Negative quantity/price, or a final monto_total ≤ 0 on a document that carries amounts. Fix the lines — the exact field comes in context.field.
REFERENCIA_INVALIDA 400 A credit/debit note (56/61 and also the export ones, 111/112) references a type+folio that does not exist for your company. Check tipo_doc_ref/folio_ref against the original document.
RECEPTOR_INCOMPLETO 400 Invoice-family documents (33/34/43/46/56/61) require the receiver’s direccion and comuna — the real SII rejects without them (HED-3-845/HED-3-846). context.missing_fields lists what’s missing. Boletas (39/41) and export documents (110/111/112) don’t require them.
DETALLE_DESCUADRADO 400 You sent a monto_item that doesn’t match cantidad × precio (± discounts/surcharges, ±1 peso rounding tolerance). The error carries the expected and received value per line. To set a final line amount directly, omit precio and send only monto_item.
IND_TRASLADO_REQUERIDO 400 Dispatch guides (52) require ind_traslado per the SII table (1=sale … 9=export sale). Note: a sale guide (1 or 9) must also carry amounts — with price 0 it responds MONTO_INVALIDO.
EXPORT_TPO_CAMBIO_REQUERIDO 400 Export document in a foreign currency without tpo_cambio: the real SII rejects the 1:1 fallback (“Los Valores del Encabezado No Cuadran”). Send tpo_cambio (> 0); an explicit mnt_export or forma_pago_exp: 21 (no payment) remain valid without a rate.

These codes come from the Simplo platform (not the invoicing engine) and always have a clear way out:

Code Status What to do
BILLING_API_KEY_ROUTE_NOT_ALLOWED 403 The route is session-only. A billing:dte key only reaches the 6 DTE routes — see the quickstart’s table. It’s not an unactivated account.
BILLING_API_KEY_IDEMPOTENCY_KEY_REQUIRED 400 Add the Idempotency-Key header to your key-authenticated POST (/billing/dte and /billing/dte/send-boletas).
BILLING_API_KEY_NOT_ENABLED 403 Your company doesn’t have production emission by key enabled — write to [email protected]. The sandbox doesn’t need this.
BILLING_SANDBOX_API_KEY_NOT_ENABLED 403 Your plan doesn’t include the invoicing sandbox.
COMPANY_RUT_REQUIRED_FOR_SANDBOX 422 When creating a sandbox key: your company doesn’t have a RUT yet — complete it in the app and retry.
COMPANY_RAZON_SOCIAL_REQUIRED_FOR_SANDBOX 422 When creating a sandbox key: the business name (razón social) is missing — complete it in the app and retry.
SANDBOX_COMPANY_FLOW_REMOVED 410 You called the old POST /api/v1/sandbox/companies flow, which no longer exists: the sandbox is a mode of your own company — create a sandbox key under Developers.
SANDBOX_NOT_AVAILABLE 503 The sandbox is unavailable right now — try again later.

The SDK retries on 429, 5xx and network errors with exponential backoff and jitter, respecting the Retry-After header. Default: 2 retries; override with maxRetries on the client or per request.

Safety rule: a POST is only retried when it carries an Idempotency-Key. dtes.emit generates one automatically (UUID v4), so emission retries can never duplicate a document. Other POSTs (folios.upload, webhooks.create) aren’t retried automatically — call them again yourself if needed; both are safe to re-run (a duplicate CAF returns ConflictError).

const simplo = new Simplo({ maxRetries: 3, timeout: 30_000 });
await simplo.dtes.list({}, { maxRetries: 0 }); // per-request override
  • Emission — idempotency in detail
  • Limits — the per-company rate limit