bruxel.ai

Product catalog

Push and maintain your catalog by SKU from your own system (ERP, PIM, store).

Endpoints

EndpointWhat it does
GET /v1/productsPaginated list (limit ≤ 200, offset, q search, status). Scope catalog:read.
GET /v1/products/{id}Product detail, including its source_data. Scope catalog:read.
POST /v1/productsBulk upsert by SKU, up to 500 per request. Scope catalog:write.
PATCH /v1/products/{id}Partial edit of sku, name, brand or category. Scope catalog:write.
DELETE /v1/products/{id}Soft delete (204). Keeps history; hidden from lists and export. Scope catalog:write.
GET /v1/products/{id}/revisionsWhat changed in the record: versions, with what changed in each one. Scope catalog:read.

Bulk upsert by SKU

The POST is idempotent per (organization, SKU): re-sending the same batch leaves the catalog unchanged, and a previously deleted SKU is revived. The response breaks down how many were created, updated or skipped.

POST /v1/products
{
  "products": [
    {
      "sku": "CAMISA-001",
      "name": "Camisa de lino",
      "brand": "Acme",
      "category": "Ropa/Camisas",
      "source_data": { "color": "azul", "talla": "M" }
    }
  ]
}

{ "total": 1, "created": 1, "updated": 0, "skipped": 0 }
A re-upsert REPLACES the entire product by SKU (including its source_data). Always send the full product, not just the fields that changed — for one-off edits use PATCH.

source_data

source_data is yours: a free-form JSON object with your product's extra attributes (color, size, material, specs…). The generation engine uses it as context to write better descriptions — the richer it is, the better the result.

Editing and deleting

PATCH edits individual fields without touching the rest. The product's status is NOT editable via API (the generation cycle manages it). If the new SKU already exists on another product, it responds 409.

DELETE is a soft delete: the product disappears from lists and the export but its history is kept, and a later upsert with the same SKU revives it. Repeating the DELETE responds 404.

A record's history

If you sync with your store, this tells you what to look at without downloading the whole record again: each version carries what changed from the previous one — fields, attributes (by code and language), categories and family. From imported data it reports the names of the columns that changed, never their contents.

GET /v1/products/7f3a…/revisions

{
  "revisions": [
    {
      "version": 7,
      "reason": "import",
      "created_at": "2026-09-12T05:03:11Z",
      "changes": {
        "base": { "name": ["Crema hidratante", "Crema hidratante 50 ml"] },
        "values": { "tamano": { "": [["1.7 OZ"], ["50 ml"]] } },
        "source_data": { "changed": ["precio"] }
      }
    }
  ],
  "total": 7,
  "limit": 20
}

It pages backwards: limit (1–50, 20 by default) and before_version, which you set to the lowest version you received. changes is empty on a product's first version, which has nothing to compare against; and if a change is huge it arrives trimmed, with _truncated and counts.

It doesn't carry who made the change —that's internal to your organization and shows in the app— nor the full state of the record at each version, which you get from the product detail. Going back to an earlier version isn't available over the API either: that's done in the app, where it requires an administrator role.
Product catalog — Bruxel