Facturas
El recurso Facturas te permite registrar ventas y, opcionalmente, emitir su factura electrónica ante la DGI. También expone las notas de crédito (solo lectura), que comparten la misma forma de respuesta.
Todas las rutas son relativas a la URL base de producción:
https://api-pos.zelta.dev/public/v1INFO
La autenticación se hace con el header Authorization: Bearer <key> o X-API-Key: <key>, usando una llave con prefijo zpk_. Consulta para más detalles.
Crear una venta
POST /invoicesCrea una venta y, salvo que lo indiques, emite su factura electrónica (documento fiscal) ante la DGI. Devuelve 201.
Campos del cuerpo
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
date | string (ISO date) | No | Fecha de la venta. Por defecto, el momento actual. |
branchId | string (cuid2) | No | Sucursal donde se registra la venta. Por defecto, la sucursal asociada a la API key. |
clientId | string (cuid2) | No | ID de un cliente existente. Tiene precedencia sobre client. |
client | object | No | Cliente inline para buscar o crear (find-or-create). Ver tabla siguiente. |
items | array | Sí | Líneas de la venta. Mínimo 1. Ver tabla de ítems. |
payments | array | Sí | Pagos que cubren el total. Mínimo 1. Ver tabla de pagos. |
stamp | object | No | Control del documento fiscal. Por defecto se emite el documento. |
Si omites tanto clientId como client, la venta se registra a nombre de Consumidor Final.
Objeto client (inline)
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
name | string | Sí | Nombre del cliente. |
identification | string | No | Documento de identidad. |
kind | string | No | Tipo de contribuyente: natural, juridica o gobierno. |
ruc | string | No | RUC del cliente. |
dv | string | No | Dígito verificador. |
email | string | No | Correo electrónico. |
phone | string | No | Teléfono. |
Objeto de cada ítem (items[])
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
id | string (cuid2) | Uno requerido | ID de la variante de producto. Tiene precedencia sobre referenceId. |
referenceId | string | Uno requerido | Referencia externa del integrador (máx. 120 caracteres). |
quantity | number | Sí | Cantidad. Debe ser mayor que 0. |
price | number | No | Precio unitario. Por defecto, el precio del catálogo. |
warehouseId | string (cuid2) | No | Bodega de donde se descuenta el stock. |
discount | number | No | Descuento de la línea. Debe ser ≥ 0. |
WARNING
El ITBMS (impuesto) de cada línea se toma del catálogo del producto y no puede sobrescribirse por API en las facturas. Por eso los ítems no aceptan un campo tax.
Objeto de cada pago (payments[])
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
paymentMethod | string (cuid2) | Sí | ID del método de pago. Lo obtienes de . |
amount | number | Sí | Monto del pago. Debe ser mayor que 0. |
La suma de los pagos debe cubrir el total de la venta.
Objeto stamp
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
generateStamp | boolean | No | Si emite documento fiscal. Por defecto true. Envía false para registrar la venta sin emitir factura electrónica. |
Direccionamiento de ítems
Cada ítem se referencia por id (cuid2 interno) o por referenceId (la referencia externa que maneja tu integración, máx. 120 caracteres). Debes enviar uno de los dos; si envías ambos, id tiene precedencia. El SKU o el código de barras no se aceptan por API.
Ejemplo 1: venta a Consumidor Final (mínima)
Sin client, un ítem por referenceId y un solo pago.
curl -X POST "https://api-pos.zelta.dev/public/v1/invoices" \
-H "Authorization: Bearer zpk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "referenceId": "SKU-CAFE-500", "quantity": 2 }
],
"payments": [
{ "paymentMethod": "pm_2x9a8b7c6d5e4f3g", "amount": 10.70 }
]
}'Ejemplo JS
const res = await fetch('https://api-pos.zelta.dev/public/v1/invoices', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
items: [
{ referenceId: 'SKU-CAFE-500', quantity: 2 }
],
payments: [
{ paymentMethod: 'pm_2x9a8b7c6d5e4f3g', amount: 10.70 }
]
})
});
const data = await res.json();Ejemplo Py
import requests
res = requests.post(
'https://api-pos.zelta.dev/public/v1/invoices',
headers={'Authorization': 'Bearer zpk_live_xxx', 'Content-Type': 'application/json'},
json={
'items': [
{ 'referenceId': 'SKU-CAFE-500', 'quantity': 2 }
],
'payments': [
{ 'paymentMethod': 'pm_2x9a8b7c6d5e4f3g', 'amount': 10.70 }
]
}
)
data = res.json()Ejemplo 2: venta a un cliente con datos fiscales
Cliente inline con ruc y kind (find-or-create).
curl -X POST "https://api-pos.zelta.dev/public/v1/invoices" \
-H "Authorization: Bearer zpk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"client": {
"name": "Distribuidora El Istmo, S.A.",
"kind": "juridica",
"ruc": "155612345-2-2018",
"dv": "47",
"email": "[email protected]"
},
"items": [
{ "referenceId": "SKU-CAFE-500", "quantity": 12, "discount": 5.00 }
],
"payments": [
{ "paymentMethod": "pm_2x9a8b7c6d5e4f3g", "amount": 59.20 }
]
}'Ejemplo JS
const res = await fetch('https://api-pos.zelta.dev/public/v1/invoices', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
client: {
name: 'Distribuidora El Istmo, S.A.',
kind: 'juridica',
ruc: '155612345-2-2018',
dv: '47',
email: '[email protected]'
},
items: [
{ referenceId: 'SKU-CAFE-500', quantity: 12, discount: 5.00 }
],
payments: [
{ paymentMethod: 'pm_2x9a8b7c6d5e4f3g', amount: 59.20 }
]
})
});
const data = await res.json();Ejemplo Py
import requests
res = requests.post(
'https://api-pos.zelta.dev/public/v1/invoices',
headers={'Authorization': 'Bearer zpk_live_xxx', 'Content-Type': 'application/json'},
json={
'client': {
'name': 'Distribuidora El Istmo, S.A.',
'kind': 'juridica',
'ruc': '155612345-2-2018',
'dv': '47',
'email': '[email protected]'
},
'items': [
{ 'referenceId': 'SKU-CAFE-500', 'quantity': 12, 'discount': 5.00 }
],
'payments': [
{ 'paymentMethod': 'pm_2x9a8b7c6d5e4f3g', 'amount': 59.20 }
]
}
)
data = res.json()Ejemplo 3: venta sin emitir documento fiscal
Útil para registrar una venta interna sin generar factura electrónica (stamp.generateStamp = false).
curl -X POST "https://api-pos.zelta.dev/public/v1/invoices" \
-H "Authorization: Bearer zpk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "id": "itm_9k8j7h6g5f4d3s2a", "quantity": 1 }
],
"payments": [
{ "paymentMethod": "pm_2x9a8b7c6d5e4f3g", "amount": 25.00 }
],
"stamp": { "generateStamp": false }
}'Ejemplo JS
const res = await fetch('https://api-pos.zelta.dev/public/v1/invoices', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
items: [
{ id: 'itm_9k8j7h6g5f4d3s2a', quantity: 1 }
],
payments: [
{ paymentMethod: 'pm_2x9a8b7c6d5e4f3g', amount: 25.00 }
],
stamp: { generateStamp: false }
})
});
const data = await res.json();Ejemplo Py
import requests
res = requests.post(
'https://api-pos.zelta.dev/public/v1/invoices',
headers={'Authorization': 'Bearer zpk_live_xxx', 'Content-Type': 'application/json'},
json={
'items': [
{ 'id': 'itm_9k8j7h6g5f4d3s2a', 'quantity': 1 }
],
'payments': [
{ 'paymentMethod': 'pm_2x9a8b7c6d5e4f3g', 'amount': 25.00 }
],
'stamp': { 'generateStamp': False }
}
)
data = res.json()Respuesta
{
"id": "doc_4f3g2h1j5k6l7m8n",
"number": "FE-0000123",
"type": "invoice",
"date": "2026-06-30T14:21:08.000Z",
"status": "completed",
"total": "10.70",
"clientId": null,
"saleId": "sal_1a2b3c4d5e6f7g8h",
"stamp": {
"status": "pending",
"cufe": null,
"qrCodeData": null,
"pdfUrl": null,
"authorizationProtocol": null,
"enrolledAt": null
},
"createdAt": "2026-06-30T14:21:08.000Z"
}| Campo | Tipo | Descripción |
|---|---|---|
id | string | null | ID del documento fiscal. null si no se emitió documento. |
number | string | null | Número del documento. null si no se emitió. |
type | string | invoice o credit_note. |
date | string | Fecha de la venta. |
status | string | Estado de la venta. |
total | string | Total de la venta. |
clientId | string | null | Cliente asociado. |
saleId | string | null | ID de la venta. Úsalo para registrar pagos con . |
stamp | object | Estado del documento fiscal. Ver abajo. |
createdAt | string | Fecha de creación. |
Objeto stamp en la respuesta
| Campo | Tipo | Descripción |
|---|---|---|
status | string | pending, enrolled, failed, cancelled o skipped. |
cufe | string | null | Código Único de Factura Electrónica. |
qrCodeData | string | null | Datos del código QR del documento. |
pdfUrl | string | null | URL del PDF del documento. |
authorizationProtocol | string | null | Protocolo de autorización de la DGI. |
enrolledAt | string | null | Fecha de enrolamiento del documento. |
Flujo del documento fiscal
Cuando emites una factura electrónica, el stamp.status arranca en pending mientras se procesa ante la DGI y pasa a enrolled cuando queda autorizada (ahí se rellenan cufe, qrCodeData y pdfUrl). Si envías stamp.generateStamp = false, el estado será skipped. Consulta la factura nuevamente con GET /invoices/{id} para ver el stamp actualizado.
Listar facturas
GET /invoicesDevuelve la lista de facturas en un envoltorio { "data": [ ... ] }. Soporta paginación y sincronización incremental (start, limit, updatedSince, from/to, metadata). Consulta la .
curl "https://api-pos.zelta.dev/public/v1/invoices?limit=30&metadata=true" \
-H "Authorization: Bearer zpk_live_xxx"Ejemplo JS
const res = await fetch('https://api-pos.zelta.dev/public/v1/invoices?limit=30&metadata=true', {
headers: { 'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}` }
});
const data = await res.json();Ejemplo Py
import requests
res = requests.get(
'https://api-pos.zelta.dev/public/v1/invoices',
headers={'Authorization': 'Bearer zpk_live_xxx'},
params={'limit': 30, 'metadata': 'true'}
)
data = res.json(){
"data": [
{
"id": "doc_4f3g2h1j5k6l7m8n",
"number": "FE-0000123",
"type": "invoice",
"date": "2026-06-30T14:21:08.000Z",
"status": "completed",
"total": "10.70",
"clientId": null,
"saleId": "sal_1a2b3c4d5e6f7g8h",
"stamp": {
"status": "enrolled",
"cufe": "FE0120000...",
"qrCodeData": "https://dgi-fep.mef.gob.pa/...",
"pdfUrl": "https://api-pos.zelta.dev/docs/doc_4f3g2h1j5k6l7m8n.pdf",
"authorizationProtocol": "2026000123456",
"enrolledAt": "2026-06-30T14:21:12.000Z"
},
"createdAt": "2026-06-30T14:21:08.000Z"
}
],
"metadata": { "total": 1 }
}Obtener una factura
GET /invoices/{id}Devuelve una factura como objeto directo (sin envoltorio). Responde 404 si no existe.
curl "https://api-pos.zelta.dev/public/v1/invoices/doc_4f3g2h1j5k6l7m8n" \
-H "Authorization: Bearer zpk_live_xxx"Ejemplo JS
const res = await fetch('https://api-pos.zelta.dev/public/v1/invoices/doc_4f3g2h1j5k6l7m8n', {
headers: { 'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}` }
});
const data = await res.json();Ejemplo Py
import requests
res = requests.get(
'https://api-pos.zelta.dev/public/v1/invoices/doc_4f3g2h1j5k6l7m8n',
headers={'Authorization': 'Bearer zpk_live_xxx'}
)
data = res.json()Notas de crédito
Las notas de crédito son de solo lectura por API y comparten la misma forma de respuesta que las facturas, con type: "credit_note".
Listar notas de crédito
GET /credit-notesDevuelve { "data": [ ... ] }. Soporta los mismos parámetros de paginación y sincronización incremental.
Obtener una nota de crédito
GET /credit-notes/{id}Devuelve la nota de crédito como objeto directo. Responde 404 si no existe.
curl "https://api-pos.zelta.dev/public/v1/credit-notes?limit=30" \
-H "Authorization: Bearer zpk_live_xxx"Ejemplo JS
const res = await fetch('https://api-pos.zelta.dev/public/v1/credit-notes?limit=30', {
headers: { 'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}` }
});
const data = await res.json();Ejemplo Py
import requests
res = requests.get(
'https://api-pos.zelta.dev/public/v1/credit-notes',
headers={'Authorization': 'Bearer zpk_live_xxx'},
params={'limit': 30}
)
data = res.json()Problemas comunes
| Código | HTTP | Causa probable |
|---|---|---|
validation_error | 400 | Falta items o payments, una cantidad es ≤ 0, o los pagos no cubren el total. |
unauthorized | 401 | API key ausente o inválida. |
forbidden | 403 | La key no tiene acceso al recurso solicitado. |
not_found | 404 | El id de la factura, el cliente o un ítem no existe. |
conflict | 409 | Conflicto al registrar la venta (por ejemplo, número duplicado). |
insufficient_stock | 409 | No hay stock suficiente en la bodega indicada. |
rate_limited | 429 | Se superó el límite de peticiones. |
internal_error | 500 | Error interno del servidor. |
Siguientes pasos
- — registra pagos adicionales sobre una venta con su
saleId. - — gestiona los clientes que asocias a tus facturas.
- — descubre los
idde métodos de pago y sucursales. - — consulta las devoluciones generadas desde el dashboard.