Facturar en Ecuador
Emitir una factura
POST /api/v1/invoices/issue · scope invoices:write
El payload mínimo es corto: un comprador y una línea. Todo lo demás tiene un valor por defecto sensato.
{ "comprador": { "identificacion": "1792060346001", "razon_social": "COMERCIAL ANDINA S.A." }, "items": [ { "descripcion": "Servicio de soporte", "cantidad": 1, "precio_unitario": 100.00 } ]}Eso emite una factura con IVA 0 %, con fecha de hoy y el secuencial que toque. En la práctica querrás enviar algo más.
Campos de primer nivel
Sección titulada «Campos de primer nivel»| Campo | Obligatorio | Por defecto | Notas |
|---|---|---|---|
comprador | ✅ | — | Ver más abajo |
items | ✅ | — | Al menos una línea |
referencia_externa | — | — | Envíala siempre. Tu identificador de la venta |
fecha_emision | — | Hoy en Ecuador | YYYY-MM-DD. No puede ser futura |
fecha_vencimiento | — | — | Para ventas a crédito |
establecimiento | — | El de la clave o el del negocio | { "codigo", "punto_emision" } |
secuencial | — | El siguiente de la serie | Solo si controlas la numeración |
totales | — | — | Si lo envías, se comprueba |
moneda | — | USD | El SRI solo admite dólares |
info_adicional | — | [] | Campos libres que salen en el RIDE |
campos_personalizados | — | {} | Valores de los campos personalizados del negocio por su clave ({"orden_compra": "OC-7781"}). Los marcados «mostrar en el documento» van al RIDE y al infoAdicional del XML |
enviar_email | — | true | Enviar el RIDE al comprador al autorizarse |
notas | — | — | Texto libre interno |
comprador
Sección titulada «comprador»{ "tipo_identificacion": "04", "identificacion": "1792060346001", "razon_social": "COMERCIAL ANDINA S.A.", "email": "facturas@andina.ec", "telefono": "0991234567", "direccion": "Av. Amazonas N24-03, Quito"}| Campo | Obligatorio | Notas |
|---|---|---|
identificacion | ✅ | RUC, cédula, pasaporte… |
razon_social | ✅ | Nombre legal del comprador |
tipo_identificacion | — | Se deduce del formato si no lo envías |
email | — | Necesario si quieres que el RIDE llegue por correo |
telefono, direccion | — | Salen en el RIDE |
Tipos de identificación del SRI:
| Código | Tipo |
|---|---|
04 | RUC (13 dígitos) |
05 | Cédula (10 dígitos) |
06 | Pasaporte |
07 | Consumidor final |
08 | Identificación del exterior |
09 | Placa |
Para ventas sin datos del cliente, ver Consumidor final.
El comprador se guarda como contacto
Sección titulada «El comprador se guarda como contacto»Si la identificación no corresponde a ningún cliente del negocio, Olympus crea el contacto automáticamente con los datos que enviaste. Así el negocio ve a sus clientes en Olympus aunque facture solo desde tu sistema.
Si prefieres que no, desactiva “Crear clientes automáticamente” en la clave, desde Configuración → Integraciones.
{ "codigo_principal": "SRV-001", "descripcion": "Servicio de soporte mensual", "cantidad": 1, "unidad_medida": "Unidad", "precio_unitario": 100.00, "descuento": 0, "impuestos": [{ "codigo": "2", "codigo_porcentaje": "4" }]}| Campo | Obligatorio | Notas |
|---|---|---|
descripcion | ✅ | Lo que ve el cliente en el RIDE |
cantidad | ✅ | Mayor que cero |
precio_unitario | ✅ | Sin impuestos, dos decimales |
codigo_principal | — | Si coincide con el SKU de un producto, la línea se enlaza a él |
descuento | — | En importe, no en porcentaje |
unidad_medida | — | Por defecto Unidad |
impuestos | — | Sin este campo, la línea va con IVA 0 % |
El cálculo de cada línea es:
subtotal = cantidad × precio_unitario − descuentoIVA = subtotal × tarifaEnlazar con productos del negocio
Sección titulada «Enlazar con productos del negocio»Si codigo_principal coincide con el SKU de un producto de Olympus, la línea queda enlazada. Eso
tiene una consecuencia importante: si el producto es un bien, la venta descuenta stock, y si no
hay existencias la emisión falla con 422 insufficient_stock.
Si no quieres tocar inventario, usa un código que no exista como SKU o deja codigo_principal vacío.
totales
Sección titulada «totales»{ "total_sin_impuestos": 100.00, "descuento": 0, "importe_total": 115.00 }Es opcional pero muy recomendable. Si lo envías, Olympus compara con lo que calcula y rechaza la
emisión si no cuadra (422 totals_mismatch, tolerancia 0,01).
Sirve para que nunca se emita un comprobante distinto del que tu sistema contabilizó: si vuestros redondeos difieren, te enteras antes de que el SRI lo autorice, no después.
La respuesta
Sección titulada «La respuesta»202 Accepted:
{ "id": "6f1e8a24-91cd-4c73-9a1f-2b0f5d7e4c88", "tipo": "factura", "estado": "pendiente", "clave_acceso": "2409202601179001234500110010010000000011234567813", "secuencial": "001-001-000000001", "ambiente": "produccion", "fecha_emision": "2026-09-24", "referencia_externa": "SO-2026-0912", "totales": { "total_sin_impuestos": 100.0, "descuento": 0.0, "iva": 15.0, "importe_total": 115.0 }, "urls": { "detalle": "…", "pdf": null, "xml": null, "publica": null }}Guarda el id y la clave_acceso. El id es tu referencia para consultar, descargar y reenviar;
la clave de acceso es el identificador del comprobante ante el SRI, y es lo que tu cliente usará para
verificarlo.
Los enlaces de pdf, xml y publica llegan vacíos hasta que el SRI autorice.
Qué pasa después
Sección titulada «Qué pasa después»El SRI suele tardar segundos. Consulta el estado con GET /invoices/{id} o espera el webhook
(próximamente). Ver Estados de un comprobante.
Ejemplo completo
Sección titulada «Ejemplo completo»curl -X POST https://olympus.atharix.com/api/v1/invoices/issue \ -H "X-API-Key: $OLYMPUS_API_KEY" \ -H "Idempotency-Key: venta-4821" \ -H "Content-Type: application/json" \ -d '{ "referencia_externa": "SO-2026-0912", "establecimiento": { "codigo": "001", "punto_emision": "001" }, "comprador": { "tipo_identificacion": "04", "identificacion": "1792060346001", "razon_social": "COMERCIAL ANDINA S.A.", "email": "facturas@andina.ec", "direccion": "Av. Amazonas N24-03, Quito" }, "items": [ { "codigo_principal": "SRV-001", "descripcion": "Servicio de soporte mensual", "cantidad": 1, "precio_unitario": 100.00, "impuestos": [{ "codigo": "2", "codigo_porcentaje": "4" }] }, { "codigo_principal": "LIC-010", "descripcion": "Licencia adicional", "cantidad": 2, "precio_unitario": 25.00, "descuento": 5.00, "impuestos": [{ "codigo": "2", "codigo_porcentaje": "4" }] } ], "totales": { "total_sin_impuestos": 145.00, "descuento": 5.00, "importe_total": 166.75 }, "info_adicional": [{ "nombre": "Orden de compra", "valor": "OC-7781" }] }'factura = client.post( "/invoices/issue", headers={"Idempotency-Key": f"venta-{venta.id}"}, json={ "referencia_externa": venta.codigo, "establecimiento": {"codigo": "001", "punto_emision": "001"}, "comprador": { "tipo_identificacion": venta.cliente.tipo_id, "identificacion": venta.cliente.identificacion, "razon_social": venta.cliente.razon_social, "email": venta.cliente.email, "direccion": venta.cliente.direccion, }, "items": [ { "codigo_principal": linea.sku, "descripcion": linea.descripcion, "cantidad": float(linea.cantidad), "precio_unitario": float(linea.precio), "descuento": float(linea.descuento), "impuestos": [{"codigo": "2", "codigo_porcentaje": linea.codigo_iva}], } for linea in venta.lineas ], "totales": { "total_sin_impuestos": float(venta.base), "descuento": float(venta.descuento), "importe_total": float(venta.total), }, },).json()
venta.factura_id = factura["id"]venta.clave_acceso = factura["clave_acceso"]venta.save()Errores frecuentes
Sección titulada «Errores frecuentes»| Error | Causa habitual |
|---|---|
totals_mismatch | Redondeos distintos entre tu sistema y Olympus. Revisa línea a línea |
external_id_conflict | La venta ya estaba facturada. Recupera la factura con details.id |
business_not_ready | El negocio no tiene la firma electrónica configurada |
insufficient_stock | Una línea enlaza un producto sin existencias |
establishment_not_found | El código de establecimiento no existe. Consulta GET /establishments |
validation_error | Falta un campo o tiene un valor inválido; details dice cuál |
Todos, con detalle, en Errores.