Productos, categorías e inventario. Elige un SDK o integra con cURL; todos los ejemplos usan la misma autenticación HMAC.
Catálogo Business API
Catálogo — Categorías
Catálogo — Productos
Devuelve todas las categorías del comercio, con stock agregado por categoría e URLs de imagen si existen.
{
"categories": [
{
"id": "uuid-categoria",
"merchant_id": "uuid-comercio",
"parent_id": null,
"name": "Ropa",
"slug": "ropa",
"description": null,
"sort_order": 0,
"is_active": true,
"cloudflare_image_id": null,
"image_url": null,
"image_urls": null,
"direct_stock_quantity": 12,
"total_stock_quantity": 48,
"direct_warehouse_stock_quantity": 20,
"total_warehouse_stock_quantity": 80,
"created_at": "2026-01-15T10:00:00.000Z",
"updated_at": "2026-01-15T10:00:00.000Z"
}
]
}const { categories } = await client.listCatalogCategories();Crea o actualiza categorías en lote. Si el `id` ya existe, se actualiza; si no, se crea. Los `slug` deben ser únicos por comercio.
Cuerpo JSON (camelCase)
categoriesrequeridoarrayLista de 1–50 categorías.
categories[].idrequeridouuidUUID de la categoría (generado por tu sistema).
categories[].namerequeridostringNombre visible.
categories[].parentIdopcionaluuid | nullCategoría padre para jerarquía.
categories[].slugopcionalstringSlug URL; si se omite, se deriva del nombre.
categories[].descriptionopcionalstring | nullDescripción opcional.
categories[].sortOrderopcionalnumberOrden en listados (default 0).
categories[].isActiveopcionalbooleanVisible en tienda si es true (default true).
{
"categories": [
{
"id": "00000000-0000-4000-8000-000000000101",
"parentId": null,
"name": "Ropa",
"slug": "ropa",
"description": "Prendas y accesorios",
"sortOrder": 0,
"isActive": true
}
]
}{
"categories": [
{
"id": "00000000-0000-4000-8000-000000000101",
"merchant_id": "uuid-comercio",
"name": "Ropa",
"slug": "ropa",
"sort_order": 0,
"is_active": true
}
]
}await client.upsertCatalogCategories([
{
id: '00000000-0000-4000-8000-000000000101',
name: 'Ropa',
slug: 'ropa',
sortOrder: 0,
isActive: true,
},
]);Elimina categorías por UUID. Falla si alguna categoría tiene productos asignados o subcategorías activas.
Cuerpo JSON (camelCase)
idsrequeridouuid[]IDs de categorías a eliminar (1–50).
{ "ids": ["00000000-0000-4000-8000-000000000101"] }{ "deleted_count": 1 }await client.deleteCatalogCategories([
'00000000-0000-4000-8000-000000000101',
]);Paso 1 del flujo de imagen: obtiene `upload_url` de Cloudflare y reserva un `cloudflare_image_id` por categoría.
Cuerpo JSON (camelCase)
uploadsrequeridoarray1–50 solicitudes de subida.
uploads[].clientReferencerequeridostringReferencia local para correlacionar la respuesta.
uploads[].categoryIdrequeridouuidCategoría a la que se asociará la imagen.
{
"uploads": [
{
"clientReference": "portada-ropa",
"categoryId": "00000000-0000-4000-8000-000000000101"
}
]
}{
"uploads": [
{
"client_reference": "portada-ropa",
"category_id": "00000000-0000-4000-8000-000000000101",
"cloudflare_image_id": "cf-image-id",
"upload_url": "https://upload.imagedelivery.net/...",
"image_url": "https://imagedelivery.net/.../card",
"image_urls": {
"thumb": "https://...",
"card": "https://...",
"product": "https://...",
"zoom": "https://..."
}
}
]
}const { uploads } = await client.requestCatalogCategoryImageDirectUploads([
{ clientReference: 'portada', categoryId: categoryId },
]);
// POST file → uploads[0].upload_urlPaso 3 del flujo de imagen: asocia el `cloudflareImageId` ya subido a la categoría.
Cuerpo JSON (camelCase)
imagesrequeridoarray1–50 registros.
images[].categoryIdrequeridouuidCategoría destino.
images[].cloudflareImageIdrequeridostringID devuelto en direct-uploads.
{
"images": [
{
"categoryId": "00000000-0000-4000-8000-000000000101",
"cloudflareImageId": "cf-image-id"
}
]
}{
"categories": [
{
"id": "00000000-0000-4000-8000-000000000101",
"image_url": "https://imagedelivery.net/.../card",
"cloudflare_image_id": "cf-image-id"
}
]
}await client.registerCatalogCategoryImages([
{ categoryId, cloudflareImageId: uploads[0].cloudflare_image_id },
]);Elimina la imagen asociada a una o más categorías. El body usa los IDs de categoría (no el ID de imagen).
Cuerpo JSON (camelCase)
idsrequeridouuid[]IDs de categorías cuya imagen se quitará.
{ "ids": ["00000000-0000-4000-8000-000000000101"] }{ "deleted_count": 1 }await client.deleteCatalogCategoryImages([categoryId]);Monedas en las que el comercio puede vender (según métodos de pago configurados). Necesario al crear productos (`currencyId`).
{
"currencies": [
{
"id": "00000000-0000-4000-8000-000000000201",
"singular_name": "peso",
"plural_name": "pesos",
"iso_code": "COP",
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z"
}
]
}const { currencies } = await client.listCatalogCurrencies();Crea un producto con al menos una variante en la misma solicitud. El `id` del producto y de cada variante los define tu integración (UUID v4).
Cuerpo JSON (camelCase)
idrequeridouuidUUID del producto.
namerequeridostringNombre del producto.
currencyIdrequeridouuidMoneda base (ver listCatalogCurrencies).
categoryIdopcionaluuid | nullCategoría opcional.
slugopcionalstringSlug URL; si se omite, se deriva del nombre.
descriptionopcionalstringDescripción HTML o texto.
statusopcionalstring`draft` | `active` | `archived` (default `draft`).
metadataopcionalobjectJSON arbitrario del comercio.
variantsrequeridoarrayAl menos una variante (SKU + precio). Obligatorio.
variants[].idrequeridouuidUUID de la variante.
variants[].skurequeridostringSKU único por comercio.
variants[].priceInCentsrequeridonumber | stringPrecio en centavos (entero).
variants[].compareAtPriceInCentsopcionalnumber | string | nullPrecio tachado opcional.
variants[].currencyIdopcionaluuidDefault: currencyId del producto.
variants[].stockQuantityopcionalnumberStock inicial (default 0).
variants[].trackInventoryopcionalbooleanSi false, la variante siempre está «en stock».
variants[].weightGramsopcionalnumber | nullPeso para envíos.
variants[].lengthMmopcionalnumber | nullDimensiones en mm.
variants[].widthMmopcionalnumber | nullDimensiones en mm.
variants[].heightMmopcionalnumber | nullDimensiones en mm.
variants[].optionsopcionalobjectAtributos (talla, color, etc.).
variants[].isActiveopcionalbooleanVariante vendible (default true).
{
"id": "00000000-0000-4000-8000-000000000301",
"name": "Camiseta básica",
"currencyId": "00000000-0000-4000-8000-000000000201",
"categoryId": "00000000-0000-4000-8000-000000000101",
"status": "draft",
"variants": [
{
"id": "00000000-0000-4000-8000-000000000302",
"sku": "CAM-001-S",
"priceInCents": 29900,
"stockQuantity": 10,
"trackInventory": true,
"options": { "talla": "S" }
}
]
}{
"id": "00000000-0000-4000-8000-000000000301",
"merchant_id": "uuid-comercio",
"name": "Camiseta básica",
"slug": "camiseta-basica",
"status": "draft",
"currency_id": "00000000-0000-4000-8000-000000000201",
"variants": [{ "id": "...", "sku": "CAM-001-S", "price_in_cents": "29900", "stock_quantity": 10 }]
}const product = await client.createCatalogProduct({
id: crypto.randomUUID(),
name: 'Camiseta básica',
currencyId: process.env.HANIX_CURRENCY_ID!,
variants: [{
id: crypto.randomUUID(),
sku: 'CAM-001-S',
priceInCents: 29900,
stockQuantity: 10,
}],
});Lista paginada de productos con filtros por estado, categoría, texto y disponibilidad.
Query string
statusopcionalstring`draft` | `active` | `archived`.
categoryIdopcionaluuidFiltrar por categoría.
qopcionalstringBúsqueda por nombre, slug o SKU.
pageopcionalnumberPágina (default 1).
limitopcionalnumberTamaño de página (default 20).
inStockopcionalbooleanSi `true`, solo productos con stock vendible.
{
"items": [
{
"id": "uuid-producto",
"name": "Camiseta básica",
"status": "active",
"stock_quantity": 10,
"in_stock": true,
"images": [{ "url": "https://...", "is_primary": true }]
}
],
"total": 42,
"page": 1,
"limit": 20
}const { items, total, page, limit } = await client.listCatalogProducts({
status: 'active',
q: 'camiseta',
page: 1,
limit: 20,
inStock: true,
});Detalle completo: producto, todas las variantes e imágenes asociadas.
Parámetros de ruta
product_idrequeridouuidUUID del producto.
{
"id": "uuid-producto",
"name": "Camiseta básica",
"description": "...",
"status": "active",
"variants": [{ "id": "...", "sku": "CAM-001-S", "price_in_cents": "29900" }],
"images": [{ "id": "...", "url": "https://...", "is_primary": true }]
}const product = await client.getCatalogProduct('uuid-producto');Actualización parcial del producto (solo envía los campos que cambian).
Parámetros de ruta
product_idrequeridouuidUUID del producto.
Cuerpo JSON (camelCase)
nameopcionalstringNuevo nombre.
slugopcionalstringNuevo slug (único por comercio).
descriptionopcionalstring | nullDescripción.
categoryIdopcionaluuid | nullReasignar categoría o `null`.
statusopcionalstring`draft` | `active` | `archived`.
metadataopcionalobject | nullMetadatos JSON.
{
"name": "Camiseta premium",
"status": "active",
"categoryId": "00000000-0000-4000-8000-000000000101"
}await client.updateCatalogProduct(productId, {
name: 'Camiseta premium',
status: 'active',
});Elimina productos y sus variantes/imágenes asociadas en lote.
Cuerpo JSON (camelCase)
idsrequeridouuid[]IDs de productos (1–50).
{ "ids": ["00000000-0000-4000-8000-000000000301"] }{ "deleted_count": 1 }await client.deleteCatalogProducts([productId]);Actualización parcial de una variante (precio, stock, SKU, opciones, envío).
Parámetros de ruta
product_idrequeridouuidUUID del producto.
variant_idrequeridouuidUUID de la variante.
Cuerpo JSON (camelCase)
skuopcionalstringSKU único.
nameopcionalstringNombre de la variante.
priceInCentsopcionalnumber | stringPrecio en centavos.
compareAtPriceInCentsopcionalnumber | string | nullPrecio comparación.
currencyIdopcionaluuidMoneda de la variante.
stockQuantityopcionalnumberStock disponible para venta.
trackInventoryopcionalbooleanControl de inventario.
weightGramsopcionalnumber | nullPeso en gramos.
lengthMmopcionalnumber | nullLargo en mm.
widthMmopcionalnumber | nullAncho en mm.
heightMmopcionalnumber | nullAlto en mm.
optionsopcionalobject | nullAtributos de la variante.
isActiveopcionalbooleanVariante activa/vendible.
{
"priceInCents": 34900,
"stockQuantity": 25,
"isActive": true
}await client.updateCatalogProductVariant(productId, variantId, {
priceInCents: 34900,
stockQuantity: 25,
});Establece el inventario físico en bodega. No afecta directamente el stock vendible en tienda (`stockQuantity`); es informativo/operativo.
Parámetros de ruta
product_idrequeridouuidUUID del producto.
variant_idrequeridouuidUUID de la variante.
Cuerpo JSON (camelCase)
warehouseStockQuantityrequeridonumberUnidades en almacén (entero ≥ 0).
{ "warehouseStockQuantity": 100 }await client.setCatalogVariantWarehouseStock(productId, variantId, 100);Crea o actualiza variantes en lote. Cada ítem debe incluir `productId` y `id` de variante.
Cuerpo JSON (camelCase)
variantsrequeridoarray1–50 variantes.
variants[].idrequeridouuidUUID de la variante.
variants[].productIdrequeridouuidProducto padre.
variants[].skurequeridostringSKU único.
variants[].priceInCentsrequeridonumber | stringPrecio en centavos.
variants[].nameopcionalstringNombre opcional.
variants[].stockQuantityopcionalnumberStock disponible.
variants[].trackInventoryopcionalbooleanControl de inventario.
variants[].optionsopcionalobjectAtributos.
variants[].isActiveopcionalbooleanActiva/vendible.
{
"variants": [
{
"id": "00000000-0000-4000-8000-000000000302",
"productId": "00000000-0000-4000-8000-000000000301",
"sku": "CAM-001-M",
"priceInCents": 29900,
"stockQuantity": 5,
"options": { "talla": "M" }
}
]
}{ "variants": [{ "id": "...", "sku": "CAM-001-M", "price_in_cents": "29900" }] }await client.upsertCatalogVariants([{
id: variantId,
productId,
sku: 'CAM-001-M',
priceInCents: 29900,
}]);Elimina variantes por UUID. Falla con 409 si el borrado dejaría un producto sin variantes.
Cuerpo JSON (camelCase)
idsrequeridouuid[]IDs de variantes (1–50).
{ "ids": ["00000000-0000-4000-8000-000000000302"] }{ "deleted_count": 1 }await client.deleteCatalogVariants([variantId]);Paso 1: solicita `upload_url` y `cloudflare_image_id` por producto.
Cuerpo JSON (camelCase)
uploadsrequeridoarray1–50 solicitudes.
uploads[].clientReferencerequeridostringCorrelación local.
uploads[].productIdrequeridouuidProducto destino.
{
"uploads": [
{ "clientReference": "hero", "productId": "00000000-0000-4000-8000-000000000301" }
]
}{
"uploads": [
{
"client_reference": "hero",
"product_id": "00000000-0000-4000-8000-000000000301",
"cloudflare_image_id": "cf-image-id",
"upload_url": "https://upload.imagedelivery.net/...",
"url": "https://imagedelivery.net/.../product",
"urls": { "thumb": "...", "card": "...", "product": "...", "zoom": "..." }
}
]
}const { uploads } = await client.requestCatalogImageDirectUploads([
{ clientReference: 'hero', productId },
]);Paso 3: registra imágenes ya subidas a Cloudflare en el catálogo del producto.
Cuerpo JSON (camelCase)
imagesrequeridoarray1–50 imágenes.
images[].idrequeridouuidUUID del registro de imagen.
images[].productIdrequeridouuidProducto.
images[].variantIdopcionaluuid | nullVariante específica (opcional).
images[].cloudflareImageIdrequeridostringID de Cloudflare.
images[].altTextopcionalstringTexto alternativo.
images[].sortOrderopcionalnumberOrden en galería (default 0).
images[].isPrimaryopcionalbooleanSi true, esta imagen sale en las tarjetas de listados (inicio, categorías, búsqueda).
{
"images": [
{
"id": "00000000-0000-4000-8000-000000000401",
"productId": "00000000-0000-4000-8000-000000000301",
"cloudflareImageId": "cf-image-id",
"isPrimary": true,
"sortOrder": 0
}
]
}{ "images": [{ "id": "...", "url": "https://...", "is_primary": true }] }await client.registerCatalogImages([{
id: crypto.randomUUID(),
productId,
cloudflareImageId: uploads[0].cloudflare_image_id,
isPrimary: true,
}]);Actualiza metadatos de imágenes ya registradas. Usa `isPrimary: true` para definir la imagen de tarjeta en listados.
Cuerpo JSON (camelCase)
imagesrequeridoarray1–50 actualizaciones.
images[].idrequeridouuidUUID del registro de imagen.
images[].isPrimaryopcionalbooleanSi true, esta imagen sale en las tarjetas de listados (inicio, categorías, búsqueda).
images[].variantIdopcionaluuid | nullAsignar a una variante, o null para el producto completo.
images[].altTextopcionalstring | nullTexto alternativo.
images[].sortOrderopcionalnumberOrden en la galería.
{
"images": [
{
"id": "00000000-0000-4000-8000-000000000401",
"isPrimary": true
}
]
}{ "images": [{ "id": "00000000-0000-4000-8000-000000000401", "is_primary": true, "url": "https://..." }] }await client.updateCatalogImages([{ id: imageId, isPrimary: true }]);Elimina registros de imagen del catálogo por UUID de imagen (no confundir con category image delete).
Cuerpo JSON (camelCase)
idsrequeridouuid[]IDs de imagen de producto (1–50).
{ "ids": ["00000000-0000-4000-8000-000000000401"] }{ "deleted_count": 1 }await client.deleteCatalogImages([imageId]);Aplica deltas de stock a variantes. Útil para recepciones, mermas o correcciones. `quantityDelta` positivo suma; negativo resta.
Cuerpo JSON (camelCase)
adjustmentsrequeridoarray1–50 ajustes.
adjustments[].variantIdrequeridouuidVariante a ajustar.
adjustments[].quantityDeltarequeridonumberEntero distinto de cero (+/- unidades).
adjustments[].reasonopcionalstring`sale` | `restock` | `adjustment` | `return` | `reservation` (default `adjustment`).
adjustments[].referenceTypeopcionalstringTipo de documento origen (opcional).
adjustments[].referenceIdopcionaluuidID del documento origen (opcional).
{
"adjustments": [
{
"variantId": "00000000-0000-4000-8000-000000000302",
"quantityDelta": 10,
"reason": "restock"
}
]
}{ "adjusted_count": 1 }await client.adjustCatalogInventory([{
variantId,
quantityDelta: 10,
reason: 'restock',
}]);