API de facturación electrónica
Emite facturas y notas de crédito autorizadas por el SRI de Ecuador desde tu tienda web, tu ERP o cualquier sistema. Solverline firma el comprobante, lo envía al SRI, le manda el correo al comprador y lo registra en la contabilidad de la empresa.
Dirección base: https://facturacion.solverline.com/api/v1
Todo viaja en JSON por HTTPS. Las fechas van en formato ISO (2026-09-23) y los montos en dólares, con punto decimal.
- La empresa crea una credencial en Solverline y te entrega el
client_idy la clave secreta. - Tu sistema cambia esos datos por un token, que dura 1 hora.
- Con el token llamas a las funciones: emitir factura, nota de crédito, consultar, catálogo, pedidos.
1. Credenciales
El administrador de la empresa entra a Solverline → API — Integraciones → Nueva credencial y elige:
- De pruebas o real. Las de pruebas empiezan con
slv_test_y no envían nada al SRI (ver Modo pruebas). - Permisos. Cada función exige uno:
| Permiso | Qué permite |
|---|---|
catalogo:read | Leer productos, precios y stock. |
pedidos:write | Crear pedidos (quedan como proformas) y consultarlos. |
facturas:write | Emitir facturas y notas de crédito al SRI. |
comprobantes:read | Consultar el estado de un comprobante y bajar su PDF y XML. |
2. Obtener el token
curl -X POST https://facturacion.solverline.com/api/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{"grant_type": "client_credentials",
"client_id": "slv_xxxxxxxx",
"client_secret": "slv_secret_xxxxxxxx"}'
Respuesta:
{
"access_token": "eyJhbGciOi...",
"refresh_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "facturas:write comprobantes:read"
}
Envía el token en cada llamada con la cabecera Authorization: Bearer <access_token>.
Renovar sin volver a mandar la clave
El refresh_token dura 30 días y sirve una sola vez: cada renovación entrega uno nuevo.
{"grant_type": "refresh_token", "refresh_token": "eyJhbGciOi..."}
Otras funciones del token
| Ruta | Para qué |
|---|---|
GET /oauth/me | Comprueba el token: devuelve la empresa, su RUC, los permisos y cuándo vence. |
POST /oauth/revoke | Anula el token que envías en la cabecera. |
Modo pruebas
Con una credencial de pruebas (slv_test_…) todas las funciones responden con la misma forma que en real, pero:
- No se envía nada al SRI y no se crean clientes, proformas ni comprobantes en la empresa.
- Los comprobantes de prueba usan la serie
999-999y el ambiente1. Su PDF dice «COMPROBANTE DE PRUEBA — SIN VALIDEZ TRIBUTARIA». - Las notas de crédito solo se pueden hacer sobre facturas de prueba.
- Los pedidos de prueba no se guardan, así que no se pueden consultar después.
- Una credencial real no ve los comprobantes de prueba, y una de prueba no ve los reales.
client_id y la clave por los de una credencial real. No hay que cambiar nada más del código.Límites y errores
| Límite | Valor |
|---|---|
| Llamadas por minuto, por credencial | 60. Si te pasas, recibes 429 con la cabecera Retry-After. |
| Duración del token | 60 minutos. |
| Duración del refresh token | 30 días. |
| Intentos fallidos de clave | 10 en 10 minutos bloquean esa credencial durante 10 minutos. |
| Productos por página en el catálogo | 200 como máximo. |
Los errores llegan así: {"detail": "texto que explica el problema"}. El texto está pensado para mostrarlo tal cual.
| Código | Qué significa |
|---|---|
400 | Faltan datos o no son válidos (por ejemplo, un IVA que no existe). |
401 | Token ausente, vencido o anulado. Pide uno nuevo. |
403 | Tu credencial no tiene ese permiso, está desactivada o la empresa llegó al límite de su plan. |
404 | No existe o no pertenece a tu empresa. |
409 | El comprobante todavía no está autorizado por el SRI. |
422 | El JSON no tiene la forma esperada (falta un campo obligatorio o tiene otro tipo). |
429 | Demasiadas llamadas. Espera los segundos que indica Retry-After. |
Emitir una factura
Solverline toma el secuencial, firma, envía al SRI y espera la autorización. La respuesta llega cuando el SRI contesta, normalmente en pocos segundos.
{
"cliente": {
"identificacion": "1790012345001",
"razon_social": "COMERCIAL EJEMPLO S.A.",
"email": "compras@ejemplo.com",
"telefono": "0991234567",
"direccion": "AV. AMAZONAS N24-100, QUITO"
},
"items": [
{ "sku": "ST-001", "descripcion": "SOPORTE TECNICO POR HORAS",
"cantidad": 2, "precio_unitario": 35.00, "descuento": 0, "iva": 15 }
],
"forma_pago": "TRANSFERENCIA",
"referencia_externa": "PEDIDO-10025",
"info_adicional": { "Vendedor": "Ana" }
}
| Campo | Obligatorio | Detalle |
|---|---|---|
cliente.identificacion | sí | RUC (13 dígitos), cédula (10), pasaporte o 9999999999999 para consumidor final. |
cliente.tipo_identificacion | no | Se deduce solo. Ver tabla. |
cliente.razon_social | sí | Nombre como figura en el RUC o la cédula. |
cliente.email | no | Si viene, el comprador recibe la factura en su correo. |
cliente.telefono, cliente.direccion | no | Aparecen en la factura. |
items[].descripcion | sí | Texto de la línea. |
items[].cantidad | sí | Mayor que cero. |
items[].precio_unitario | * | Precio sin IVA. Si no lo envías, se usa el precio del producto en Solverline (necesita sku). |
items[].sku | no | Código o SKU del producto en Solverline. Si coincide, la venta descuenta inventario. |
items[].descuento | no | Monto en dólares de toda la línea, no porcentaje. Por defecto 0. |
items[].iva | no | Porcentaje: 0, 5 o 15. Por defecto 15. |
forma_pago | no | Ver formas de pago. Por defecto SISTEMA_FINANCIERO. |
plazo_dias | no | Días de crédito, si aplica. |
referencia_externa | no | Tu número de pedido. Aparece en la factura como información adicional. |
info_adicional | no | Pares nombre → valor que se imprimen en la factura. |
Respuesta:
{
"estado": "AUTORIZADO",
"clave_acceso": "2309202601179999999900120010010000009624089753012",
"numero_autorizacion": "2309202601179999999900120010010000009624089753012",
"fecha_autorizacion": "2026-09-23 00:19:09-05:00",
"ambiente": "2",
"mensajes": []
}
Guarda la clave_acceso: identifica la factura para consultarla, bajar su PDF o hacerle una nota de crédito.
estado no es AUTORIZADO, revisa mensajes: ahí llega lo que respondió el SRI. Si llega EN_PROCESO, el SRI aún no contesta; consulta la factura en unos segundos con GET /public/comprobantes/{clave}. No la vuelvas a enviar, o se emitiría dos veces.Una factura emitida por la API queda pendiente de cobro en Solverline, aunque la forma de pago sea efectivo. El cobro se registra en la app.
Emitir una nota de crédito
Solo necesitas la clave de acceso de la factura original. Solverline toma de ella al comprador, el número y la fecha.
Anular toda la factura
{ "factura_clave_acceso": "2309202601179999999900120010010000009624089753012" }
Devolver una parte
{
"factura_clave_acceso": "2309202601179999999900120010010000009624089753012",
"motivo": "01",
"items": [ { "codigo": "ST-001", "cantidad": 1 } ]
}
| Campo | Detalle |
|---|---|
factura_clave_acceso | Obligatorio. La factura tiene que estar autorizada y ser de tu empresa. |
items | Opcional. Sin ítems, la nota de crédito cubre toda la factura. Con ítems, usa el codigo con que se facturó (el sku) y una cantidad que no supere la facturada. El precio y el IVA se toman de la factura. |
motivo | Opcional. 01 devolución, 02 anulación, 03 rebaja, 04 otros. Por defecto 02 si es toda la factura y 01 si es parcial. |
La respuesta tiene la misma forma que la de la factura, con la clave_acceso de la nota de crédito.
Consultar un comprobante, bajar el PDF y el XML
{
"clave_acceso": "2309202601179999999900120010010000009624089753012",
"tipo": "FACTURA",
"secuencial": "000000962",
"estado": "AUTORIZADO",
"numero_autorizacion": "2309202601179999999900120010010000009624089753012",
"fecha_emision": "2026-09-23",
"fecha_autorizacion": "2026-09-23T00:19:09-05:00",
"total": 80.5,
"ambiente": "produccion",
"mensaje_sri": null,
"pdf": "https://facturacion.solverline.com/api/v1/public/comprobantes/2309.../pdf",
"xml": "https://facturacion.solverline.com/api/v1/public/comprobantes/2309.../xml"
}
El PDF es el RIDE oficial; el XML es el comprobante autorizado por el SRI. Los dos exigen el token, así que descárgalos desde tu servidor. Si el comprobante aún no está autorizado, responden 409.
Catálogo de productos
| Parámetro | Detalle |
|---|---|
buscar | Texto que se busca en la descripción, el código y el SKU. |
categoria | Filtra por categoría exacta. |
con_stock | true solo con stock; false solo sin stock. |
pagina, por_pagina | Por defecto 1 y 50. Máximo 200 por página. |
GET /public/catalogo?buscar=soporte&pagina=1&por_pagina=50
{
"total": 42, "pagina": 1, "por_pagina": 50,
"productos": [
{ "id": "…", "codigo": "ST-001", "sku": null,
"descripcion": "SOPORTE TECNICO POR HORAS",
"precio": 35.0, "pvp2": null, "pvp3": null, "unidad_medida": null,
"iva": "4", "categoria": null, "stock": 0, "tipo": "servicio",
"imagen": null, "imagen2": null, "imagen3": null }
]
}
precio va sin IVA. iva es el código del SRI (ver tabla).
Pedidos (proformas)
Para tiendas que reciben pedidos y los facturan después, desde Solverline.
{
"modo": "proforma",
"cliente": { "identificacion": "0912345678", "razon_social": "MARIA PEREZ",
"email": "maria@correo.com" },
"items": [ { "sku": "ST-001", "descripcion": "SOPORTE TECNICO POR HORAS", "cantidad": 1 } ],
"notas": "Entregar en la mañana",
"referencia_externa": "WEB-5541",
"webhook_url": "https://mitienda.com/avisos/solverline"
}
Respuesta:
{
"modo": "proforma", "proforma_id": "…", "numero_proforma": "PRO-2026-030",
"referencia_externa": "WEB-5541",
"subtotal": 35.0, "total_iva": 5.25, "total": 40.25, "estado": "pendiente",
"url_ver": "https://facturacion.solverline.com/empresas/…/proformas/…"
}
modosolo acepta"proforma". Para facturar usaPOST /public/facturas.- Si envías
webhook_url, Solverline hace unPOSTa esa dirección con la misma respuesta. Tiene que ser una dirección pública. - Los ítems funcionan igual que en la factura:
precio_unitariosin IVA, o el precio de Solverline si mandas unskuválido.
Devuelve el estado de la proforma y, si la empresa ya la facturó, numero_factura y clave_acceso_sri.
Ejemplo completo: token y factura
<?php
$api = "https://facturacion.solverline.com/api/v1";
function llamar($metodo, $url, $datos = null, $token = null) {
$ch = curl_init($url);
$cab = ["Content-Type: application/json"];
if ($token) $cab[] = "Authorization: Bearer $token";
curl_setopt_array($ch, [CURLOPT_CUSTOMREQUEST => $metodo, CURLOPT_HTTPHEADER => $cab,
CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 90]);
if ($datos !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($datos));
$resp = json_decode(curl_exec($ch), true);
$codigo = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($codigo >= 400) throw new Exception($resp["detail"] ?? "Error $codigo");
return $resp;
}
$t = llamar("POST", "$api/oauth/token", ["grant_type" => "client_credentials",
"client_id" => getenv("SLV_CLIENT_ID"), "client_secret" => getenv("SLV_CLIENT_SECRET")]);
$factura = llamar("POST", "$api/public/facturas", [
"cliente" => ["identificacion" => "9999999999999", "razon_social" => "CONSUMIDOR FINAL"],
"items" => [["sku" => "ST-001", "descripcion" => "SOPORTE TECNICO POR HORAS", "cantidad" => 1]],
"forma_pago" => "TARJETA_CREDITO",
"referencia_externa" => "PEDIDO-10025",
], $t["access_token"]);
echo $factura["estado"] . " " . $factura["clave_acceso"];
const API = "https://facturacion.solverline.com/api/v1";
async function llamar(metodo, ruta, datos, token) {
const r = await fetch(API + ruta, {
method: metodo,
headers: { "Content-Type": "application/json", ...(token && { Authorization: `Bearer ${token}` }) },
body: datos ? JSON.stringify(datos) : undefined,
});
const json = await r.json();
if (!r.ok) throw new Error(json.detail || `Error ${r.status}`);
return json;
}
const t = await llamar("POST", "/oauth/token", { grant_type: "client_credentials",
client_id: process.env.SLV_CLIENT_ID, client_secret: process.env.SLV_CLIENT_SECRET });
const factura = await llamar("POST", "/public/facturas", {
cliente: { identificacion: "9999999999999", razon_social: "CONSUMIDOR FINAL" },
items: [{ sku: "ST-001", descripcion: "SOPORTE TECNICO POR HORAS", cantidad: 1 }],
forma_pago: "TARJETA_CREDITO",
referencia_externa: "PEDIDO-10025",
}, t.access_token);
console.log(factura.estado, factura.clave_acceso);
import os, requests
API = "https://facturacion.solverline.com/api/v1"
def llamar(metodo, ruta, datos=None, token=None):
cab = {"Authorization": f"Bearer {token}"} if token else {}
r = requests.request(metodo, API + ruta, json=datos, headers=cab, timeout=90)
if r.status_code >= 400:
raise Exception(r.json().get("detail", f"Error {r.status_code}"))
return r.json()
t = llamar("POST", "/oauth/token", {"grant_type": "client_credentials",
"client_id": os.environ["SLV_CLIENT_ID"], "client_secret": os.environ["SLV_CLIENT_SECRET"]})
factura = llamar("POST", "/public/facturas", {
"cliente": {"identificacion": "9999999999999", "razon_social": "CONSUMIDOR FINAL"},
"items": [{"sku": "ST-001", "descripcion": "SOPORTE TECNICO POR HORAS", "cantidad": 1}],
"forma_pago": "TARJETA_CREDITO",
"referencia_externa": "PEDIDO-10025",
}, t["access_token"])
print(factura["estado"], factura["clave_acceso"])
Reutiliza el token mientras dure (1 hora); no pidas uno nuevo en cada llamada.
Códigos y valores
IVA
Envías iva | Código SRI (catálogo) |
|---|---|
0 | 0 |
5 | 5 |
15 (por defecto) | 4 |
Tipo de identificación
| Código | Tipo | Se deduce cuando… |
|---|---|---|
04 | RUC | la identificación tiene 13 dígitos |
05 | Cédula | tiene 10 dígitos |
06 | Pasaporte | cualquier otro caso |
07 | Consumidor final | es 9999999999999 |
Formas de pago
| Valor | Significado |
|---|---|
EFECTIVO | Efectivo |
TRANSFERENCIA | Transferencia bancaria |
SISTEMA_FINANCIERO | Otro pago por el sistema financiero (por defecto) |
TARJETA_CREDITO · TARJETA_DEBITO · TARJETA_PREPAGO | Tarjetas |
DEBITO_BANCARIO · CHEQUE | Débito bancario, cheque |
DINERO_ELECTRONICO · DEUNA | Billeteras electrónicas |
CREDITO_15 · CREDITO_30 · CREDITO_45 · CREDITO_60 · CREDITO_90 | Venta a crédito a esos días |
Motivos de nota de crédito
| Código | Motivo |
|---|---|
01 | Devolución de mercadería |
02 | Anulación de factura |
03 | Rebaja en precio |
04 | Otros |