Product catalog
Push and maintain your catalog by SKU from your own system (ERP, PIM, store).
Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/products | Paginated 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/products | Bulk 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}/revisions | What 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 }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.