Catalog

Product data on revenexx — the product row, the attribute data model that shapes it, categories, reference domains, assets, associations, completeness, and bulk import/export.

The catalog is the products app: 16 entities and 92 capability routes, all under https://api.revenexx.com/v1/products — plus the shared io app under /v1/io for bulk import and export.

The one thing to understand first

A product row carries only what every product has: sku, kind, family_id, enabled, tax_class. Everything the merchant modelled — names, descriptions, weights, colours, technical data — lives in a single attribute_values jsonb document, keyed by attribute code.

That means the catalog's shape is data, not schema. Adding a "load capacity in kg" property to a product family is a row in attributes and a row in family_attributes, not a migration. A client that hard-codes column names will not survive a merchant modelling their own catalog; a client that reads GET /v1/products/attribute-schema will.

Routes at a glance

AreaBase path
Products/v1/products
Grid and batch/v1/products/grid, /v1/products/batch, /v1/products/labels
Attributes and families/v1/products/attributes, /attribute_groups, /attribute_options, /families, /family_attributes, /family_variants, /measurement_families
Categories/v1/products/categories, /v1/products/product_categories
References/v1/products/reference_entities, /reference_entity_records
Manufacturers/v1/products/manufacturers — see Manufacturers
Assets/v1/products/asset_families, /v1/products/assets
Associations/v1/products/association_types, /v1/products/product_associations
CompletenessPOST /v1/products/{id}/completeness, GET /v1/products/attribute-schema
VocabulariesGET /v1/products/vocabularies, /vocabularies/{name}
Bulk/v1/io/profiles, /v1/io/uploads, /v1/io/imports, /v1/io/exports, /v1/io/bulk-jobs

Every one of them is browsable and callable in the API Explorer.

Manufacturers

A manufacturer is the company a product is made by, kept once and pointed at by every product it makes (products.manufacturer_id). Besides what a brand page shows, it holds what the EU General Product Safety Regulation (GPSR) requires an offer to name. POST /v1/products/manufacturers creates one, and the other routes work on one manufacturer by the id that GET /v1/products/manufacturers returns; restoring one from the trash is covered below:

MethodPathWhat it does
POST/v1/products/manufacturersCreates a manufacturer. code, name and country are required; country must be an ISO 3166-1 alpha-2 code in capitals, else 400 (invalid_country). An address that is not a valid platform address answers 400 (invalid_address). A code already taken answers 409.
GET/v1/products/manufacturers/{id}Reads one manufacturer, the whole row as stored.
PUT/v1/products/manufacturers/{id}Partial patch: changes only the named columns. A country that is null or malformed answers 400 (invalid_country), and so does an invalid address (invalid_address). A code already taken answers 409.
DELETE/v1/products/manufacturers/{id}Moves it to the trash; called on one already there, deletes it for good. Deleting for good is refused with 409 while a switched-on product still made by the manufacturer holds it; any other product is unlinked. A storefront contact can move it to the trash but not delete it for good (403). See Trash and restore.
GET/v1/products/manufacturers/{id}/gpsrLists which GPSR data the manufacturer has and which it is missing: the legal name, a postal address with country and an e-mail address, and for a manufacturer outside the European Economic Area (EEA) the responsible person in the EEA with address and e-mail. Writes nothing.
PUT/v1/products/manufacturers/{id}/logoSets the logo to an existing asset of this catalog (asset_id; the asset needs a delivery path or external URL already, and the logo links to its code), or to a delivery path or https URL (path). asset_id wins when both are sent.
DELETE/v1/products/manufacturers/{id}/logoClears the logo. The asset or the stored file it pointed at is left alone.
GET/v1/products/manufacturers/{id}/productsLists the products linked to the manufacturer, paged. A product that names the manufacturer only in the older free-text attribute is not listed until POST /v1/products/manufacturers/migrate links it.

Trash and restore

Deleting a manufacturer sends it to the trash. A second delete of the same manufacturer deletes it for good, and so does emptying the trash. The platform also deletes trashed records that outlive the tenant's trash retention (90 days unless the tenant set another), except one that something still blocks.

The first delete fires manufacturer.trashed and a restore manufacturer.restored. manufacturer.deleted fires only once the record is deleted for good. See Deletes and the trash.

  • Restore. POST /v1/products/manufacturers/{id}/restore brings the manufacturer back from the trash. Nothing else goes to the trash with a manufacturer, so nothing else comes back with it. Everything that pointed at the manufacturer acts again as it did, because nothing was changed when it went. Restoring needs the same right as deleting. The answer lists every record that came back in restored.
  • Refused with 409. not_in_trash when the manufacturer is not in the trash. parent_in_trash when the record it belongs to is in the trash in another group; details.restore names the call that brings that one back first.
  • Refused with 404 when no manufacturer of this tenant has that id. A manufacturer of another tenant gets the same 404, not a 403.
  • Empty the trash. POST /v1/products/manufacturers/trash/empty deletes the trashed manufacturers for good, oldest first, up to 200 per call, each through the same checks as a second DELETE.
  • A record a blocker holds is listed in blocked with its reason and stays in the trash; the rest of the call goes on.
  • deleted lists what this call removed for good. more: true means the call stopped at its limit and the trash may hold more: call it again.
  • A storefront contact gets 403: emptying the trash is back-office work.

GET /v1/products/manufacturers also filters on active. active is true unless the entry was retired (false). A list returns both unless you filter, so use ?active=true for a picker. Records that already carry a retired entry keep it; a write cannot newly assign one (409 entry_inactive).

Reading and filtering the trash. GET /v1/products/manufacturers leaves trashed manufacturers out unless the call asks with trashed: only returns the trash alone, include returns the live and the trashed manufacturers together. Each manufacturer carries the fields below, and each one is also an exact-match filter on the list call.

FieldWhat it holds
deleted_atWhen the manufacturer went to the trash; null while it is live.
deleted_byWho moved it there: an e-mail, or system for a scheduled job. A name for the trash view, not an identity to check.
deleted_by_idThe platform subject of that person, the stable id a privacy request pseudonymises. Null for a scheduled job.
purge_atWhen the daily purge deletes it for good: deleted_at plus the tenant's trash retention (90 days unless the tenant set another).
trash_batchThe trash action it went with. A restore brings back exactly the records that carry the same value.
purge_blocked_reasonWhy the daily purge could not delete it for good; null when nothing held it.

The platform sets these fields when a manufacturer is deleted and clears them when it is restored, never through an update, so a client does not normally send them in a write.

In this group

  • Products — the row, the variant hierarchy, the attribute document, the grid.
  • Data model — attributes, groups, options, families, variants, measurement families.
  • Categories — the tree, membership, rules.
  • References — brands, manufacturers and other reference domains.
  • Assets — asset families, assets, and where the bytes live.
  • Associations — cross-sells, accessories, quantified relations.
  • Completeness — how complete is this product, and against what.
  • Import and export — profiles, jobs, files.