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 (
clientIdoclientinline); - 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
POST /layawaysReserva el stock para un cliente y, opcionalmente, registra un abono inicial. Devuelve 201.
Campos del cuerpo
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
branchId | string (cuid2) | No | Sucursal del apartado. Por defecto, la sucursal de la API key. |
clientId | string (cuid2) | Condicional | Cliente existente. Tiene precedencia sobre client. Requerido si no envías client. |
client | object | Condicional | Cliente inline para buscar o crear (find-or-create). Requerido si no envías clientId. |
items | array | Sí | Líneas del apartado. Mínimo 1. |
items[].id | string (cuid2) | Uno requerido | ID de la variante de producto. Tiene precedencia sobre referenceId. |
items[].referenceId | string (≤120) | Uno requerido | Referencia externa de la variante. |
items[].quantity | number (>0) | Sí | Cantidad. |
items[].price | number (≥0) | No | Precio unitario. Por defecto, el del catálogo. |
items[].warehouseId | string (cuid2) | No | Bodega de la que se reserva el stock de la línea. |
items[].discount | number (≥0) | No | Descuento por línea. |
deposit | array | No | Abono inicial. Cada elemento tiene paymentMethod y amount. |
deposit[].paymentMethod | string (cuid2) | Sí | ID del método de pago. Lo obtienes de . |
deposit[].amount | number (>0) | Sí | 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).
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
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
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:
{
"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"
}| Campo | Tipo | Descripción |
|---|---|---|
paymentStatus | string | pending, partial, paid, expired o cancelled. |
fulfillmentStatus | string | reserved, prepared, delivered, expired, extended (prórroga) o cancelled. |
subtotal / taxes / discounts / total | string | Totales del apartado. |
amountPaid | string | Suma de los abonos registrados. |
balanceRemaining | string | Saldo pendiente por pagar. |
expiresAt | string | null | Fecha límite del apartado. null si no tiene vencimiento. |
items[] | array | Líneas: productVariantId (puede ser null), name, quantity, unitPrice, discount, tax. |
payments[] | array | Abonos: id, paymentMethodId, amount, reference (folio P-XXXXXX), date. |
Problemas comunes
| Código | HTTP | Causa |
|---|---|---|
validation_error | 400 | Falta el cliente, no hay ítems, o el depósito no cumple la política de depósito mínimo. |
unauthorized | 401 | API key ausente o inválida. |
not_found | 404 | El clientId, la branchId o una variante no existen. |
insufficient_stock | 409 | No hay existencias suficientes para reservar. |
Listar apartados
GET /layawaysAcepta los parámetros de : start, limit, updatedSince, from, to y metadata. Además:
| Parámetro | Tipo | Descripción |
|---|---|---|
status | string | Filtra por estado de entrega (reserved, prepared, delivered, expired, extended, cancelled). |
includePaid | boolean | Por defecto se excluyen los apartados ya pagados por completo (pasan a ser ventas). Envía true para incluirlos. |
curl "https://api-pos.zelta.dev/public/v1/layaways?status=reserved&metadata=true" \
-H "Authorization: Bearer zpk_live_xxx"Ejemplo JS
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
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
GET /layaways/{id}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
id | string (cuid2) | Sí | 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
POST /layaways/{id}/paymentsRegistra 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
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
paymentMethod | string (cuid2) | Sí | ID del método de pago. |
amount | number (>0) | Sí | 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).
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
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
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:
{
"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
POST /layaways/{id}/extendOtorga 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
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
days | number (entero, 1–365) | Sí | Días adicionales de reserva. |
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
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
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
POST /layaways/{id}/cancelAnula 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.
curl -X POST "https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/cancel" \
-H "Authorization: Bearer zpk_live_xxx"Ejemplo JS
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
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
POST /layaways/{id}/deliverMarca el apartado como entregado. Solo pueden entregarse los apartados pagados en su totalidad. Devuelve 200 con el apartado en estado delivered. No requiere cuerpo.
curl -X POST "https://api-pos.zelta.dev/public/v1/layaways/la2k5m8p1q4v7r0n3t6w9y2z/deliver" \
-H "Authorization: Bearer zpk_live_xxx"Ejemplo JS
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
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ódigo | HTTP | Causa |
|---|---|---|
validation_error | 400 | Cuerpo inválido (por ejemplo, days fuera de rango en la prórroga). |
unauthorized | 401 | API key ausente o inválida. |
not_found | 404 | El apartado no existe. |
conflict | 409 | Operació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
idde métodos de pago y sucursales.