Facturiadocs
Start here

The sandbox

Test mode — what it guarantees, how to force a rejection or a reparo, and the test helpers that move the outside world.

What it is, and what it is not

Test mode (sk_test_)SII certification (sk_live_, empresa on ambiente: certificacion)Production (sk_live_, empresa on ambiente: produccion)
Traffic to the SII (Chile's tax authority)nonemaullin / apicert (the SII's test servers)palena / api.sii.cl
Certificate requirednoyes (the empresa's real .pfx)yes
Real CAF required (the SII-signed block that folios come from)no (virtual folios)yes (a maullin CAF)yes
Latency to a terminal state~1 s, deterministicminutes to hoursminutes to hours
Outcomeforceablewhatever the SII sayswhatever the SII says
Legal effectnonenonefull
Pricefree foreverthe single plan: $19.990 CLP + IVA (Chile's VAT) a month per empresa, unlimited documentsthe same plan, the same price

Test mode is a complete in-process simulation. It never opens a socket to the SII, so it is instant, deterministic and immune to the SII's maintenance windows. SII certification is the real SII on its test servers, used during go-live to certify one specific empresa — you will rarely drive it directly.

Determinism guarantees

In test mode:

  • Folios start at 1 for each (empresa, tipo_dte) and increment by one. No CAF is needed; GET /v1/folios reports a virtual range from 1 to 1.000.000.
  • The track_id is a synthetic increasing 10-digit integer starting at 9000000001.
  • Documents settle queued → sent → accepted in roughly a second, firing every webhook a real document would fire.
  • The XML is schema-valid, signed with Facturia's sandbox certificate, and carries a TED built from a sandbox CAF. It will not validate against the SII — that is the point.
  • PDFs come with the real layout plus a SIN VALOR TRIBUTARIO watermark.
  • Every object carries "mode": "test" stamped on it.

Test-mode rate limits are the same as live's, so a load test is representative.

Forcing outcomes

Add a test object to any issuance.

curl -sS https://api.facturia.cl/v1/documents \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Facturia-Empresa: $EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{
    "tipo_dte": 33,
    "receptor": {
      "rut": "76543212-K",
      "giro": "Venta al por mayor de alimentos",
      "address": { "street": "Los Militares 5001, of. 302", "comuna": "Las Condes", "city": "Santiago" }
    },
    "items": [{ "description": "Consultoría", "quantity": 1, "unit_price": 1200000 }],
    "test": { "outcome": "rejected", "reason_code": "TED-2-510", "settle_after_ms": 3000 }
  }'
test.outcomeResult
accepted (the default)status: accepted, SII status DOK
reparostatus: reparo (accepted with objections), SII status DNK, a rejection_reason
rejectedstatus: rejected, envelope status RCT, reason_code returned as sent
failedstatus: failed before transmitting (simulates a build or signing failure)
stuckstays at sent forever (to test your polling timeouts)

Trap

The test field is an alarm, not an option

Sending the test object with a live key is rejected with 400 test_field_in_live_mode. It is a deliberate trip wire: simulation code cannot quietly run against the SII.

If your client cannot add fields, the magic receptor RUTs do the same job:

Receptor RUTForces
44444440-1accepted
44444441-Kreparo
44444442-8rejected
44444443-6failed
44444444-4stuck
66666666-6anonymous final consumer (valid in live as well, and only on boletas, Chile's consumer receipts)

Test helpers

The endpoints under /v1/test_helpers exist only for test keys (403 test_mode_only otherwise). They let you drive the halves of the system the outside world normally drives.

# Inyectar un DTE de proveedor como si hubiera llegado a la casilla de la empresa
# (una empresa de sandbox no tiene casilla real: `reception_email` va en null)
curl -sS https://api.facturia.cl/v1/test_helpers/received_documents \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Facturia-Empresa: $EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{
    "tipo_dte": 33,
    "folio": 8812,
    "emisor": { "rut": "96790240-3", "legal_name": "Proveedor Austral SpA" },
    "issue_date": "2026-08-12",
    "totals": { "net": 450000, "iva": 85500, "total": 535500 }
  }'
# Sembrar filas de RCV en un período para que la propuesta de F29 tenga sobre qué calcular
curl -sS https://api.facturia.cl/v1/test_helpers/rcv/seed \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{"period":"2026-07","purchases":12,"sales":40}'

# Adelantar el reloj simulado de esta empresa
# (prueba la ventana de acuse de 8 días, el vencimiento de folios, los plazos del F29)
curl -sS https://api.facturia.cl/v1/test_helpers/clock \
  -H "Authorization: Bearer $FACTURIA_KEY" -H "Facturia-Empresa: $EMPRESA" \
  -H "Content-Type: application/json" \
  -d '{"advance_days":9}'

# Llevar una máquina de estados de puesta en marcha a cualquier estado sin esperar al SII
curl -sS https://api.facturia.cl/v1/test_helpers/empresas/emp_01K2R.../onboarding \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"action_required","step":"sii_postulacion"}'

# Forzar UN paso: lo que el estado completo no alcanza a expresar
# (una prueba de certificación que falla para el 61 mientras el 33 pasa)
curl -sS https://api.facturia.cl/v1/test_helpers/empresas/emp_01K2R.../onboarding/steps \
  -H "Authorization: Bearer $FACTURIA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"step":"set_de_pruebas","tipo_dte":61,"status":"failed"}'

The clock helper is how you test the 8-day window

Inject a received document, advance the clock nine days and watch the commercial response stop accepting actions with 422 commercial_response_window_closed. It is the only way to exercise that deadline without waiting more than a week. The window is explained in The 8-day window.

Upstream realities worth designing for anyway

Above our own limits there are two SII realities that exist even though the sandbox does not simulate them: the SII rate-limits without publishing its numbers, and it has scheduled maintenance windows. Issuance is queued on our side, so a burst over 10 req/s is a client-side 429, never a lost document — but a document we have already accepted will sit in queued longer during an SII outage. Build on webhooks, not on tight polling.

Next step

On this page