Skip to content

Productos y existencias ​

Con estos endpoints tu tienda en línea lee el catálogo del comercio, lo mantiene al día, consulta cuántas unidades hay en cada bodega y sabe desde qué sucursal vende.

EndpointPermiso
GET /productscatalog:read
GET /products/{id}catalog:read
GET /categoriescatalog:read
GET /brandscatalog:read
GET /stockstock:read
GET /branchesorders:write o stock:read
GET /warehousesorders:write o stock:read
  • Un producto es la ficha: nombre, categoría, marca, impuesto e imágenes.
  • Cada producto tiene una o más variantes, que son lo que se vende: cada talla o color, con su SKU, código de barras y precio. Un producto sin opciones tiene una sola variante.
  • Para vender, tu tienda envía el SKU de la variante. Una variante sin SKU no se puede vender por la API.

Listar productos ​

http
GET /products
ParámetroTipoDescripción
pageenteroPágina, desde 1. Por defecto 1
limitenteroResultados por página, de 1 a 100. Por defecto 50
updatedSincefecha ISO 8601Solo los productos que cambiaron en ese momento o después. Debe llevar zona horaria: 2026-10-06T00:00:00Z o 2026-10-06T00:00:00-05:00
cursortextoEl nextCursor de la respuesta anterior. Pide la página siguiente sin saltarse productos; con cursor, page se ignora
categoryIdtextoSolo los productos de esa categoría. Es el id de category de cualquiera de sus productos
searchtexto, hasta 120 caracteresBusca por nombre del producto, SKU o código de barras

Los productos salen ordenados por updatedAt, del más antiguo al más reciente.

bash
curl "https://api.zenci.app/public/v1/products?limit=2" \
  -H "Authorization: Bearer $ZENCI_API_KEY"
json
{
  "data": [
    {
      "id": "p4k8m2x9q7w1e5r3t6y0u2i8",
      "name": "Sandalia Crucero",
      "type": "product",
      "category": { "id": "c2v6b8n4m1q9w3e7r5t0y2u4", "name": "Calzado" },
      "brand": { "id": "d9f3g7h1j5k2l8z4x6c0v2b6", "name": "Marea" },
      "taxRate": "7.00",
      "images": [
        {
          "id": "i1o5p9a3s7d2f6g0h4j8k2l6",
          "url": "https://imagenes.mareaboutique.test/sandalia-crucero-cruda.jpg"
        }
      ],
      "variants": [
        {
          "id": "v3q7w1e5r9t2y6u0i4o8p2a6",
          "sku": "MB-029-CRU-38",
          "barcode": "7451234560381",
          "name": "Sandalia Crucero - Cruda / 38",
          "price": "36.00",
          "priceSource": "catalog",
          "attributes": [
            { "option": "Color", "value": "Cruda" },
            { "option": "Talla", "value": "38" }
          ]
        },
        {
          "id": "v8s2d6f0g4h7j1k5l9z3x7c1",
          "sku": "MB-029-CRU-39",
          "barcode": "7451234560398",
          "name": "Sandalia Crucero - Cruda / 39",
          "price": "36.00",
          "priceSource": "catalog",
          "attributes": [
            { "option": "Color", "value": "Cruda" },
            { "option": "Talla", "value": "39" }
          ]
        }
      ],
      "priceList": null,
      "deleted": false,
      "createdAt": "2026-08-14T14:02:11.000Z",
      "updatedAt": "2026-10-02T21:15:40.000Z"
    },
    {
      "id": "p6n0b4v8c2x6z1l5k9j3h7g1",
      "name": "Arreglo de ruedo",
      "type": "service",
      "category": null,
      "brand": null,
      "taxRate": null,
      "images": [],
      "variants": [
        {
          "id": "v5t9y3u7i1o4p8a2s6d0f4g8",
          "sku": "MB-SRV-RUEDO",
          "barcode": null,
          "name": "Arreglo de ruedo",
          "price": "5.00",
          "priceSource": "catalog",
          "attributes": []
        }
      ],
      "priceList": null,
      "deleted": false,
      "createdAt": "2026-09-01T16:40:00.000Z",
      "updatedAt": "2026-10-03T13:22:05.000Z"
    }
  ],
  "pagination": { "page": 1, "limit": 2, "total": 128, "totalPages": 64 },
  "nextCursor": "WyIyMDI2LTEwLTAzVDEzOjIyOjA1LjAwMFoiLCJwNm4wYjR2OGMyeDZ6MWw1azlqM2g3ZzEiXQ"
}

nextCursor es la llave para pedir la página siguiente. Vale null cuando la página vino incompleta, es decir, cuando ya no hay más productos. Trátalo como un texto opaco: no lo armes a mano.

Campos del producto ​

CampoDescripción
idIdentificador del producto
nameNombre del producto
typeproduct (mercancía con existencias) o service (servicio, no mueve inventario)
category, brand{ id, name }, o null si no tiene
taxRatePorcentaje de impuesto, por ejemplo "7.00", o null si no lleva impuesto
imagesImágenes del producto, con id y url
variantsVariantes que se venden (ver abajo)
priceList{ id, name } de la lista de precios de la clave, o null si la clave usa los precios del catálogo
deletedtrue si el producto se eliminó. Solo aparece en true cuando consultas con updatedSince
createdAt, updatedAtFechas de creación y del último cambio

Campos de la variante ​

CampoDescripción
idIdentificador de la variante
skuSKU con el que se vende por la API, o null
barcodeCódigo de barras, o null
nameNombre del producto seguido de los valores de sus opciones: Sandalia Crucero - Cruda / 38
pricePrecio de venta por unidad, sin impuesto
priceSourcecatalog si es el precio del catálogo, price_list si sale de la lista de precios de la clave
attributesOpciones de la variante, como { "option": "Talla", "value": "38" }

Precios y lista de precios ​

price es el mismo precio que POST /orders cobra por esa variante con la misma clave. No incluye el impuesto: para mostrar el precio final, súmale taxRate. Por ejemplo, 36.00 con taxRate 7.00 se muestra como 38.52. El cálculo exacto está en .

Si la clave tiene una lista de precios, cada variante trae el precio de esa lista. Una variante que la lista no cubre sale con el precio del catálogo y priceSource: "catalog"; por eso priceSource va en cada variante.

Si la lista de la clave se elimina o se desactiva, el catálogo sigue respondiendo con los precios del catálogo y priceList pasa a null. No da error: revisa priceList si tu tienda depende de esa lista.

Leer un producto ​

http
GET /products/{id}

Devuelve un producto con la misma forma que cada elemento de la lista. Un producto eliminado, o de otro comercio, responde 404 not_found con el mensaje Product not found.

bash
curl https://api.zenci.app/public/v1/products/p4k8m2x9q7w1e5r3t6y0u2i8 \
  -H "Authorization: Bearer $ZENCI_API_KEY"

Para mantener una copia del catálogo en tu tienda, sin descargarlo completo cada vez:

  1. Primera carga. Llama a GET /products?limit=100 y sigue pidiendo con cursor=<nextCursor> hasta que nextCursor venga en null. Así recibes todos los productos vigentes.
  2. Guarda una marca. Anota el updatedAt del último producto que procesaste.
  3. Siguientes cargas. Llama a GET /products?updatedSince=<marca>&limit=100 y sigue los nextCursor igual que en la primera carga. Recibes solo lo que cambió desde la marca, incluidos los productos eliminados con deleted: true: quítalos de tu tienda.
  4. Actualiza la marca con el updatedAt del último producto de esa pasada.

Ten en cuenta:

  • Recorre siempre con cursor, no con page. Con page, un producto que cambia mientras recorres salta al final de la lista y corre a los demás una posición, y el siguiente quedaría sin leer. El cursor no tiene ese problema.
  • updatedSince incluye el momento exacto de la marca, así que el último producto de la pasada anterior vuelve a llegar. Actualiza por id y no lo dupliques.
  • Cambian el updatedAt de un producto: editar su ficha o sus variantes, eliminarlo, y cambiar su precio en una lista de precios.
  • Las existencias no son parte del producto y no cambian su updatedAt. Consúltalas en GET /stock.
  • Sin updatedSince, los productos eliminados no aparecen.
  • Los product.updated y product.deleted te dicen cuándo vale la pena sincronizar, sin consultar a cada rato.
bash
curl "https://api.zenci.app/public/v1/products?updatedSince=2026-10-02T21:15:40.000Z&limit=100" \
  -H "Authorization: Bearer $ZENCI_API_KEY"

curl "https://api.zenci.app/public/v1/products?updatedSince=2026-10-02T21:15:40.000Z&limit=100&cursor=WyIyMDI2LTEw…" \
  -H "Authorization: Bearer $ZENCI_API_KEY"

Categorías y marcas ​

http
GET /categories
GET /brands

Sirven para armar los filtros de tu tienda. No se paginan: traen todas las del comercio. Con el id de una categoría puedes filtrar GET /products?categoryId=….

json
{
  "data": [
    { "id": "c2v6b8n4m1q9w3e7r5t0y2u4", "name": "Calzado", "slug": "calzado", "parentId": null },
    { "id": "c8x2z6l0k4j7h1g5f9d3s7a1", "name": "Sandalias", "slug": "sandalias", "parentId": "c2v6b8n4m1q9w3e7r5t0y2u4" }
  ]
}

parentId indica la categoría de la que depende, o null si es de primer nivel. Las marcas traen solo id y name.

Consultar existencias ​

http
GET /stock
ParámetroTipoDescripción
pageenteroPágina, desde 1. Por defecto 1
limitenteroVariantes por página, de 1 a 100. Por defecto 50
skutextoSolo la variante con ese SKU exacto
warehouseIdtextoSolo esa bodega

Devuelve una fila por variante y bodega. Solo aparecen las variantes que tienen existencias registradas en alguna bodega; los servicios no tienen.

bash
curl "https://api.zenci.app/public/v1/stock?sku=MB-029-CRU-38" \
  -H "Authorization: Bearer $ZENCI_API_KEY"
json
{
  "data": [
    {
      "variantId": "v3q7w1e5r9t2y6u0i4o8p2a6",
      "sku": "MB-029-CRU-38",
      "warehouseId": "w2e6r0t4y8u1i5o9p3a7s1d5",
      "warehouseName": "Bodega Obarrio",
      "availableQty": "4",
      "stockedQty": "5"
    },
    {
      "variantId": "v3q7w1e5r9t2y6u0i4o8p2a6",
      "sku": "MB-029-CRU-38",
      "warehouseId": "w9f3g7h1j4k8l2z6x0c4v8b2",
      "warehouseName": "Bodega Costa del Este",
      "availableQty": "2",
      "stockedQty": "2"
    }
  ],
  "pagination": { "page": 1, "limit": 50, "total": 1, "totalPages": 1 }
}
CampoDescripción
variantId, skuLa variante
warehouseId, warehouseNameLa bodega
stockedQtyUnidades físicas en la bodega
availableQtyUnidades que se pueden vender: las físicas menos las reservadas (por ejemplo, por apartados). Es la que debes mostrar en tu tienda. Puede ser negativa si el comercio vende sin existencias

La paginación cuenta variantes, no filas ​

limit, total y totalPages cuentan variantes. Cada variante trae una fila por cada bodega donde tiene existencias, así que una página puede traer más filas que limit. En el ejemplo, total es 1 y data trae dos filas.

Presentaciones enlazadas ​

Una presentación enlazada es una variante que no tiene inventario propio y se descuenta de otra: por ejemplo, una caja de 12 que sale de las unidades sueltas. Su fila muestra existencias calculadas: las unidades de la base divididas entre las que trae la presentación, redondeadas hacia abajo.

Si hay 30 unidades sueltas disponibles, la caja de 12 muestra availableQty: "2". Al vender una caja por la API se descuentan 12 unidades de la base, y las dos filas cambian.

Qué bodega mirar ​

Una venta por la API descuenta de la bodega de la sucursal donde se registra (la de la clave, o la que envíes en branchId). Busca esa sucursal en GET /branches, toma su warehouseId y filtra GET /stock por él para mostrar lo que de verdad puedes vender.

Sucursales y bodegas ​

http
GET /branches
GET /warehouses

Las pide quien vende (orders:write) o quien consulta existencias (stock:read). No se paginan.

json
{
  "data": [
    {
      "id": "b7n2q8w4e1r9t3y6u0i5o2p7",
      "name": "Obarrio",
      "code": "001",
      "address": "Calle 50, edificio Marea",
      "phone": "+507 6000-0000",
      "warehouseId": "w2e6r0t4y8u1i5o9p3a7s1d5"
    }
  ]
}
CampoDescripción
idEl branchId que acepta POST /orders
codeCódigo de la sucursal
warehouseIdLa bodega de la que descuenta una venta en esa sucursal

GET /warehouses trae id, name e isBranch, que vale true si la bodega es la de una sucursal.

Siguientes pasos ​

  • — Vende por SKU.
  • — Registra a quién le vendes y a qué compañía se factura.
  • — Entérate de los cambios de producto, existencias y precio sin consultar a cada rato.

Documentación oficial de Zelta