Products, categories, and inventory. Pick an SDK or integrate with cURL; all examples use the same HMAC authentication.
Business API catalog
Catalog — Categories
Returns all merchant categories, with aggregated stock per category and image URLs when present.
{
"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();Creates or updates categories in bulk. If `id` exists, it is updated; otherwise created. `slug` values must be unique per merchant.
JSON body (camelCase)
categoriesrequiredarrayList of 1–50 categories.
categories[].idrequireduuidCategory UUID (generated by your system).
categories[].namerequiredstringDisplay name.
categories[].parentIdoptionaluuid | nullParent category for hierarchy.
categories[].slugoptionalstringURL slug; derived from name if omitted.
categories[].descriptionoptionalstring | nullOptional description.
categories[].sortOrderoptionalnumberSort order in listings (default 0).
categories[].isActiveoptionalbooleanVisible in store when 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,
},
]);Deletes categories by UUID. Fails if a category has assigned products or active subcategories.
JSON body (camelCase)
idsrequireduuid[]Category IDs to delete (1–50).
{ "ids": ["00000000-0000-4000-8000-000000000101"] }{ "deleted_count": 1 }await client.deleteCatalogCategories([
'00000000-0000-4000-8000-000000000101',
]);Image flow step 1: obtains Cloudflare `upload_url` and reserves a `cloudflare_image_id` per category.
JSON body (camelCase)
uploadsrequiredarray1–50 upload requests.
uploads[].clientReferencerequiredstringLocal reference to correlate the response.
uploads[].categoryIdrequireduuidCategory that will receive the image.
{
"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_urlImage flow step 3: associates the uploaded `cloudflareImageId` with the category.
JSON body (camelCase)
imagesrequiredarray1–50 records.
images[].categoryIdrequireduuidTarget category.
images[].cloudflareImageIdrequiredstringID returned from 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 },
]);Removes the image from one or more categories. Body uses category IDs (not image IDs).
JSON body (camelCase)
idsrequireduuid[]Category IDs whose image will be removed.
{ "ids": ["00000000-0000-4000-8000-000000000101"] }{ "deleted_count": 1 }await client.deleteCatalogCategoryImages([categoryId]);Currencies the merchant can sell in (based on configured payment methods). Required when creating products (`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();Creates a product. You may include variants in the same request. Product and variant `id` values are defined by your integration (UUID v4).
JSON body (camelCase)
idrequireduuidProduct UUID.
namerequiredstringProduct name.
currencyIdrequireduuidBase currency (see listCatalogCurrencies).
categoryIdoptionaluuid | nullOptional category.
slugoptionalstringURL slug; derived from name if omitted.
descriptionoptionalstringHTML or text description.
statusoptionalstring`draft` | `active` | `archived` (default `draft`).
metadataoptionalobjectArbitrary merchant JSON.
variantsrequiredarrayInitial variants (optional).
variants[].idrequireduuidVariant UUID.
variants[].skurequiredstringUnique SKU per merchant.
variants[].priceInCentsrequirednumber | stringPrice in cents (integer).
variants[].compareAtPriceInCentsoptionalnumber | string | nullOptional compare-at price.
variants[].currencyIdoptionaluuidDefaults to product currencyId.
variants[].stockQuantityoptionalnumberInitial stock (default 0).
variants[].trackInventoryoptionalbooleanIf false, variant is always «in stock».
variants[].weightGramsoptionalnumber | nullWeight for shipping.
variants[].lengthMmoptionalnumber | nullDimensions in mm.
variants[].widthMmoptionalnumber | nullDimensions in mm.
variants[].heightMmoptionalnumber | nullDimensions in mm.
variants[].optionsoptionalobjectAttributes (size, color, etc.).
variants[].isActiveoptionalbooleanSellable variant (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,
}],
});Paginated product list with filters by status, category, text, and availability.
Query string
statusoptionalstring`draft` | `active` | `archived`.
categoryIdoptionaluuidFilter by category.
qoptionalstringSearch by name, slug, or SKU.
pageoptionalnumberPage (default 1).
limitoptionalnumberPage size (default 20).
inStockoptionalbooleanWhen `true`, only products with sellable stock.
{
"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,
});Full detail: product, all variants, and associated images.
Path parameters
product_idrequireduuidProduct UUID.
{
"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');Partial product update (send only fields that change).
Path parameters
product_idrequireduuidProduct UUID.
JSON body (camelCase)
nameoptionalstringNew name.
slugoptionalstringNew slug (unique per merchant).
descriptionoptionalstring | nullDescription.
categoryIdoptionaluuid | nullReassign category or `null`.
statusoptionalstring`draft` | `active` | `archived`.
metadataoptionalobject | nullJSON metadata.
{
"name": "Camiseta premium",
"status": "active",
"categoryId": "00000000-0000-4000-8000-000000000101"
}await client.updateCatalogProduct(productId, {
name: 'Camiseta premium',
status: 'active',
});Deletes products and associated variants/images in bulk.
JSON body (camelCase)
idsrequireduuid[]Product IDs (1–50).
{ "ids": ["00000000-0000-4000-8000-000000000301"] }{ "deleted_count": 1 }await client.deleteCatalogProducts([productId]);Partial variant update (price, stock, SKU, options, shipping).
Path parameters
product_idrequireduuidProduct UUID.
variant_idrequireduuidVariant UUID.
JSON body (camelCase)
skuoptionalstringUnique SKU.
nameoptionalstringVariant name.
priceInCentsoptionalnumber | stringPrice in cents.
compareAtPriceInCentsoptionalnumber | string | nullCompare-at price.
currencyIdoptionaluuidVariant currency.
stockQuantityoptionalnumberAvailable stock for sale.
trackInventoryoptionalbooleanInventory tracking.
weightGramsoptionalnumber | nullWeight in grams.
lengthMmoptionalnumber | nullLength in mm.
widthMmoptionalnumber | nullWidth in mm.
heightMmoptionalnumber | nullHeight in mm.
optionsoptionalobject | nullVariant attributes.
isActiveoptionalbooleanActive/sellable variant.
{
"priceInCents": 34900,
"stockQuantity": 25,
"isActive": true
}await client.updateCatalogProductVariant(productId, variantId, {
priceInCents: 34900,
stockQuantity: 25,
});Sets physical warehouse inventory. Does not directly affect sellable store stock (`stockQuantity`); operational/informational.
Path parameters
product_idrequireduuidProduct UUID.
variant_idrequireduuidVariant UUID.
JSON body (camelCase)
warehouseStockQuantityrequirednumberUnits in warehouse (integer ≥ 0).
{ "warehouseStockQuantity": 100 }await client.setCatalogVariantWarehouseStock(productId, variantId, 100);Creates or updates variants in bulk. Each item must include `productId` and variant `id`.
JSON body (camelCase)
variantsrequiredarray1–50 variants.
variants[].idrequireduuidVariant UUID.
variants[].productIdrequireduuidParent product.
variants[].skurequiredstringUnique SKU.
variants[].priceInCentsrequirednumber | stringPrice in cents.
variants[].nameoptionalstringOptional name.
variants[].stockQuantityoptionalnumberAvailable stock.
variants[].trackInventoryoptionalbooleanInventory tracking.
variants[].optionsoptionalobjectAttributes.
variants[].isActiveoptionalbooleanActive/sellable.
{
"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,
}]);Deletes variants by UUID. A product should keep at least one active variant in practice.
JSON body (camelCase)
idsrequireduuid[]Variant IDs (1–50).
{ "ids": ["00000000-0000-4000-8000-000000000302"] }{ "deleted_count": 1 }await client.deleteCatalogVariants([variantId]);Step 1: request `upload_url` and `cloudflare_image_id` per product.
JSON body (camelCase)
uploadsrequiredarray1–50 requests.
uploads[].clientReferencerequiredstringLocal correlation.
uploads[].productIdrequireduuidTarget product.
{
"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 },
]);Step 3: register images already uploaded to Cloudflare in the product catalog.
JSON body (camelCase)
imagesrequiredarray1–50 images.
images[].idrequireduuidImage record UUID.
images[].productIdrequireduuidProduct.
images[].variantIdoptionaluuid | nullSpecific variant (optional).
images[].cloudflareImageIdrequiredstringCloudflare ID.
images[].altTextoptionalstringAlt text.
images[].sortOrderoptionalnumberGallery order (default 0).
images[].isPrimaryoptionalbooleanWhen true, this image appears on listing cards (home, categories, search).
{
"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,
}]);Updates metadata for registered images. Use `isPrimary: true` for the listing card image.
JSON body (camelCase)
imagesrequiredarray1–50 updates.
images[].idrequireduuidImage record UUID.
images[].isPrimaryoptionalbooleanWhen true, this image appears on listing cards (home, categories, search).
images[].variantIdoptionaluuid | nullAssign to a variant, or null for the whole product.
images[].altTextoptionalstring | nullAlt text.
images[].sortOrderoptionalnumberGallery order.
{
"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 }]);Deletes catalog image records by product image UUID (not category image delete).
JSON body (camelCase)
idsrequireduuid[]Product image IDs (1–50).
{ "ids": ["00000000-0000-4000-8000-000000000401"] }{ "deleted_count": 1 }await client.deleteCatalogImages([imageId]);Applies stock deltas to variants. Useful for receipts, shrinkage, or corrections. Positive `quantityDelta` adds; negative subtracts.
JSON body (camelCase)
adjustmentsrequiredarray1–50 adjustments.
adjustments[].variantIdrequireduuidVariant to adjust.
adjustments[].quantityDeltarequirednumberNon-zero integer (+/- units).
adjustments[].reasonoptionalstring`sale` | `restock` | `adjustment` | `return` | `reservation` (default `adjustment`).
adjustments[].referenceTypeoptionalstringSource document type (optional).
adjustments[].referenceIdoptionaluuidSource document ID (optional).
{
"adjustments": [
{
"variantId": "00000000-0000-4000-8000-000000000302",
"quantityDelta": 10,
"reason": "restock"
}
]
}{ "adjusted_count": 1 }await client.adjustCatalogInventory([{
variantId,
quantityDelta: 10,
reason: 'restock',
}]);