Cotizaciones
Una cotización (estimate) es una propuesta de venta con un vencimiento. No afecta el inventario ni genera cuentas por cobrar hasta que se convierte en una orden de venta.
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 las facturas y los ajustes, en las cotizaciones los ítems se pueden direccionar por id interno (cuid2) o por referenceId (identificador externo, máx. 120 caracteres). Debes enviar uno de los dos en cada línea.

POST /estimates.Crear una cotización
POST /estimates| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
clientId | string (cuid2) | No | Cliente al que se dirige la cotización. |
branchId | string (cuid2) | No | Sucursal. Por defecto, la sucursal de la API key. |
dueDate | string (ISO datetime) | Sí | Fecha de vencimiento. Debe ser futura. |
notes | string (≤2000) | No | Notas de la cotización. |
items | array | Sí | Mínimo un ítem. |
items[].id | string (cuid2) | Condicional | Identificador interno de la variante. Requerido si no se envía referenceId. |
items[].referenceId | string (≤120) | Condicional | Identificador externo. Requerido si no se envía id. |
items[].quantity | number (>0) | Sí | Cantidad. |
items[].price | number | No | Precio unitario. Por defecto, el del catálogo. |
items[].discount | number (≥0) | No | Descuento por línea. |
items[].tax | number (0–100) | No | Tasa de impuesto por línea, en porcentaje. |
TIP
Las cotizaciones sí aceptan impuesto por línea mediante el campo tax (tasa porcentual de 0 a 100). Si lo omites, se aplica la configuración de impuesto del catálogo del producto.
curl -X POST "https://api-pos.zelta.dev/public/v1/estimates" \
-H "Authorization: Bearer zpk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"clientId": "cl8h3k6m9p2q5v8r1n4t7w0y",
"dueDate": "2026-02-28T23:59:59.000Z",
"notes": "Cotización válida por 30 días",
"items": [
{ "referenceId": "SKU-1001", "quantity": 5, "price": 19.90, "discount": 5, "tax": 7 },
{ "id": "pv1a3s5d7f9g2h4j6k8l0z2x", "quantity": 2 }
]
}'Ejemplo JS
const res = await fetch('https://api-pos.zelta.dev/public/v1/estimates', {
method: 'POST',
headers: { 'Authorization': `Bearer ${process.env.ZELTA_POS_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({
clientId: 'cl8h3k6m9p2q5v8r1n4t7w0y',
dueDate: '2026-02-28T23:59:59.000Z',
notes: 'Cotización válida por 30 días',
items: [
{ referenceId: 'SKU-1001', quantity: 5, price: 19.90, discount: 5, tax: 7 },
{ id: 'pv1a3s5d7f9g2h4j6k8l0z2x', quantity: 2 }
]
})
});
const data = await res.json();Ejemplo Py
import requests
res = requests.post(
'https://api-pos.zelta.dev/public/v1/estimates',
headers={'Authorization': 'Bearer zpk_live_xxx', 'Content-Type': 'application/json'},
json={
'clientId': 'cl8h3k6m9p2q5v8r1n4t7w0y',
'dueDate': '2026-02-28T23:59:59.000Z',
'notes': 'Cotización válida por 30 días',
'items': [
{ 'referenceId': 'SKU-1001', 'quantity': 5, 'price': 19.90, 'discount': 5, 'tax': 7 },
{ 'id': 'pv1a3s5d7f9g2h4j6k8l0z2x', 'quantity': 2 }
]
}
)
data = res.json()Respuesta 201 Created:
{
"id": "es2k5m8p1q4v7r0n3t6w9y2z",
"number": "EST-000058",
"clientId": "cl8h3k6m9p2q5v8r1n4t7w0y",
"branchId": "br3b8n5k2j7h9g4f1d6s0a8q",
"status": "draft",
"subtotal": 137.30,
"tax": 9.61,
"total": 146.91,
"notes": "Cotización válida por 30 días",
"dueDate": "2026-02-28T23:59:59.000Z",
"convertedOrderId": null,
"items": [
{
"productVariantId": "pv9z2x4c6v8b1n3m5k7j9h2g",
"name": "Camiseta básica - Talla M",
"sku": "SKU-1001",
"quantity": 5,
"unitPrice": 19.90,
"discount": 5,
"taxRate": 7,
"total": 99.86
},
{
"productVariantId": "pv1a3s5d7f9g2h4j6k8l0z2x",
"name": "Gorra deportiva",
"sku": null,
"quantity": 2,
"unitPrice": 22.00,
"discount": 0,
"taxRate": 7,
"total": 47.08
}
],
"createdAt": "2026-01-29T16:40:00.000Z",
"updatedAt": "2026-01-29T16:40:00.000Z"
}El campo status puede ser draft, accepted, expired o converted. Los campos clientId, notes, convertedOrderId y items[].sku pueden ser null.
Problemas comunes
| Código | HTTP | Causa |
|---|---|---|
validation_error | 400 | Falta dueDate, no es futura, o un ítem no trae id ni referenceId. |
unauthorized | 401 | API key ausente o inválida. |
not_found | 404 | El clientId, la branchId o una variante no existen. |
Listar cotizaciones
GET /estimatesAcepta los parámetros de : start, limit, updatedSince, from, to y metadata.
curl "https://api-pos.zelta.dev/public/v1/estimates?limit=30&metadata=true" \
-H "Authorization: Bearer zpk_live_xxx"Ejemplo JS
const res = await fetch('https://api-pos.zelta.dev/public/v1/estimates?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/estimates',
headers={'Authorization': 'Bearer zpk_live_xxx'},
params={'limit': 30, 'metadata': 'true'}
)
data = res.json()Respuesta 200 OK:
{
"data": [
{
"id": "es2k5m8p1q4v7r0n3t6w9y2z",
"number": "EST-000058",
"clientId": "cl8h3k6m9p2q5v8r1n4t7w0y",
"branchId": "br3b8n5k2j7h9g4f1d6s0a8q",
"status": "draft",
"subtotal": 137.30,
"tax": 9.61,
"total": 146.91,
"dueDate": "2026-02-28T23:59:59.000Z",
"convertedOrderId": null,
"createdAt": "2026-01-29T16:40:00.000Z",
"updatedAt": "2026-01-29T16:40:00.000Z"
}
],
"metadata": { "total": 58 }
}Obtener una cotización
GET /estimates/{id}| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
id | string (cuid2) | Sí | Identificador de la cotización. |
Devuelve el objeto de la cotización directamente (sin envoltorio), con la misma estructura que la respuesta de creación. Si no existe, responde 404 not_found.
Siguientes pasos
- — emite la venta cuando la cotización se acepta.
- — gestiona los clientes a los que cotizas.
- — revisa precios y existencias antes de cotizar.
- — paginación, sincronización y códigos de error.