Skip to content

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/v1

INFO

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

http
POST /invoices

Crea una venta y, salvo que lo indiques, emite su factura electrónica (documento fiscal) ante la DGI. Devuelve 201.

Campos del cuerpo

CampoTipoRequeridoDescripción
datestring (ISO date)NoFecha de la venta. Por defecto, el momento actual.
branchIdstring (cuid2)NoSucursal donde se registra la venta. Por defecto, la sucursal asociada a la API key.
clientIdstring (cuid2)NoID de un cliente existente. Tiene precedencia sobre client.
clientobjectNoCliente inline para buscar o crear (find-or-create). Ver tabla siguiente.
itemsarrayLíneas de la venta. Mínimo 1. Ver tabla de ítems.
paymentsarrayPagos que cubren el total. Mínimo 1. Ver tabla de pagos.
stampobjectNoControl 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)

CampoTipoRequeridoDescripción
namestringNombre del cliente.
identificationstringNoDocumento de identidad.
kindstringNoTipo de contribuyente: natural, juridica o gobierno.
rucstringNoRUC del cliente.
dvstringNoDígito verificador.
emailstringNoCorreo electrónico.
phonestringNoTeléfono.

Objeto de cada ítem (items[])

CampoTipoRequeridoDescripción
idstring (cuid2)Uno requeridoID de la variante de producto. Tiene precedencia sobre referenceId.
referenceIdstringUno requeridoReferencia externa del integrador (máx. 120 caracteres).
quantitynumberCantidad. Debe ser mayor que 0.
pricenumberNoPrecio unitario. Por defecto, el precio del catálogo.
warehouseIdstring (cuid2)NoBodega de donde se descuenta el stock.
discountnumberNoDescuento 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[])

CampoTipoRequeridoDescripción
paymentMethodstring (cuid2)ID del método de pago. Lo obtienes de .
amountnumberMonto del pago. Debe ser mayor que 0.

La suma de los pagos debe cubrir el total de la venta.

Objeto stamp

CampoTipoRequeridoDescripción
generateStampbooleanNoSi 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.

bash
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

javascript
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

python
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).

bash
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

javascript
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

python
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).

bash
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

javascript
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

python
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

json
{
  "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"
}
CampoTipoDescripción
idstring | nullID del documento fiscal. null si no se emitió documento.
numberstring | nullNúmero del documento. null si no se emitió.
typestringinvoice o credit_note.
datestringFecha de la venta.
statusstringEstado de la venta.
totalstringTotal de la venta.
clientIdstring | nullCliente asociado.
saleIdstring | nullID de la venta. Úsalo para registrar pagos con .
stampobjectEstado del documento fiscal. Ver abajo.
createdAtstringFecha de creación.

Objeto stamp en la respuesta

CampoTipoDescripción
statusstringpending, enrolled, failed, cancelled o skipped.
cufestring | nullCódigo Único de Factura Electrónica.
qrCodeDatastring | nullDatos del código QR del documento.
pdfUrlstring | nullURL del PDF del documento.
authorizationProtocolstring | nullProtocolo de autorización de la DGI.
enrolledAtstring | nullFecha 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

http
GET /invoices

Devuelve la lista de facturas en un envoltorio { "data": [ ... ] }. Soporta paginación y sincronización incremental (start, limit, updatedSince, from/to, metadata). Consulta la .

bash
curl "https://api-pos.zelta.dev/public/v1/invoices?limit=30&metadata=true" \
  -H "Authorization: Bearer zpk_live_xxx"

Ejemplo JS

javascript
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

python
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()
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

http
GET /invoices/{id}

Devuelve una factura como objeto directo (sin envoltorio). Responde 404 si no existe.

bash
curl "https://api-pos.zelta.dev/public/v1/invoices/doc_4f3g2h1j5k6l7m8n" \
  -H "Authorization: Bearer zpk_live_xxx"

Ejemplo JS

javascript
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

python
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

http
GET /credit-notes

Devuelve { "data": [ ... ] }. Soporta los mismos parámetros de paginación y sincronización incremental.

Obtener una nota de crédito

http
GET /credit-notes/{id}

Devuelve la nota de crédito como objeto directo. Responde 404 si no existe.

bash
curl "https://api-pos.zelta.dev/public/v1/credit-notes?limit=30" \
  -H "Authorization: Bearer zpk_live_xxx"

Ejemplo JS

javascript
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

python
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ódigoHTTPCausa probable
validation_error400Falta items o payments, una cantidad es ≤ 0, o los pagos no cubren el total.
unauthorized401API key ausente o inválida.
forbidden403La key no tiene acceso al recurso solicitado.
not_found404El id de la factura, el cliente o un ítem no existe.
conflict409Conflicto al registrar la venta (por ejemplo, número duplicado).
insufficient_stock409No hay stock suficiente en la bodega indicada.
rate_limited429Se superó el límite de peticiones.
internal_error500Error interno del servidor.

Siguientes pasos

  • — registra pagos adicionales sobre una venta con su saleId.
  • — gestiona los clientes que asocias a tus facturas.
  • — descubre los id de métodos de pago y sucursales.
  • — consulta las devoluciones generadas desde el dashboard.

Documentación oficial de Zelta