Skip to content

Ventas ​

Cuando un comprador paga en tu tienda en línea, registra la venta en Zenci con POST /orders. La venta queda cobrada y entregada en una sola llamada: descuenta las existencias, aparece en Ventas → Órdenes con la etiqueta Tienda en línea y, si el comercio tiene la facturación electrónica configurada, se factura sola unos segundos después.

EndpointPermiso
POST /ordersorders:write
GET /ordersorders:read
GET /orders/{id}orders:read

Antes de vender ​

  1. Revisa la clave con . Si defaultBranchId es null, cada venta tiene que enviar branchId: búscalo en .
  2. Lee las formas de pago con y guarda sus id.
  3. Ten a mano los SKU de tus productos, que salen de .
  4. Si la venta lleva factura a nombre del comprador, búscalo o créalo con y envía su customerId.

La petición ​

Encabezados ​

EncabezadoObligatorioDescripción
Authorization o X-API-KeySíTu clave de API
Content-TypeRecomendadoapplication/json
X-Idempotency-KeySíUn identificador único de esta venta en tu tienda, por ejemplo el número de tu pedido. Consulta

Cuerpo ​

CampoTipoObligatorioDescripción
itemslistaSíAl menos una línea
items[].skutextoSíSKU de la variante. Debe coincidir exactamente, con mayúsculas y minúsculas; no se busca por código de barras
items[].quantitytextoSíCantidad mayor que cero, hasta 6 decimales: "1", "2", "0.5"
items[].pricetextoNoPrecio por unidad sin impuesto, hasta 2 decimales. Solo con el permiso orders:set-price; consulta
paymentslistaSíAl menos un pago
payments[].paymentMethodIdtextoSíUn id de GET /payment-methods
payments[].amounttextoSíMonto mayor que cero, hasta 2 decimales
branchIdtextoNoSucursal de la venta. Si no lo envías, se usa la de la clave
customerIdtexto o nullNoCliente al que se le vende y se le factura. Búscalo o créalo con . Sin cliente, la factura sale a consumidor final
companyIdtexto o nullNoUna de las compañías del cliente, para facturarle a ella y no a la principal. Necesita customerId

No hay campos para descuentos, notas ni envío. Cualquier campo que no esté en esta tabla se rechaza con 400.

Ejemplo ​

Un par de sandalias talla 38, pagado en efectivo con 40.00:

bash
curl -X POST https://api.zenci.app/public/v1/orders \
  -H "Authorization: Bearer $ZENCI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: pedido-10482" \
  -d '{
    "items": [
      { "sku": "MB-029-CRU-38", "quantity": "1" }
    ],
    "payments": [
      { "paymentMethodId": "m1a5s9d3f7g2h6j0k4l8z2x6", "amount": "40.00" }
    ]
  }'

Respuesta 201:

json
{
  "id": "o5h9j3k7l1z4x8c2v6b0n4m8",
  "displayId": "004518",
  "status": "paid",
  "customerId": null,
  "companyId": null,
  "invoice": null,
  "subtotal": "36.00",
  "taxes": "2.52",
  "total": "38.52",
  "items": [
    {
      "sku": "MB-029-CRU-38",
      "name": "Sandalia Crucero",
      "quantity": "1",
      "price": "36.00",
      "total": "36.00"
    }
  ],
  "payments": [
    {
      "paymentMethodId": "m1a5s9d3f7g2h6j0k4l8z2x6",
      "paymentMethodName": "Efectivo",
      "amount": "38.52",
      "tenderedAmount": "40.00",
      "change": "1.48"
    }
  ],
  "change": "1.48",
  "createdAt": "2026-10-06T15:56:12.000Z"
}
CampoDescripción
idIdentificador de la venta en Zenci. Guárdalo junto a tu pedido
displayIdNúmero de orden que ve el comercio en Órdenes
statusEstado del cobro: paid en una venta completa
customerId, companyIdEl cliente y la compañía a la que se factura, o null
invoiceLa factura electrónica (ver ). Al registrar la venta todavía es null: se emite unos segundos después
subtotalSuma de las líneas, sin impuesto
taxesImpuesto total
totalLo que se cobró: subtotal + taxes
items[].skuEl SKU que enviaste
items[].nameNombre del producto
items[].pricePrecio por unidad sin impuesto con el que se vendió
items[].totalprice × quantity, sin impuesto
payments[].amountLo que se aplicó a la venta
payments[].tenderedAmountLo que entregó el comprador. Igual a amount si pagó exacto
payments[].changeEl vuelto de ese pago; "0.00" si no hubo
changeLa suma de los vueltos

Cómo se calcula el total ​

Los precios de Zenci no incluyen el impuesto. Para cobrarle al comprador lo mismo que registrará Zenci, calcula así, línea por línea:

  1. Base de la línea = price × quantity, redondeada a centavos.
  2. Impuesto de la línea = base × taxRate ÷ 100, redondeado a centavos. Si taxRate es null, el impuesto es 0.00.
  3. Subtotal = suma de las bases. Impuesto = suma de los impuestos de cada línea. Total = subtotal + impuesto.

El redondeo es al centavo más cercano y, en el medio exacto, hacia arriba (0.125 → 0.13).

Ejemplo con tres líneas al 7 %:

LíneaBaseImpuesto
1 × MB-029-CRU-38 a 36.0036.002.52
1 × MB-087-PAN-M a 12.5012.500.88 (de 0.875)
1 × MB-087-PAN-L a 12.5012.500.88 (de 0.875)
Sumasubtotal 61.00impuesto 4.28

Total: 61.00 + 4.28 = 65.28.

Redondea el impuesto por línea, no sobre el subtotal. Sobre el subtotal saldría 61.00 × 7 % = 4.27, un centavo menos: tu tienda cobraría 65.27 y la venta se rechazaría porque el pago no cubre el total.

El taxRate y el price que debes usar son los de GET /products con la misma clave.

Los pagos ​

  • La suma de los pagos tiene que cubrir el total. Si queda corta, la respuesta es 409 conflict con el mensaje Payments do not cover the order total. La venta se anula en el acto: no queda cobrada ni descuenta existencias, aunque el comercio puede verla como anulada en Órdenes. Corrige los montos y reenvía con la misma X-Idempotency-Key.
  • Pagar de más solo se puede con una forma de pago que da vuelto, como el efectivo. El pago se aplica hasta el total y el resto se devuelve en change.
  • Con las demás formas de pago, el monto debe ser exacto. Un monto de más con tarjeta responde 400 (Payment amount exceeds remaining balance of …) y la venta se anula, igual que en el caso anterior.
  • Puedes combinar pagos, por ejemplo una parte con tarjeta y otra en efectivo. Lo más simple para una tienda en línea es un solo pago por el total exacto.

El precio de venta ​

Por defecto, el precio lo pone Zenci: cada línea se vende al precio de la variante en el catálogo o, si la clave tiene una lista de precios, en esa lista. Es el mismo price que ves en GET /products.

Si tu tienda necesita vender a otro precio (una oferta propia, por ejemplo), la clave necesita el permiso orders:set-price. Con él, puedes enviar price en cada línea: es el precio por unidad sin impuesto, y el impuesto se suma igual que siempre. Las líneas sin price siguen usando el precio de Zenci.

Si envías price con una clave que no tiene ese permiso, la venta se rechaza con 400 (This API key cannot set prices…). No se ignora en silencio: así nunca cobras un precio y registras otro.

Reintentos e idempotencia ​

Todo POST /orders lleva X-Idempotency-Key. Sin ella, la respuesta es 400 (The X-Idempotency-Key header is required).

La regla es una sola: la clave manda, el cuerpo no. Zenci registra una sola venta por cada X-Idempotency-Key de tu clave de API.

  • Reintento con la misma clave y el mismo cuerpo: recibes 201 con la venta original. No se cobra ni se descuenta otra vez.
  • Reintento con la misma clave y un cuerpo distinto: también recibes 201 con la venta original. Zenci no compara el cuerpo: los cambios que hayas hecho no se aplican.
  • Una venta nueva lleva una clave nueva. Usa el identificador de tu pedido, no uno al azar en cada intento: así un reintento siempre reconoce su venta.

La clave es propia de cada clave de API. Dos tiendas del mismo comercio que usan claves de API distintas pueden mandar la misma X-Idempotency-Key sin chocar.

Cuándo reintentar ​

RespuestaQué hacer
Sin respuesta (corte de red, tiempo agotado)Reintenta con la misma clave. Si la venta ya entró, recibes la original
409 The original sale is still being registered. Retry in a few seconds.La venta original todavía se está registrando. Espera lo que dice el encabezado Retry-After y reintenta con la misma clave
500Reintenta con la misma clave en unos segundos
409 Payments do not cover the order totalCorrige los pagos y reenvía con la misma clave: la venta fallida no la ocupa
409 We could not confirm whether the original sale completed. Review it before charging again.No reintentes. La venta con esa clave quedó a medias hace más de cinco minutos: pídele al comercio que la revise en Órdenes
409 The original sale was cancelled while completing. Register it manually.No reintentes. La venta se anuló mientras se registraba: el comercio debe registrarla a mano
400, 403, 404Corrige la petición y reenvíala. Una venta rechazada no ocupa la clave, así que puedes usar la misma
429Espera unos segundos y reintenta con la misma clave

Qué pasa con la venta en Zenci ​

  • Se registra en la sucursal de la clave, o en la de branchId, y descuenta las existencias de la bodega de esa sucursal en el momento.
  • Queda pagada y entregada. No pasa por ninguna caja, así que no aparece en el cierre de una caja.
  • Se factura sola si el comercio tiene configurada la facturación electrónica, a nombre del cliente y la compañía de la venta. Si el cliente tiene correo, la DGI se la envía como cualquier otra factura. Si el comercio no factura electrónicamente, la venta queda sin factura y no da error.
  • Aparece en Ventas → Órdenes con la etiqueta Tienda en línea.
  • Una presentación enlazada (por ejemplo, una caja de 12) descuenta sus unidades de la variante base.
  • Los servicios no mueven existencias.
Lista de Órdenes con la orden 004518 marcada con la etiqueta Tienda en línea, pagada y entregadaLista de Órdenes con la orden 004518 marcada con la etiqueta Tienda en línea, pagada y entregada
Una venta registrada por la API, en Órdenes, con la etiqueta Tienda en línea.

Sin existencias suficientes ​

Depende de cómo tenga configurado el comercio la venta sin existencias. Si la permite, la venta se registra y las existencias quedan en negativo. Si la bloquea, la venta se rechaza con 400 y no se cobra nada; el mensaje dice qué producto no alcanzó: Not enough stock for "Sandalia Crucero". Consulta GET /stock antes de cobrar para evitarlo.

Errores frecuentes ​

HTTPMensajeCausa
400The X-Idempotency-Key header is requiredFalta el encabezado
400A JSON body is requiredEl cuerpo no es JSON
400Unknown SKU "…"El SKU no existe, está eliminado o tiene otra escritura
400No branch was provided and this API key has no default branch…La clave no tiene sucursal y no enviaste branchId
400This API key cannot set prices…Enviaste price sin el permiso orders:set-price
400Invalid "items.0.quantity": …Un campo con formato inválido; el mensaje dice cuál
400Payment amount exceeds remaining balance of …Pago de más con una forma de pago que no da vuelto
400Unknown or disabled payment method "…"El paymentMethodId no existe o el comercio lo apagó
400"…" cannot be used to charge a sale through the API…Una forma de pago que la API no acepta: crédito, nota de crédito, tarjeta de regalo o retención
400"companyId" requires "customerId"Enviaste una compañía sin cliente
400The billing company does not belong to this customerLa compañía no es de ese cliente
400Not enough stock for "…"El comercio no permite vender sin existencias
403Insufficient permissionsLa clave no tiene orders:write
404Customer not foundEl customerId no es un cliente del comercio
404Warehouse not found for branch …El branchId no es una sucursal del comercio
409Payments do not cover the order totalLos pagos no llegan al total

El formato de todos los errores está en la .

La factura ​

Después de registrar la venta, Zenci emite la factura electrónica por detrás. Mientras la DGI responde, la venta trae la factura en pending; cuando la acepta, en enrolled, con su número, su CUFE y un enlace al PDF. Normalmente tarda unos segundos.

Para enterarte sin consultar a cada rato, suscríbete al order.invoiced. También puedes consultar la venta:

http
GET /orders/{id}
json
{
  "id": "o5h9j3k7l1z4x8c2v6b0n4m8",
  "displayId": "004518",
  "status": "paid",
  "customerId": "u3i7o1p5a9s2d6f0g4h8j2k6",
  "companyId": null,
  "invoice": {
    "id": "f7g1h5j9k3l6z0x4c8v2b6n0",
    "status": "enrolled",
    "number": "004518",
    "cufe": "FE01200000155612345-2-2015-001000000000451820261006…",
    "issuedAt": "2026-10-06T15:56:19.000Z",
    "pdfUrl": "https://api.zenci.app/public/v1/invoices/f7g1h5j9k3l6z0x4c8v2b6n0/pdf?tenant=…&expires=…&signature=…",
    "pdfUrlExpiresAt": "2026-10-06T16:11:24.000Z"
  },
  "subtotal": "36.00",
  "taxes": "2.52",
  "total": "38.52",
  "items": [ … ],
  "payments": [ … ],
  "change": "1.48",
  "createdAt": "2026-10-06T15:56:12.000Z"
}
invoice.statusQué significa
pendingSe envió a la DGI y todavía no hay respuesta
enrolledLa DGI la aceptó. Trae number, cufe, issuedAt (en UTC) y pdfUrl
failedLa DGI la rechazó. El comercio la corrige y reintenta desde Zenci
review_requiredLa respuesta fue ambigua y el comercio la revisa en Zenci
cancelledSe anuló

number es el número de la factura en Zenci, el mismo que ve el comercio en Órdenes. El comprobante de la DGI muestra además el número de documento del proveedor; lo que identifica la factura ante la DGI es el cufe.

invoice es null mientras la factura se crea, y también si el comercio no tiene la facturación electrónica configurada o si la factura no se pudo crear. En ese último caso llega el aviso order.invoice_failed con el motivo, por ejemplo que el cliente no tiene DV: corrige el cliente y Zenci la vuelve a intentar sola en las horas siguientes.

El PDF ​

pdfUrl es un enlace temporal: vence a los 15 minutos (pdfUrlExpiresAt) y no necesita la clave de API, así que puedes mostrárselo al comprador o descargar el PDF desde tu servidor. Si venció, vuelve a consultar la venta y recibirás uno nuevo. Úsalo tal cual: cualquier cambio en el enlace lo invalida.

Consultar tus ventas ​

http
GET /orders
ParámetroTipoDescripción
pageenteroPágina, desde 1. Por defecto 1
limitenteroDe 1 a 100. Por defecto 50
createdSincefecha ISO 8601Solo las ventas registradas en ese momento o después
createdUntilfecha ISO 8601Solo las registradas antes de ese momento

Cada clave ve solo las ventas que registró ella, de la más reciente a la más antigua. Cada elemento trae id, displayId, status, total, customerId, companyId, invoice y createdAt; para las líneas y los pagos, consulta GET /orders/{id}. Una venta de otra clave responde 404.

Siguientes pasos ​

  • — A quién le vendes y a qué compañía se factura.
  • — Con qué puedes cobrar.
  • — Entérate cuando la venta se factura, se anula o se devuelve.

Documentación oficial de Zelta