Quickstart
Create your sandbox API key at app.simplo.cl and emit your first DTE (Chilean electronic invoice or receipt) in minutes, no certificate or CAF required — against the same REST API as production, with curl, C#, Java, Python or the TypeScript SDK.
Emit your first DTE (Documento Tributario Electronico — a Chilean electronic
tax document, such as an invoice or receipt) in your own company’s
sandbox: the same model as Stripe, where the test environment isn’t
another account but a mode of yours, selected by the API key
(sk_simplo_sandbox_… vs sk_simplo_…). A synthetic certificate and
folios (CAF) are provisioned for you — no digital certificate to buy, no
folios to file with the SII, no talking to sales. Simplo is a REST API:
any language that speaks HTTP + JSON works. Here’s the same flow in curl,
C#, Java, Python and the TypeScript SDK.
The sandbox’s amounts and tax math (VAT) are not fake — it’s the same engine that produces real invoices. The only thing simulated is the SII: the document never really reaches the SII and has no tax validity. When you’re ready to emit real documents, see Go to production.
1. Create your sandbox key
Section titled “1. Create your sandbox key”With your Simplo account created (and your company carrying its real RUT and razón social), go to app.simplo.cl → Desarrolladores and create a sandbox API key. That’s the whole procedure: any plan with Facturación includes the sandbox, self-serve.
The key is shown exactly once — save it now:
export SIMPLO_API_KEY="sk_simplo_sandbox_3f9a...e1"Prefer the API? The same mint exists as an endpoint (with your session JWT, owner or admin role — see Authentication):
curl -sS -X POST https://api.simplo.cl/api/v1/api-keys \ -H "Authorization: Bearer $SIMPLO_JWT" \ -H "Content-Type: application/json" \ -d '{"name": "mi integración", "environment": "sandbox", "scopes": ["billing:dte"]}'// 201 Created{ "id": "a1b2c3d4-...", "name": "mi integración", "prefix": "sk_simpl", "is_sandbox": true, "key": "sk_simplo_sandbox_3f9a...e1"}If your company doesn’t have its RUT or razón social yet, the mint responds
422 (COMPANY_RUT_REQUIRED_FOR_SANDBOX /
COMPANY_RAZON_SOCIAL_REQUIRED_FOR_SANDBOX) — complete your company’s
identity in the app and retry. There’s no certificate step and no CAF step:
when the key is created, your company’s sandbox is provisioned with a
synthetic certificate and folios, ready to emit.
2. Emit an invoice
Section titled “2. Emit an invoice”The exact same endpoint as production — POST /api/v1/billing/dte, with an
Idempotency-Key header (UUID) so a retry can never duplicate the invoice.
Not a single line of the example changes between sandbox and
production; the only thing that changes is which API key you use.
curl -X POST https://api.simplo.cl/api/v1/billing/dte \ -H "X-Api-Key: $SIMPLO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "tipo_dte": 33, "receptor": { "rut": "76543210-3", "razon_social": "Cliente Ejemplo SpA", "giro": "Servicios de software", "direccion": "Av. Providencia 1234", "comuna": "Providencia" }, "detalle": [ { "nombre": "Servicios de consultoría", "cantidad": 10, "precio": 50000 } ] }'// 201 Created{ "id": "4f9c9c2e-8a49-4f2e-9d5f-0d8f3a1b2c3d", "folio": 1042, "tipo_dte": 33, "estado": "firmado", "monto_total": 595000, "created_at": "2026-08-01T14:30:00Z"}using System.Net.Http.Json;using System.Text.Json;
var http = new HttpClient { BaseAddress = new Uri("https://api.simplo.cl") };http.DefaultRequestHeaders.Add("X-Api-Key", Environment.GetEnvironmentVariable("SIMPLO_API_KEY"));
var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/billing/dte"){ Content = JsonContent.Create(new { tipo_dte = 33, // Electronic Invoice receptor = new { rut = "76543210-3", razon_social = "Cliente Ejemplo SpA", giro = "Servicios de software", direccion = "Av. Providencia 1234", comuna = "Providencia", }, detalle = new[] { new { nombre = "Servicios de consultoría", cantidad = 10, precio = 50000 }, }, })};request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await http.SendAsync(request);response.EnsureSuccessStatusCode(); // 201
var dte = await response.Content.ReadFromJsonAsync<JsonElement>();Console.WriteLine($"folio {dte.GetProperty("folio")} — {dte.GetProperty("estado")}");// folio 1042 — firmadoimport java.net.URI;import java.net.http.*;import java.util.UUID;
var body = """ { "tipo_dte": 33, "receptor": { "rut": "76543210-3", "razon_social": "Cliente Ejemplo SpA", "giro": "Servicios de software", "direccion": "Av. Providencia 1234", "comuna": "Providencia" }, "detalle": [ { "nombre": "Servicios de consultoría", "cantidad": 10, "precio": 50000 } ] }""";
HttpClient http = HttpClient.newHttpClient();HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://api.simplo.cl/api/v1/billing/dte")) .header("X-Api-Key", System.getenv("SIMPLO_API_KEY")) .header("Content-Type", "application/json") .header("Idempotency-Key", UUID.randomUUID().toString()) .POST(HttpRequest.BodyPublishers.ofString(body)) .build();
HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());System.out.println(response.statusCode()); // 201System.out.println(response.body());// {"id":"4f9c…","folio":1042,"estado":"firmado",…}import http.client, json, os, uuid
conn = http.client.HTTPSConnection("api.simplo.cl")conn.request( "POST", "/api/v1/billing/dte", body=json.dumps({ "tipo_dte": 33, # Electronic Invoice "receptor": { "rut": "76543210-3", "razon_social": "Cliente Ejemplo SpA", "giro": "Servicios de software", "direccion": "Av. Providencia 1234", "comuna": "Providencia", }, "detalle": [ {"nombre": "Servicios de consultoría", "cantidad": 10, "precio": 50000} ], }), headers={ "X-Api-Key": os.environ["SIMPLO_API_KEY"], "Content-Type": "application/json", "Idempotency-Key": str(uuid.uuid4()), },)
dte = json.loads(conn.getresponse().read())print(dte["folio"], dte["estado"]) # 1042 firmadoPreview of how it will look once published:
import Simplo from '@simplohq/sdk';
const simplo = new Simplo(); // reads SIMPLO_API_KEY
const dte = await simplo.dtes.emit({ tipo_dte: 33, // Electronic Invoice receptor: { rut: '76543210-3', razon_social: 'Cliente Ejemplo SpA', giro: 'Servicios de software', direccion: 'Av. Providencia 1234', comuna: 'Providencia', }, detalle: [{ nombre: 'Servicios de consultoría', cantidad: 10, precio: 50000 }],});
console.log(dte.folio, dte.estado); // 1042 "firmado"The SDK will generate the Idempotency-Key and retry with backoff for you —
see TypeScript SDK.
The 201 response returns as soon as the document is signed
(estado: "firmado"): folio assigned from the active CAF, XML generated,
electronic stamp (TED) and digital signature ready. Submission to the SII
(or, in sandbox, to the SII simulator) happens asynchronously.
Amounts are integer Chilean pesos (CLP). VAT (19%) is computed by the API — you send net prices.
3. Watch the status
Section titled “3. Watch the status”By polling GET /api/v1/billing/dte/{id}. In sandbox, the full cycle plays
out in front of you in seconds — firmado → enviando → procesando → aceptado — so you actually see that the SII (real or simulated) is
asynchronous, not just another synchronous call:
curl https://api.simplo.cl/api/v1/billing/dte/4f9c9c2e-8a49-4f2e-9d5f-0d8f3a1b2c3d \ -H "X-Api-Key: $SIMPLO_API_KEY"// 200 OK (excerpt){ "estado": "aceptado", "tracking": { "track_id": "1234567890", "estado_envio": "aceptado" }}var current = await http.GetFromJsonAsync<JsonElement>( $"/api/v1/billing/dte/{dte.GetProperty("id")}");Console.WriteLine(current.GetProperty("estado"));// "enviando" | "procesando" | "aceptado" | "rechazado" | "con_reparos"HttpRequest poll = HttpRequest.newBuilder() .uri(URI.create("https://api.simplo.cl/api/v1/billing/dte/" + dteId)) .header("X-Api-Key", System.getenv("SIMPLO_API_KEY")) .GET() .build();System.out.println(http.send(poll, HttpResponse.BodyHandlers.ofString()).body());// "estado": "enviando" | "procesando" | "aceptado" | "rechazado" | "con_reparos"conn.request( "GET", f"/api/v1/billing/dte/{dte['id']}", headers={"X-Api-Key": os.environ["SIMPLO_API_KEY"]},)current = json.loads(conn.getresponse().read())print(current["estado"])# "enviando" | "procesando" | "aceptado" | "rechazado" | "con_reparos"Preview — the SDK isn’t published yet, use the curl tab:
const current = await simplo.dtes.retrieve(dte.id);console.log(current.estado); // "enviando" | "procesando" | "aceptado" | …console.log(current.tracking?.track_id); // SII track ID once submittedThe full status lifecycle is documented in
States. In sandbox you can force a verdict
(rechazado, con_reparos, exhausted folios) with magic receptor RUTs
instead of waiting for the happy path — see step 5.
Webhook delivery is not live yet — you follow the verdict by polling
GET /api/v1/billing/dte/{id}, as above. See Webhooks.
4. Emit a boleta (type 39)
Section titled “4. Emit a boleta (type 39)”A receipt (boleta) to an end consumer is the same endpoint with
tipo_dte: 39 and one extra required field: ind_servicio (use 3 —
boleta for sales and services). Omitting it is the most common 400 when
integrating boletas:
curl -X POST https://api.simplo.cl/api/v1/billing/dte \ -H "X-Api-Key: $SIMPLO_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "tipo_dte": 39, "ind_servicio": 3, "receptor": { "rut": "66666666-6", "razon_social": "Cliente Boleta" }, "detalle": [ { "nombre": "Producto", "cantidad": 1, "precio": 10000 } ] }'One difference from the invoice: on boletas precio is gross (VAT
included, shelf-price style) — monto_total stays at 10000 and the engine
breaks the VAT out internally (monto_neto: 8403). On invoices, precio is
net and the API adds the VAT. The response is a 201 with
estado: "firmado", just like the invoice.
5. Try the unhappy paths
Section titled “5. Try the unhappy paths”A DTE aceptado on the first try is the easy case. To test your error
handling — SII rejection, exhausted folios, a DTE stuck in procesando —
without waiting for it to happen by chance, the sandbox ships magic
receptor RUTs that force each outcome deterministically. See
Try the unhappy paths.
6. Download the PDF and XML
Section titled “6. Download the PDF and XML”# PDF with PDF417 electronic stamp (add ?cedible=true for factoring)curl -o factura.pdf \ "https://api.simplo.cl/api/v1/billing/dte/$DTE_ID/pdf" \ -H "X-Api-Key: $SIMPLO_API_KEY"
# Signed XML (ISO-8859-1)curl -o factura.xml \ "https://api.simplo.cl/api/v1/billing/dte/$DTE_ID/xml" \ -H "X-Api-Key: $SIMPLO_API_KEY"With the SDK (once it ships): simplo.dtes.pdf(dte.id, { cedible: true }) and
simplo.dtes.xml(dte.id).
What your API key can do
Section titled “What your API key can do”A billing:dte key (sandbox or live) reaches exactly six routes —
the ones you just used, plus the boleta envelope. Everything else in the
billing API is managed with your session at app.simplo.cl, never with the key:
| Operation | Via API key? |
|---|---|
POST /api/v1/billing/dte — emit (requires Idempotency-Key) |
✅ |
GET /api/v1/billing/dte — list |
✅ |
GET /api/v1/billing/dte/{id} — detail and status |
✅ |
GET /api/v1/billing/dte/{id}/pdf · …/xml — downloads |
✅ |
POST /api/v1/billing/dte/send-boletas — send a set of boletas in one envelope (requires Idempotency-Key) |
✅ |
| CAF/folios, company, certificate, libros, webhooks, clients, products | ❌ 403 BILLING_API_KEY_ROUTE_NOT_ALLOWED — session only |
POST /api/v1/mcp (MCP for agents) |
❌ 401 — the MCP takes agent:read keys or OAuth, not billing:dte |
No “preflight” needed: in the sandbox the certificate and folios are
already provisioned — just emit. If one of your calls returns
403 BILLING_API_KEY_ROUTE_NOT_ALLOWED, it’s not a problem with your
account: that route simply isn’t part of the key surface.
Next steps
Section titled “Next steps”- Try the unhappy paths — magic RUTs and the structured error catalog
- Go to production — the honest go-live checklist: real certificate, SII certification, real CAF
- The OpenAPI contract — download the spec and generate a C#, Java or TypeScript client
- Endpoint reference — all 27 endpoints, with curl, C#, Java and JavaScript examples
- Authentication — API keys and advanced auth
- DTE types — the 12 document types
- Errors — error handling and retries
- Pagination — cursor-based listings
- Webhooks — the delivery contract (not live yet; use polling)
- Multi-company — operating more than one company
- Agents — pointers for coding agents