Skip to content

Apartados

Un apartado (layaway) es una venta puesta en espera: el cliente reserva la mercancía y la abona a lo largo del tiempo. Al crearlo, Zelta POS reserva el stock y la orden solo puede entregarse (y facturarse) cuando queda pagada en su totalidad.

La URL base de producción es https://api-pos.zelta.dev/public/v1 y todas las rutas se expresan relativas a ella. Todas las peticiones deben hacerse sobre HTTPS e incluir tu API key. Consulta .

INFO

A diferencia de una venta directa ():

  • el cliente es obligatorio (clientId o client inline);
  • el abono inicial (deposit) es opcional (solo si tu cuenta permite apartados sin depósito) y debe cumplir la política de depósito mínimo configurada;
  • no se emite documento fiscal hasta que el apartado se entrega.

INFO

Los montos y cantidades en las respuestas se devuelven como strings decimales (por ejemplo "59.20"), tal como en el resto de la API. Los campos numéricos de los requests se envían como números.

Se notifica al cliente por correo

Si el cliente tiene un correo registrado, Zelta POS le envía un correo automático al crear el apartado y en cada abono parcial (con el recibo en PDF adjunto). El abono que salda el apartado por completo no genera este correo. Este comportamiento está activado por defecto a nivel de cuenta (ajuste notifications.email_layaway_to_customer) y hoy no se puede desactivar desde la app. Tenlo en cuenta al registrar apartados o abonos de forma masiva desde una integración.

Registrar un apartado

http
POST /layaways

Reserva el stock para un cliente y, opcionalmente, registra un abono inicial. Devuelve 201.

Campos del cuerpo

CampoTipoRequeridoDescripción
branchIdstring (cuid2)NoSucursal del apartado. Por defecto, la sucursal de la API key.
clientIdstring (cuid2)CondicionalCliente existente. Tiene precedencia sobre client. Requerido si no envías client.
clientobjectCondicionalCliente inline para buscar o crear (find-or-create). Requerido si no envías clientId.
itemsarrayLíneas del apartado. Mínimo 1.
items[].idstring (cuid2)Uno requeridoID de la variante de producto. Tiene precedencia sobre referenceId.
items[].referenceIdstring (≤120)Uno requeridoReferencia externa de la variante.
items[].quantitynumber (>0)Cantidad.
items[].pricenumber (≥0)NoPrecio unitario. Por defecto, el del catálogo.
items[].warehouseIdstring (cuid2)NoBodega de la que se reserva el stock de la línea.
items[].discountnumber (≥0)NoDescuento por línea.
depositarrayNoAbono inicial. Cada elemento tiene paymentMethod y amount.
deposit[].paymentMethodstring (cuid2)ID del método de pago. Lo obtienes de .
deposit[].amountnumber (>0)Monto del abono.

WARNING

El ITBMS de cada línea se toma del catálogo del producto y no puede sobrescribirse por API: los ítems no aceptan un campo tax. Las notas de crédito no se aceptan como método de pago en apartados (son un instrumento interno del POS).

bash
curl -X POST "https://api-pos.zelta.dev/public/v1/layaways" \
  -H "Authorization: Bearer zpk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "cl8h3k6m9p2q5v8r1n4t7w0y",
    "items": [
      { "referenceId": "SKU-1001", "quantity": 2, "price": 120.00 }
    ],
    "deposit": [
      { "paymentMethod": "pm_2x9a8b7c6d5e4f3g", "amount": 50.00 }
    ]
  }'

Ejemplo JS

javascript
const res = await fetch('https://api-pos.zelta.dev/public/v1/layaways', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({
    clientId: 'cl8h3k6m9p2q5v8r1n4t7w0y',
    items: [
      { referenceId: 'SKU-1001', quantity: 2, price: 120.00 }
    ],
    deposit: [
      { paymentMethod: 'pm_2x9a8b7c6d5e4f3g', amount: 50.00 }
    ]
  })
});
const data = await res.json();

Ejemplo Py

python
import requests
res = requests.post(
    'https://api-pos.zelta.dev/public/v1/layaways',
    headers={'Authorization': 'Bearer zpk_live_xxx', 'Content-Type': 'application/json'},
    json={
        'clientId': 'cl8h3k6m9p2q5v8r1n4t7w0y',
        'items': [
            { 'referenceId': 'SKU-1001', 'quantity': 2, 'price': 120.00 }
        ],
        'deposit': [
            { 'paymentMethod': 'pm_2x9a8b7c6d5e4f3g', 'amount': 50.00 }
        ]
    }
)
data = res.json()

Respuesta 201 Created:

json
{
  "id": "la2k5m8p1q4v7r0n3t6w9y2z",
  "number": "APT-000034",
  "clientId": "cl8h3k6m9p2q5v8r1n4t7w0y",
  "branchId": "br3b8n5k2j7h9g4f1d6s0a8q",
  "paymentStatus": "partial",
  "fulfillmentStatus": "reserved",
  "subtotal": "240.00",
  "taxes": "16.80",
  "discounts": "0",
  "total": "256.80",
  "amountPaid": "50.00",
  "balanceRemaining": "206.80",
  "expiresAt": "2026-08-15T23:59:59.000Z",
  "items": [
    {
      "productVariantId": "pv9z2x4c6v8b1n3m5k7j9h2g",
      "name": "Bicicleta urbana - Talla M",
      "quantity": "2",
      "unitPrice": "120.00",
      "discount": "0",
      "tax": "7"
    }
  ],
  "payments": [
    {
      "id": "pay_3c4d5e6f7g8h9i0j",
      "paymentMethodId": "pm_2x9a8b7c6d5e4f3g",
      "amount": "50.00",
      "reference": "P-000123",
      "date": "2026-07-16T15:00:00.000Z"
    }
  ],
  "createdAt": "2026-07-16T15:00:00.000Z",
  "updatedAt": "2026-07-16T15:00:00.000Z"
}
CampoTipoDescripción
paymentStatusstringpending, partial, paid, expired o cancelled.
fulfillmentStatusstringreserved, prepared, delivered, expired, extended (prórroga) o cancelled.
subtotal / taxes / discounts / totalstringTotales del apartado.
amountPaidstringSuma de los abonos registrados.
balanceRemainingstringSaldo pendiente por pagar.
expiresAtstring | nullFecha límite del apartado. null si no tiene vencimiento.
items[]arrayLíneas: productVariantId (puede ser null), name, quantity, unitPrice, discount, tax.
payments[]arrayAbonos: id, paymentMethodId, amount, reference (folio P-XXXXXX), date.

Problemas comunes

CódigoHTTPCausa
validation_error400Falta el cliente, no hay ítems, o el depósito no cumple la política de depósito mínimo.
unauthorized401API key ausente o inválida.
not_found404El clientId, la branchId o una variante no existen.
insufficient_stock409No hay existencias suficientes para reservar.

Listar apartados

http
GET /layaways

Acepta los parámetros de : start, limit, updatedSince, from, to y metadata. Además:

ParámetroTipoDescripción
statusstringFiltra por estado de entrega (reserved, prepared, delivered, expired, extended, cancelled).
includePaidbooleanPor defecto se excluyen los apartados ya pagados por completo (pasan a ser ventas). Envía true para incluirlos.
bash
curl "https://api-pos.zelta.dev/public/v1/layaways?status=reserved&metadata=true" \
  -H "Authorization: Bearer zpk_live_xxx"

Ejemplo JS

javascript
const res = await fetch('https://api-pos.zelta.dev/public/v1/layaways?status=reserved&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/layaways',
    headers={'Authorization': 'Bearer zpk_live_xxx'},
    params={'status': 'reserved', 'metadata': 'true'}
)
data = res.json()

Devuelve { "data": [ ... ] } con la misma forma que la respuesta de creación y, con ?metadata=true, agrega { "metadata": { "total": N } }.

Obtener un apartado

http
GET /layaways/{id}
CampoTipoRequeridoDescripción
idstring (cuid2)Identificador del apartado.

Devuelve el apartado directamente (sin envoltorio), incluyendo sus items y payments (abonos). Si no existe, responde 404 not_found.

Registrar un abono

http
POST /layaways/{id}/payments

Registra un pago contra el saldo pendiente del apartado. Cuando el saldo llega a cero, el apartado queda listo para entregar. Devuelve 201.

Campos del cuerpo

CampoTipoRequeridoDescripción
paymentMethodstring (cuid2)ID del método de pago.
amountnumber (>0)Monto del abono.

INFO

Igual que en , el abono no acepta una referencia libre: el campo reference de la respuesta es un folio secuencial generado por el sistema (P-XXXXXX).

bash
curl -X POST "https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/payments" \
  -H "Authorization: Bearer zpk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "paymentMethod": "pm_2x9a8b7c6d5e4f3g", "amount": 100.00 }'

Ejemplo JS

javascript
const res = await fetch('https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/payments', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ paymentMethod: 'pm_2x9a8b7c6d5e4f3g', amount: 100.00 })
});
const data = await res.json();

Ejemplo Py

python
import requests
res = requests.post(
    'https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/payments',
    headers={'Authorization': 'Bearer zpk_live_xxx', 'Content-Type': 'application/json'},
    json={ 'paymentMethod': 'pm_2x9a8b7c6d5e4f3g', 'amount': 100.00 }
)
data = res.json()

Respuesta 201 Created:

json
{
  "id": "pay_9i0j1k2l3m4n5o6p",
  "paymentMethodId": "pm_2x9a8b7c6d5e4f3g",
  "amount": "100.00",
  "reference": "P-000124",
  "allocations": [
    { "orderId": "la2k5m8p1q4v7r0n3t6w9y2z", "amount": "100.00" }
  ],
  "date": "2026-07-20T10:30:00.000Z"
}

Prórroga

http
POST /layaways/{id}/extend

Otorga días adicionales de reserva a un apartado vencido. Solo pueden extenderse los apartados cuyo plazo ya expiró; el inventario permanece reservado. Devuelve 200 con el apartado actualizado.

Campos del cuerpo

CampoTipoRequeridoDescripción
daysnumber (entero, 1–365)Días adicionales de reserva.
bash
curl -X POST "https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/extend" \
  -H "Authorization: Bearer zpk_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{ "days": 15 }'

Ejemplo JS

javascript
const res = await fetch('https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/extend', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}`, 'Content-Type': 'application/json' },
  body: JSON.stringify({ days: 15 })
});
const data = await res.json();

Ejemplo Py

python
import requests
res = requests.post(
    'https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/extend',
    headers={'Authorization': 'Bearer zpk_live_xxx', 'Content-Type': 'application/json'},
    json={ 'days': 15 }
)
data = res.json()

Anular un apartado

http
POST /layaways/{id}/cancel

Anula el apartado, libera el stock reservado y revierte los abonos que se hayan cubierto con una nota de crédito. Devuelve 200 con el apartado en estado cancelled. No requiere cuerpo.

bash
curl -X POST "https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/cancel" \
  -H "Authorization: Bearer zpk_live_xxx"

Ejemplo JS

javascript
const res = await fetch('https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/cancel', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}` }
});
const data = await res.json();

Ejemplo Py

python
import requests
res = requests.post(
    'https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/cancel',
    headers={'Authorization': 'Bearer zpk_live_xxx'}
)
data = res.json()

Entregar un apartado

http
POST /layaways/{id}/deliver

Marca el apartado como entregado. Solo pueden entregarse los apartados pagados en su totalidad. Devuelve 200 con el apartado en estado delivered. No requiere cuerpo.

bash
curl -X POST "https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/deliver" \
  -H "Authorization: Bearer zpk_live_xxx"

Ejemplo JS

javascript
const res = await fetch('https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/deliver', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}` }
});
const data = await res.json();

Ejemplo Py

python
import requests
res = requests.post(
    'https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/deliver',
    headers={'Authorization': 'Bearer zpk_live_xxx'}
)
data = res.json()

Problemas comunes

CódigoHTTPCausa
validation_error400Cuerpo inválido (por ejemplo, days fuera de rango en la prórroga).
unauthorized401API key ausente o inválida.
not_found404El apartado no existe.
conflict409Operación no válida para el estado actual (entregar sin pagar, extender uno no vencido, etc.).

Siguientes pasos

  • — registra ventas directas y emite documentos fiscales.
  • — registra pagos sobre ventas ya existentes.
  • — gestiona los clientes de tus apartados.
  • — descubre los id de métodos de pago y sucursales.

Documentación oficial de Zelta