Skip to content
Catalog

Categories and collections

Organize the catalog with categories, brands, collections, attributes, and size guides.

Organize the catalog of a clothing store in Lima: the category Ropa > Polos, the brand "Andes", the "Verano" collection, a material attribute, and a size guide for the polo shirts.

Requirements

  • An Admin token with the catalog:create, catalog:read, catalog:update, and catalog:delete permissions in ECOMIQ_TOKEN. See Quickstart.
  • A product. See Variants and prices.

Create the categories

First create Ropa with { "name": "Ropa" }. Then create Polos with the id of Ropa in parentId.

curl -X POST https://api.ecomiq.pe/api/v1/catalog/categories \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Polos", "parentId": "7204558912004325501" }'

The API responds 201 with the id. The next steps reuse call.

  • Without slug, one is generated from name. It is unique across the store, even for categories with different parents.
  • order sorts sibling categories, lowest first.

Arrange the tree

GET /v1/catalog/categories/tree returns every category in order, each with its children in children and its route in path (ropa/polos).

To move or reorder, send the tree with PUT. If you later create Casacas, this example puts it before Polos. The API responds 200 with true.

curl -X PUT https://api.ecomiq.pe/api/v1/catalog/categories/tree \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "categories": [{
    "id": "7204558912004325501",
    "children": [{ "id": "7204558912004325503" }, { "id": "7204558912004325502" }]
  }] }'
  • Nesting sets the parent, and the position in each list sets order. The parentId and order of each node are ignored.
  • Top-level nodes become root categories. Categories you leave out don't change.

PUT replaces the category

PUT /v1/catalog/categories/{categoryId} can also move a category to another parentId, but it replaces it whole: if you omit parentId, order, description, logo, icon, requiresSizeGuide, or the commissions, they reset to their defaults. Without slug, a new one is generated from name.

Create the brand

curl -X POST https://api.ecomiq.pe/api/v1/catalog/brands \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Andes", "logo": "https://cdn.mitienda.pe/marcas/andes.png" }'

The API responds 201 with the id.

  • Without slug, one is generated from name. It is unique across the store.
  • logo and icon must be absolute URLs.
  • PUT /v1/catalog/brands/{brandId} replaces the name, slug, images, description, and order.

Assign category and brand

Send categoryId and brandId when you update the product, or when you create it. This example also adds the verano tag.

curl -X PATCH https://api.ecomiq.pe/api/v1/catalog/products/7204558912004325302 \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "categoryId": "7204558912004325502", "brandId": "7204558912004325510", "tags": ["verano"] }'
Response 200 (excerpt)
{
  "id": "7204558912004325302",
  "tags": ["verano"],
  "category": {
    "id": "7204558912004325502",
    "name": "Polos",
    "path": "ropa/polos"
  },
  "brand": { "id": "7204558912004325510", "name": "Andes" }
}
  • A product has one category and one brand.
  • To remove them, send clearCategory: true or clearBrand: true instead.

Create an automatic collection

A dynamic collection fills itself with the products that match its rules. "Verano" groups the polos tagged verano and pins one at the top. With and, a product must match every rule; with or, one is enough.

verano.json
{
  "name": "Verano",
  "type": "dynamic",
  "status": "active",
  "dynamicOperator": "and",
  "rules": [
    { "field": "categoryId", "operator": "eq", "value": "7204558912004325502" },
    { "field": "tag", "operator": "eq", "value": "verano" }
  ],
  "pins": [{ "productId": "7204558912004325302", "position": 0 }]
}
curl -X POST https://api.ecomiq.pe/api/v1/catalog/collections \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  --data @verano.json

The API responds 201 with the id.

To includeRule
Several brands{ "field": "brand", "operator": "in", "value": "<id>,<id>" }
Under S/ 100{ "field": "price", "operator": "lt", "value": "100" }
A word in the title{ "field": "title", "operator": "contains", "value": "pima" }
  • value is always a string. With in and notIn, separate up to 100 values with commas.
  • categoryId matches the product's own category, not its subcategories.
  • includeProductIds adds products. excludeProductIds removes them, even if they match the rules or are pinned.

The collection fills in the background, and only while it is active and within validFrom and validTo. Its products, with sortOrder and isPinned, are in GET /v1/catalog/collections/{collectionId}/products.

To edit, send everything

PUT /v1/catalog/collections/{collectionId} replaces the collection. Without type, it becomes static and loses its rules and products. Without status, it goes back to draft.

Create a manual collection

A static collection holds only the products you choose. Create it and send the list. The PUT responds 204.

curl -X POST https://api.ecomiq.pe/api/v1/catalog/collections \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Regalos", "type": "static", "status": "active" }'

curl -X PUT https://api.ecomiq.pe/api/v1/catalog/collections/7204558912004325521/products \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "productIds": ["7204558912004325302", "7204558912004325303"] }'
  • The list replaces the previous one, and its order is the collection's order. IDs that don't exist are ignored.
  • To add or remove products without sending the whole list, use POST /v1/catalog/products/batch/collections.

Define an attribute

An attribute (specification) describes the product, such as its material. With type: "select", it only accepts its values: each one has a value that identifies it and a name that is displayed.

curl -X POST https://api.ecomiq.pe/api/v1/catalog/specifications \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Material", "key": "material", "type": "select",
    "values": [{ "value": "algodon-pima", "name": "Algodón pima" }, { "value": "lino", "name": "Lino" }]
  }'

The API responds 201 with the id. The product stores its value in specifications, with the attribute's key:

curl -X PATCH https://api.ecomiq.pe/api/v1/catalog/products/7204558912004325302 \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "specifications": [{ "key": "material", "name": "Material", "value": "algodon-pima" }] }'
  • key accepts letters, numbers, -, and _. It is stored in lowercase and is unique across the store.
  • specifications replaces the product's list. A key that doesn't exist creates a new text attribute.
  • If the value is not one of the attribute's options, the API responds 409.

To edit, send everything

PUT /v1/catalog/specifications/{id} replaces the attribute. Always send type: without it, the API assumes input. In values, send the options you keep with their id and new ones without id. A missing option is deleted; if it is in use, the API responds 409.

Require the attribute in a category

Link Material to Polos and make it required. The API responds 204. The list replaces the previous one, and GET on the same path returns it.

curl -X PUT https://api.ecomiq.pe/api/v1/catalog/categories/7204558912004325502/specifications \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "specifications": [{ "specificationId": "7204558912004325530", "isRequired": true }] }'
  • It takes up to 100 active attributes, with level product or all.
  • A Polos product in active or outOfStock needs that value, or the API responds 409 when you save it. Only its own category counts.
  • Only categories require attributes: a product without a category has none.

Create the size guide

A guide is a table of measurements per size. Assign it on creation with assignments.

guia-polos.json
{
  "name": "Polos unisex",
  "status": "active",
  "rows": [
    {
      "label": "S",
      "sizeKey": "S",
      "order": 0,
      "measurements": [
        {
          "key": "pecho",
          "name": "Pecho",
          "min": 86,
          "max": 91,
          "value": null,
          "description": null
        }
      ]
    },
    {
      "label": "M",
      "sizeKey": "M",
      "order": 1,
      "measurements": [
        {
          "key": "pecho",
          "name": "Pecho",
          "min": 92,
          "max": 97,
          "value": null,
          "description": null
        }
      ]
    }
  ],
  "assignments": [
    { "targetType": "category", "targetId": "7204558912004325502" }
  ]
}
curl -X POST https://api.ecomiq.pe/api/v1/catalog/size-guides \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  --data @guia-polos.json

The API responds 201 with the id.

  • Only active guides apply. Without status, the guide stays in draft.
  • It needs rows or imageUrl. Each row and measurement carries all its fields, even if they are null.
  • Without gender, measurementType, or unit, they default to unisex, body, and cm. If another active guide with the same gender and measurementType is on that product or category, the API responds 409.

To edit, send everything

PUT /v1/catalog/size-guides/{sizeGuideId} replaces the data and rows, and keeps the assignments. Without status, the guide goes back to draft and stops applying.

Assign the guide

The closest assignment wins: the product's own, then its category's, then a parent category's with includeChildrenCategories: true. Between guides at the same level, the higher priority wins.

# A product's own guide. Responds 204. With null, you remove it.
curl -X PUT https://api.ecomiq.pe/api/v1/catalog/products/7204558912004325302/size-guide \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "sizeGuideId": "7204558912004325540" }'

# A category and its children. Responds 201 with the assignment id.
curl -X POST https://api.ecomiq.pe/api/v1/catalog/size-guides/7204558912004325540/assignments \
  -H "Authorization: Bearer $ECOMIQ_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "targetType": "category", "targetId": "7204558912004325501", "includeChildrenCategories": true }'

GET /v1/catalog/products/{productId}/size-guide responds 200 with the guide that applies, or 204 if none does. To remove an assignment, use DELETE /v1/catalog/size-guides/{sizeGuideId}/assignments/{assignmentId}.

Delete what you don't use

Each DELETE responds 204.

ResourcePathDeleted if
Category/v1/catalog/categories/{categoryId}It has no subcategories or products.
Brand/v1/catalog/brands/{brandId}No product uses it.
Attribute/v1/catalog/specifications/{id}No category, product, or variant uses it.
Collection/v1/catalog/collections/{collectionId}Always. Products don't change.
Size guide/v1/catalog/size-guides/{sizeGuideId}Always. It leaves the products that used it.

GET /v1/catalog/specifications/{id}/usage tells you how many products and variants use an attribute, and which categories have it.

Import and export

Categories, brands, collections, and size guides also move by file, through /imports and /exports. They respond 202 with an operation: follow it as in Product import. When an export finishes, resultUrl has the file.

Errors

codeStatusWhat to do
Common.Validation400Check errors: a field is missing or malformed.
Category.SlugExists, Brand.SlugExists, Collection.SlugExists, Collection.Exists, SizeGuide.SlugExists409Another resource uses that slug or name: change it.
Category.ParentNotFound404The parentId doesn't exist in the store.
Category.CircularReference, Categories.InvalidHierarchy400A category can't end up inside itself.
Category.HasSubcategories, Category.HasProducts, Brand.HasProducts409Move the subcategories or products before deleting.
Product.CategoryNotFound, Product.BrandNotFound404Check the category or brand ID.
Collection.InvalidType400Only static collections take products by hand.
Specification.AlreadyExists409Another attribute uses that key or alias.
Specification.ValueInUse409Change that value on the products before removing it.
Specification.HasCategorySpecifications, Specification.InUseByProducts, Specification.InUseByVariants409Remove the attribute from categories, products, and variants before deleting it.
Category.Specifications400An attribute is repeated, missing, inactive, or not for products.
SizeGuideAssignment.Conflict409A compatible guide already targets that product or category.
SizeGuide.Inactive400Activate the guide before assigning it to a product.
Common.Conflict409Read detail: a required attribute is missing or a value is invalid.

Next steps

Last updated:

On this page