{
  "openapi": "3.1.0",
  "info": {
    "title": "FlowCraftDB Partner API",
    "version": "1.3.1",
    "summary": "Read access to a tenant's product data.",
    "x-grants": [
      {
        "name": "products",
        "kind": "endpoint",
        "endpoints": [
          "/products",
          "/products/{productId}"
        ]
      },
      {
        "name": "variants",
        "kind": "endpoint",
        "endpoints": [
          "/products",
          "/products/{productId}"
        ]
      },
      {
        "name": "categories",
        "kind": "endpoint",
        "endpoints": [
          "/categories",
          "/categories/{categoryId}"
        ]
      },
      {
        "name": "attributes",
        "kind": "endpoint",
        "endpoints": [
          "/attributes",
          "/attributes/{attributeId}"
        ]
      },
      {
        "name": "manufacturers",
        "kind": "endpoint",
        "endpoints": [
          "/manufacturers",
          "/manufacturers/{manufacturerId}"
        ]
      },
      {
        "name": "suppliers",
        "kind": "endpoint",
        "endpoints": [
          "/suppliers",
          "/suppliers/{supplierId}"
        ]
      },
      {
        "name": "media",
        "kind": "field",
        "on": "Product",
        "fields": [
          "images",
          "documents"
        ]
      },
      {
        "name": "classifications",
        "kind": "field",
        "on": "Product",
        "fields": [
          "classifications"
        ]
      }
    ],
    "description": "Read-only access to one tenant's catalogue, for an external system that\nmirrors it.\n\nEverything here is a `GET`. Nothing in FlowCraftDB can be changed through\nthis API, and a token cannot be widened into one that can.\n\n## Authentication\n\nSend the token you were issued as a bearer token:\n\n```\nAuthorization: Bearer fcdb_<keyId>_<secret>\n```\n\nThe token is shown once when it is created and cannot be recovered. If\nit is lost, it is rotated, not retrieved. Every refusal answers `401`\nwith the same body regardless of cause - a wrong secret, a revoked token\nand an expired one are deliberately indistinguishable.\n\nA token belongs to exactly one tenant. Other tenants are not blocked by a\ncheck; they are not addressable through this API at all.\n\n## Your first sync\n\nIn order, because each step makes the next one readable:\n\n1. `GET /attributes` - the definitions. `dataType` tells you how to read\n   every product value, and `options` lets you map option values to your\n   own vocabulary before you touch the catalogue.\n2. `GET /categories` - each one carries its full ancestor `path`, so you\n   can place a category without assembling the tree yourself.\n3. `GET /manufacturers` and `GET /suppliers` - id and name.\n4. `GET /products` - page with `cursor` until `nextCursor` is `null`.\n\nThen keep it current:\n\n- Poll each endpoint with `updatedSince` set to the start of your last\n  successful run, still paging with `cursor`.\n- Poll `/deletions` on the same schedule. A delta pull can only report\n  records that still exist, so without it anything deleted here stays in\n  your system forever.\n\nDeletion records are kept for 90 days. If you have been offline longer\nthan that, start again from a full walk.\n\n## What your token may read\n\nA credential is granted a set of *objects*, ticked by the tenant when the\ntoken is issued. Anything not ticked answers `403` - that is the grant\nworking, not a fault. If an endpoint you expect to use returns `403`, ask\nthe tenant to add the object rather than retrying.\n\n| Object | Unlocks |\n| --- | --- |\n| `products` | `/products`, `/products/{productId}` |\n| `variants` | the same endpoints; variants are products |\n| `categories` | `/categories`, `/categories/{categoryId}` |\n| `attributes` | `/attributes`, `/attributes/{attributeId}` |\n| `manufacturers` | `/manufacturers`, `/manufacturers/{manufacturerId}` |\n| `suppliers` | `/suppliers`, `/suppliers/{supplierId}` |\n| `media` | `images` and `documents` on a `Product` |\n| `classifications` | `classifications` on a `Product` |\n\n`media` and `classifications` gate fields rather than endpoints. Without\n`media`, products still return but `images` and `documents` are empty and\n`image`/`file` attributes are left out of `attributeSets` entirely.\nWithout `classifications`, `classifications` is empty.\n\n`/deletions` is authorized per row: it reports deletions of the resources\nyour token may read, and refuses only when it may read none of them.\n\n## Rate limits\n\n**600 requests per minute per credential**, unless yours was issued with a\ndifferent budget. Every response carries the live figures:\n\n```\nRateLimit-Limit: 600\nRateLimit-Policy: 600;w=60\nRateLimit-Remaining: 573\nRateLimit-Reset: 41\n```\n\nOver the budget you get `429`, carrying both a `Retry-After` in seconds\nand the same `RateLimit-*` figures. Wait that long rather than retrying\nimmediately - a retry loop that ignores it will stay locked out.\n\nFailed authentication is counted separately and **per calling address**,\nnot against your credential. Nobody else's failed attempts can consume\nyour budget or lock your token out, whatever they know about it - so a\n`keyId` seen in a log or a support thread is not something an attacker\ncan turn against your availability. Only a `401` counts: a `403` from a\ngrant you were not given, or a `400` from a malformed parameter, costs\nyou nothing.\n\nAt `limit=500`, a 50 000-product catalogue is 100 requests. The budget is\nnot the constraint on a nightly sync; it is there to bound a leaked token.\n\n### Run your sync in sequence\n\nAlongside the per-minute budget there is a **limit on requests in flight\nat once**: four slots for you, eight across all partners, *per serving\ninstance*. Beyond it a request is answered immediately with `429` and\n`Retry-After: 1` rather than being queued.\n\nA big page costs more than a small one, because it is more: one slot per\n200 products asked for, so `limit=100` and `limit=200` cost one each and\n`limit=500` costs three. A single request is never refused on its own,\nwhatever it asks for - you simply cannot run four maximum-size pages at\nthe same moment. Two pages of 200 always fit; so do four of 100.\n\nPer instance, because what it protects is one server's ability to keep\nanswering everyone. We may run more than one, so treat four as the number\nto design against rather than a threshold to feel for - which of them\nanswers you is not something either of us chooses.\n\nThis is not a quota you can wait out - it clears the moment one of your\nown requests finishes, and `Retry-After` says so: on this refusal it is a\nsecond, not the minute a budget `429` asks for. **`Retry-After` is the\nauthoritative number on both.** A sequential sync never meets this limit\nat all. Parallelism does not pay here anyway: a `limit=500` page is tens\nof megabytes, and four already saturate the connection.\n\nFor the first full sync, `limit=200` is the friendlier setting. It costs\nmore requests, well inside the budget, and keeps each response small\nenough to stay clear of the 30-second gateway timeout. If you do see a\n`503` with an HTML body, that is the gateway, not this API - retry with\nbackoff.\n\n## Attributes\n\nAlmost everything specific to a product - titles, descriptions, GTIN,\nmanufacturer part numbers, technical characteristics - is an attribute\nhere, and each tenant configures its own. The stable identifier is `code`,\nwhich survives renames; labels and ids do not.\n\n`attributeSets` is the authoritative representation, grouped the way the\ntenant organises its data. `attributes` is a convenience index by code.\nThe same attribute can appear in two sets carrying different values - in\nthat case the index entry carries `duplicatedInSets` and you should read\n`attributeSets` instead.\n\nRead `value` to map data into your own system and `display` to show a\nhuman. See `AttributeValue.value` for what `value` looks like per type.\n\n## Not available here\n\nPrices, stock and commercial data are not part of FlowCraftDB and cannot\nbe obtained through this API - with one exception: an attribute the tenant\nhas defined as `price` is published like any other attribute, carrying its\n`currency`. There is no stock anywhere.\n\nETIM classifications are published on the product - see\n`classifications` and the `classifications` grant.",
    "contact": {
      "name": "FlowCraftDB",
      "email": "hello@flowcraftdb.de"
    }
  },
  "servers": [
    {
      "url": "https://api.flowcraftdb.de/api/partner/v1",
      "description": "production"
    }
  ],
  "tags": [
    {
      "name": "Products",
      "description": "The catalogue."
    },
    {
      "name": "Reference data",
      "description": "Categories, attributes, manufacturers and suppliers that products point at. Pull these before products: they are what makes a product payload readable."
    },
    {
      "name": "Sync",
      "description": "Keeping a mirror consistent - specifically, learning about records that no longer exist."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "The token issued to you, as `Authorization: Bearer <token>`."
      }
    },
    "schemas": {
      "AttributeValue": {
        "type": "object",
        "required": [
          "code",
          "value",
          "display",
          "isVariant",
          "isFeature"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "Stable key. Survives renames, unlike labels and ids.",
            "examples": [
              "gtin"
            ]
          },
          "label": {
            "type": "string",
            "description": "Localized display label, for a spec table.",
            "examples": [
              "EAN / GTIN"
            ]
          },
          "value": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "description": "`image` and `file`.",
                "items": {
                  "$ref": "#/components/schemas/FileValue"
                }
              },
              {
                "type": "array",
                "description": "`select_multiple` and `category_select`.",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "array",
                "description": "`product_reference`.",
                "items": {
                  "$ref": "#/components/schemas/ProductReference"
                }
              },
              {
                "type": "null"
              }
            ],
            "description": "The stored value, untouched - map this into your own catalogue.\n\nWhich of the shapes below applies is determined by the attribute's `dataType`, which `/attributes` reports. The full mapping:\n\n| `dataType` | `value` | `display` |\n| --- | --- | --- |\n| `text` | string | the same string |\n| `number` | number, or a numeric string | the same |\n| `price` | number | the same; `currency` carries ISO 4217 |\n| `description` | string, may contain HTML | HTML stripped to plain text |\n| `unit` | number, or a numeric string | formatted with `unit` and any range operator |\n| `date` | ISO 8601 string | the same |\n| `select_single` | string - the **option value**, not its label | the option label |\n| `select_multiple` | array of option values | labels, joined |\n| `boolean` | boolean | `Ja`/`Nein`, or `Yes`/`No` under `locale=enUS` |\n| `color` | string - an option value | the option label |\n| `image` | array of file objects | file names, joined |\n| `file` | array of file objects | file names, joined |\n| `url` | string | the same |\n| `product_reference` | array of `{ productId, quantity?, sortOrder? }` | product names, joined |\n| `manufacturer_select` | string - a manufacturer id | the manufacturer name |\n| `category_select` | array of category ids | category names, joined |\n| `custom_domain_select` | string | the option label where one resolves |\n\n`value` is `null` for any type when the tenant has not filled the field in,\nand an empty array for the array-valued types. Both mean the same thing:\nnothing is set.\n\nA product reference is always an object, never a bare id: `productId` names the\nreferenced product, `sortOrder` is the position the tenant put it in, and\n`quantity` appears where the reference is a set component - one master with\nfour of a part. Ids alone would lose the quantity, which is the whole point\nof a set.\n\nFile objects - the entries of an `image` or `file` value - carry `url`,\n`name`, `size`, `type` (an IANA media type) and, on images, `description`\n(alt text) and `isFeatured`. Files held in our media\nlibrary also carry `mediaId`; files linked from elsewhere carry only the URL,\nand frequently report `size` as `0`. Every file on a product is also indexed\nflat under `documents`, so nothing has to know which attribute holds a\ndatasheet.",
            "examples": [
              "2324"
            ]
          },
          "display": {
            "type": "string",
            "description": "The same value resolved for reading: option labels instead of option values, manufacturer and product names instead of ids, units and ranges formatted, HTML stripped from descriptions.\n\nShow this to a human; map `value`. Matching on `display` breaks the first time the tenant renames an option or you change `locale`.",
            "examples": [
              "Blau"
            ]
          },
          "unit": {
            "type": "string",
            "examples": [
              "kg"
            ]
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 code, present on `price`-typed values. A price without it is not safe to import.",
            "examples": [
              "EUR"
            ]
          },
          "isVariant": {
            "type": "boolean",
            "description": "This attribute distinguishes variants of the same master. Use it to decide which axes generate your variant matrix - without it, variants can only be published as unrelated standalone products."
          },
          "isFeature": {
            "type": "boolean",
            "description": "Marked as a highlighted feature on this product."
          }
        }
      },
      "AttributeIndexEntry": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AttributeValue"
          },
          {
            "type": "object",
            "properties": {
              "duplicatedInSets": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Present ONLY when this attribute appears in more than one set. When present, the indexed entry is the first occurrence and may not be the one you want - read `attributeSets` instead. The same attribute can legitimately carry different values in two sets."
              }
            }
          }
        ]
      },
      "AttributeGroup": {
        "type": "object",
        "required": [
          "attributes"
        ],
        "description": "Attributes as the tenant organises them. This is the faithful representation: a flat map cannot express the same attribute appearing in two sets with different values.\n\n`attributes` always holds at least one entry - a group with nothing in it is not published at all.",
        "properties": {
          "code": {
            "type": "string",
            "examples": [
              "technik"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Technische Daten"
            ]
          },
          "attributes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AttributeValue"
            }
          }
        }
      },
      "Image": {
        "type": "object",
        "required": [
          "url",
          "isFeatured"
        ],
        "description": "Empty when the credential does not carry the `media` grant.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "name": {
            "type": "string"
          },
          "isFeatured": {
            "type": "boolean"
          }
        }
      },
      "FileValue": {
        "type": "object",
        "required": [
          "url"
        ],
        "description": "One entry of an `image` or `file` attribute value. Those values are always arrays - an attribute can hold several datasheets.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "name": {
            "type": "string",
            "examples": [
              "datenblatt.pdf"
            ]
          },
          "size": {
            "type": "integer",
            "description": "Bytes. Frequently `0` for a file linked from elsewhere rather than uploaded here.",
            "examples": [
              1398504
            ]
          },
          "type": {
            "type": "string",
            "description": "IANA media type.",
            "examples": [
              "application/pdf"
            ]
          },
          "mediaId": {
            "type": "string",
            "description": "Present only for files held in our media library. A file linked from a supplier's own site carries a URL and no id."
          },
          "description": {
            "type": "string",
            "description": "Alt text for an image, where the tenant maintains one.",
            "examples": [
              "Contura Kaminofen 35 Hoch, Lack weiss"
            ]
          },
          "isFeatured": {
            "type": "boolean",
            "description": "The lead entry of this attribute's value. Unrelated to `images`, which carries the product's own featured image."
          }
        }
      },
      "ClassificationProperty": {
        "type": "object",
        "required": [
          "code",
          "value",
          "display"
        ],
        "description": "One property of a classification, in the same shape an attribute has - `code` to map, `display` to show.\n\n`value` carries the type the classification system declares, not the one storage happens to hold: an ETIM logical feature is always a boolean here, whether it was imported as `true` or as `\"Ja\"`.",
        "properties": {
          "code": {
            "type": "string",
            "description": "The system's own property code.",
            "examples": [
              "EF000002"
            ]
          },
          "label": {
            "type": "string",
            "description": "Localized property name from the system's catalogue.",
            "examples": [
              "Nennspannung"
            ]
          },
          "attributeId": {
            "type": "string",
            "description": "Set on `internal` properties only, joinable to `/attributes`. A provider's feature is not one of our attributes, so it has none."
          },
          "value": {
            "description": "Typed by the system: an option code (`A`), a boolean (`L`), a number (`N`), or the lower bound of a range (`R`).",
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "valueTo": {
            "description": "The upper bound of a range. Absent for every other type.",
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              }
            ]
          },
          "display": {
            "type": "string",
            "description": "The same value resolved for reading: option labels, `Ja`/`Nein`, numbers with their unit.",
            "examples": [
              "grau"
            ]
          },
          "unit": {
            "type": "string",
            "examples": [
              "mm"
            ]
          }
        }
      },
      "Classification": {
        "type": "object",
        "required": [
          "system",
          "properties"
        ],
        "description": "One classification of a product, in one system at one version.\n\n**`system` and `version` together are the identity.** A class code means nothing without its release - ETIM `EC001371` carries 24 features in 8.0 and 30 in 10.0 - so a product may legitimately carry one system twice at two versions, and `?classifications=` addresses them that way.\n\n`system` is an **open vocabulary**, not an enum: `etim` and `internal` are what you will see today. Treat one you do not recognise as data you do not map, never as an error - a closed list would have to be broken to add the next one.\n\nEmpty without the `classifications` grant.",
        "properties": {
          "system": {
            "type": "string",
            "description": "`etim` or `internal` today. See above.",
            "examples": [
              "etim"
            ]
          },
          "version": {
            "type": "string",
            "description": "The system's own version. Absent for `internal`, which has no external catalogue to version.",
            "examples": [
              "10.0"
            ]
          },
          "code": {
            "type": "string",
            "description": "Class code within the system. Absent for `internal`, which classifies by property rather than by class.",
            "examples": [
              "EC001371"
            ]
          },
          "name": {
            "type": "string",
            "description": "Localized class name.\n\nETIM releases 8.0 and 9.0 carry English only; German arrived with 10.0. Asking for `locale=deDE` against an older release therefore returns English labels rather than none.",
            "examples": [
              "Bohrschrauber (Akku)"
            ]
          },
          "properties": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClassificationProperty"
            }
          }
        }
      },
      "ProductReference": {
        "type": "object",
        "required": [
          "productId"
        ],
        "description": "One entry of a `product_reference` attribute value. Those values are always arrays - cross-selling and set components both hold several.",
        "properties": {
          "productId": {
            "type": "string",
            "description": "The referenced product, resolvable through `/products/{productId}`.",
            "examples": [
              "6a7dd5db7dbde26ed3b2bcaf"
            ]
          },
          "quantity": {
            "type": "integer",
            "description": "How many of the referenced product this one contains. Set on set components; absent on plain cross-selling references.",
            "examples": [
              4
            ]
          },
          "sortOrder": {
            "type": "integer",
            "description": "The tenant's ordering within this attribute, from 0.",
            "examples": [
              0
            ]
          }
        }
      },
      "Document": {
        "type": "object",
        "required": [
          "url",
          "attributeCode"
        ],
        "description": "A file attached to a product, indexed flat across every `file`-typed attribute.\n\nFiles are stored as attribute values, because each tenant decides which documents a product has. Faithful, but not discoverable: finding a datasheet would otherwise mean knowing that *this* tenant keeps them under `technische_datenblatter` while the next one calls it something else. `attributeCode` is kept so a consumer that does care can still tell where a file came from.\n\nEmpty when the credential does not carry the `media` grant. Images are not repeated here - they are in `images`.",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "name": {
            "type": "string",
            "examples": [
              "VYZ101_71228_FRA.pdf"
            ]
          },
          "mimeType": {
            "type": "string",
            "examples": [
              "application/pdf"
            ]
          },
          "size": {
            "type": "integer",
            "examples": [
              1398504
            ]
          },
          "mediaId": {
            "type": "string"
          },
          "attributeCode": {
            "type": "string",
            "description": "The `file`-typed attribute this came from.",
            "examples": [
              "technisches_datenblatt"
            ]
          }
        }
      },
      "ProductSupplier": {
        "type": "object",
        "required": [
          "id",
          "isDefault"
        ],
        "description": "A supplier of this product, with that supplier's own article number.\n\nSupplier-scoped *attribute* overrides are deliberately not published: whether an override replaces the product's value or merely annotates it is a per-tenant question, and guessing an answer into a contract is how a consumer imports the wrong data confidently. `attributeSets` always carries the product's own values.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "examples": [
              "Bosch Professional GmbH"
            ]
          },
          "articleNumber": {
            "type": "string",
            "description": "This supplier's identifier for the product.",
            "examples": [
              "BP-88213"
            ]
          },
          "isDefault": {
            "type": "boolean",
            "description": "The tenant's primary supplier for this product."
          }
        }
      },
      "CategoryRef": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "Product": {
        "type": "object",
        "required": [
          "id",
          "type",
          "categories",
          "suppliers",
          "images",
          "documents",
          "attributeSets",
          "attributes",
          "classifications"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "6a7dd5db7dbde26ed3b2bcaf"
            ]
          },
          "sku": {
            "type": "string",
            "examples": [
              "ABC-123"
            ]
          },
          "externalId": {
            "type": "string",
            "description": "Identifier in the system this product was imported from."
          },
          "type": {
            "type": "string",
            "enum": [
              "simple",
              "master",
              "variant"
            ]
          },
          "parentId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set on variants: the master they belong to."
          },
          "status": {
            "type": "string",
            "examples": [
              "ACTIVE"
            ]
          },
          "name": {
            "type": "string",
            "examples": [
              "Bosch Akkuschrauber GSR 18V"
            ]
          },
          "manufacturer": {
            "type": [
              "object",
              "null"
            ],
            "required": [
              "id"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "suppliers": {
            "type": "array",
            "description": "Suppliers of this product. Empty rather than absent when none are configured. This is what makes `/suppliers` joinable - without it that endpoint returns names with nothing to attach them to.",
            "items": {
              "$ref": "#/components/schemas/ProductSupplier"
            }
          },
          "categories": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CategoryRef"
            }
          },
          "images": {
            "type": "array",
            "description": "Empty without the `media` grant.",
            "items": {
              "$ref": "#/components/schemas/Image"
            }
          },
          "documents": {
            "type": "array",
            "description": "Every file attached to this product, across all `file`-typed attributes. Empty without the `media` grant.",
            "items": {
              "$ref": "#/components/schemas/Document"
            }
          },
          "attributeSets": {
            "type": "array",
            "description": "Never contains an empty group: a set is published only if it has at least one value on this product.\n\nSo a set the tenant has defined but not filled in on this product is absent rather than present-and-empty, and without the `media` grant a set holding only `image`/`file` attributes disappears entirely rather than arriving as a named shell.",
            "items": {
              "$ref": "#/components/schemas/AttributeGroup"
            }
          },
          "attributes": {
            "type": "object",
            "description": "Convenience index by attribute code, over `attributeSets`. Check `duplicatedInSets` before trusting an entry.",
            "additionalProperties": {
              "$ref": "#/components/schemas/AttributeIndexEntry"
            }
          },
          "classifications": {
            "type": "array",
            "description": "Classifications of this product, one entry per system and version.\n\nDeliberately apart from `attributeSets`: an attribute is what *this tenant* records about a product, a classification is a statement in a vocabulary someone else owns. Flattening the two would lose which is which, and with it the ability to say \"give me the ETIM 10 data\".\n\nEmpty without the `classifications` grant. Narrow it per request with `?classifications=`.",
            "items": {
              "$ref": "#/components/schemas/Classification"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Drives `updatedSince` delta pulls."
          }
        }
      },
      "Category": {
        "type": "object",
        "required": [
          "id",
          "path"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "parentId": {
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "type": "array",
            "description": "Ancestors, root first, **excluding this category**.\n\nA root category answers `[]`. The full breadcrumb is therefore `[...path, theCategory]` - the category itself is left out because you already have it.\n\n`parentId` alone made the tree derivable but not available: you had to page the entire collection and assemble it before you could place a single category. A cycle in the data is truncated where it repeats rather than followed.",
            "items": {
              "$ref": "#/components/schemas/CategoryRef"
            }
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Attribute": {
        "type": "object",
        "required": [
          "id",
          "dataType"
        ],
        "description": "An attribute definition. Pull these first: `dataType` is what tells you how to read a product's `value`, and `options` lets you map a value set before you touch the catalogue.",
        "properties": {
          "id": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "dataType": {
            "type": "string",
            "enum": [
              "text",
              "number",
              "price",
              "description",
              "unit",
              "date",
              "select_single",
              "select_multiple",
              "boolean",
              "color",
              "image",
              "file",
              "url",
              "product_reference",
              "manufacturer_select",
              "category_select",
              "custom_domain_select"
            ],
            "examples": [
              "select_single"
            ],
            "description": "Determines the JSON type of every `value` for this attribute - see `AttributeValue.value` for the full mapping.\n\nTreat an unrecognised type as opaque and pass `display` through rather than failing: this list can grow."
          },
          "options": {
            "type": "array",
            "description": "Present for option-backed types, so a value set can be pre-mapped before pulling products. A product's `value` holds the option `value`; its `display` holds the matching `label`.",
            "items": {
              "type": "object",
              "required": [
                "value"
              ],
              "properties": {
                "value": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                }
              }
            }
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "NamedRecord": {
        "type": "object",
        "required": [
          "id"
        ],
        "description": "A manufacturer or supplier. Name and id only.",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Deletion": {
        "type": "object",
        "required": [
          "id",
          "resource",
          "deletedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The id as you last saw it. The record no longer exists."
          },
          "resource": {
            "type": "string",
            "enum": [
              "product",
              "category",
              "attribute",
              "manufacturer",
              "supplier"
            ]
          },
          "deletedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "Every failure answers this shape. `error` is a short, stable reason; `message` adds detail where there is any worth giving. Neither is intended for display to an end user.",
        "properties": {
          "success": {
            "type": "boolean",
            "examples": [
              false
            ]
          },
          "error": {
            "type": "string",
            "examples": [
              "Invalid API credential"
            ]
          },
          "message": {
            "type": "string",
            "examples": [
              "limit must be a positive integer"
            ]
          }
        }
      }
    }
  },
  "paths": {
    "/products": {
      "get": {
        "tags": [
          "Products"
        ],
        "operationId": "listProducts",
        "summary": "List products",
        "description": "Ordered by most recently changed, newest first.\n\nRequires the `products` grant. Filters combine with AND.",
        "x-required-grant": "products",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "description": "From the previous response's `nextCursor`. Opaque - do not construct one.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Values above 500 are clamped to 500.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Return only records changed at or after this instant. Combine with `cursor` to page a delta pull. Deletions are NOT reported here - poll `/deletions` as well, or deleted records linger in your system forever.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "description": "Language for resolved labels.",
            "schema": {
              "type": "string",
              "enum": [
                "deDE",
                "enUS"
              ],
              "default": "deDE"
            }
          },
          {
            "name": "classifications",
            "in": "query",
            "required": false,
            "description": "Which classifications to include. Never changes which products come back, only what each one carries.\n\n- `all` - every classification the grant allows. The default.\n- `none` - leave them out, and look nothing up.\n- `etim` - one system, in every version the product carries.\n- `etim:10.0` - one system in one version.\n- `etim:10.0,internal` - any of the above, comma-separated.\n\nA page carrying classifications counts double against the limit on requests in flight.",
            "schema": {
              "type": "string",
              "default": "all"
            },
            "examples": {
              "everything": {
                "value": "all"
              },
              "oneRelease": {
                "value": "etim:10.0"
              },
              "off": {
                "value": "none"
              }
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "Restrict to one product type. A `master` is never sold itself; its `variant` children are.",
            "schema": {
              "type": "string",
              "enum": [
                "simple",
                "master",
                "variant"
              ]
            }
          },
          {
            "name": "parentId",
            "in": "query",
            "description": "The variants of one master. Without this, assembling a configurable product means paging the whole catalogue and grouping by `parentId` yourself.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "categoryId",
            "in": "query",
            "description": "Products assigned to this category.\n\n**Not recursive** - products in child categories are not included. Walk `/categories` and pass each descendant to cover a subtree.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Exact match on the tenant's own status value, e.g. `ACTIVE`.\n\nDeliberately not validated against a list: each tenant sets its own vocabulary. An unknown status returns an empty page rather than an error.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Product"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass back as `?cursor=` for the next page. Null on the last page."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "6a7dd5db7dbde26ed3b2bcaf",
                      "sku": "GSR18V-60C-2",
                      "externalId": "AS400-88213",
                      "type": "variant",
                      "parentId": "6a7dd5db7dbde26ed3b2bca0",
                      "status": "ACTIVE",
                      "name": "Bosch Akkuschrauber GSR 18V-60 C, 2 Akkus",
                      "manufacturer": {
                        "id": "6a1f0c2d9a4b1e7f2c3d4e5f",
                        "name": "Bosch"
                      },
                      "suppliers": [
                        {
                          "id": "6a2b3c4d5e6f7a8b9c0d1e2f",
                          "name": "Bosch Professional GmbH",
                          "articleNumber": "BP-88213",
                          "isDefault": true
                        }
                      ],
                      "categories": [
                        {
                          "id": "6a3c4d5e6f7a8b9c0d1e2f30",
                          "code": "akkuschrauber",
                          "name": "Akkuschrauber"
                        }
                      ],
                      "images": [
                        {
                          "url": "https://media.flowcraftdb.de/6a1f/gsr18v-60c-front.jpg",
                          "name": "gsr18v-60c-front.jpg",
                          "isFeatured": true
                        }
                      ],
                      "documents": [
                        {
                          "url": "https://media.flowcraftdb.de/6a1f/GSR18V-60C_datenblatt.pdf",
                          "name": "GSR18V-60C_datenblatt.pdf",
                          "mimeType": "application/pdf",
                          "size": 1398504,
                          "mediaId": "6a1834b293de0f524bfafdae",
                          "attributeCode": "technisches_datenblatt"
                        }
                      ],
                      "attributeSets": [
                        {
                          "code": "technik",
                          "name": "Technische Daten",
                          "attributes": [
                            {
                              "code": "gtin",
                              "label": "EAN / GTIN",
                              "value": "4059952521534",
                              "display": "4059952521534",
                              "isVariant": false,
                              "isFeature": true
                            },
                            {
                              "code": "akkuspannung",
                              "label": "Akkuspannung",
                              "value": 18,
                              "display": "18 V",
                              "unit": "V",
                              "isVariant": false,
                              "isFeature": true
                            },
                            {
                              "code": "akkukapazitaet",
                              "label": "Akkukapazität",
                              "value": "2324",
                              "display": "2,0 Ah",
                              "isVariant": true,
                              "isFeature": false
                            },
                            {
                              "code": "koffer_enthalten",
                              "label": "Koffer enthalten",
                              "value": true,
                              "display": "Ja",
                              "isVariant": false,
                              "isFeature": false
                            }
                          ]
                        },
                        {
                          "code": "kaufmaennisch",
                          "name": "Kaufmännische Daten",
                          "attributes": [
                            {
                              "code": "uvp",
                              "label": "UVP",
                              "value": 249.9,
                              "display": "249.9",
                              "currency": "EUR",
                              "isVariant": false,
                              "isFeature": false
                            },
                            {
                              "code": "technisches_datenblatt",
                              "label": "Technisches Datenblatt",
                              "value": [
                                {
                                  "mediaId": "6a1834b293de0f524bfafdae",
                                  "url": "https://media.flowcraftdb.de/6a1f/GSR18V-60C_datenblatt.pdf",
                                  "name": "GSR18V-60C_datenblatt.pdf",
                                  "size": 1398504,
                                  "type": "application/pdf"
                                }
                              ],
                              "display": "GSR18V-60C_datenblatt.pdf",
                              "isVariant": false,
                              "isFeature": false
                            }
                          ]
                        }
                      ],
                      "attributes": {
                        "gtin": {
                          "code": "gtin",
                          "label": "EAN / GTIN",
                          "value": "4059952521534",
                          "display": "4059952521534",
                          "isVariant": false,
                          "isFeature": true
                        },
                        "akkukapazitaet": {
                          "code": "akkukapazitaet",
                          "label": "Akkukapazität",
                          "value": "2324",
                          "display": "2,0 Ah",
                          "isVariant": true,
                          "isFeature": false
                        }
                      },
                      "classifications": [
                        {
                          "system": "etim",
                          "version": "10.0",
                          "code": "EC001371",
                          "name": "Bohrschrauber (Akku)",
                          "properties": [
                            {
                              "code": "EF000002",
                              "label": "Nennspannung",
                              "value": 18,
                              "display": "18 V",
                              "unit": "V"
                            },
                            {
                              "code": "EF001985",
                              "label": "Akkukapazität",
                              "value": 2,
                              "display": "2 Ah",
                              "unit": "Ah"
                            },
                            {
                              "code": "EF004351",
                              "label": "Akkutechnologie",
                              "value": "EV007507",
                              "display": "Lithium-Ionen"
                            },
                            {
                              "code": "EF026203",
                              "label": "Mit Ladegerät",
                              "value": true,
                              "display": "Ja"
                            }
                          ]
                        },
                        {
                          "system": "internal",
                          "properties": [
                            {
                              "code": "einsatzbereich",
                              "label": "Einsatzbereich",
                              "attributeId": "6a4d5e6f7a8b9c0d1e2f3055",
                              "value": "4471",
                              "display": "Innenausbau"
                            }
                          ]
                        }
                      ],
                      "createdAt": "2026-02-11T09:14:03.221Z",
                      "updatedAt": "2026-08-14T16:02:55.109Z"
                    }
                  ],
                  "nextCursor": "MjAyNi0wOC0xNFQxNjowMjo1NS4xMDlafDZhN2RkNWRiN2RiZGUyNmVkM2IyYmNhZg"
                }
              }
            }
          },
          "400": {
            "description": "Invalid cursor, limit, date or locale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Bad Request",
                  "message": "limit must be a positive integer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential was not granted this object. Ask the tenant to add it - retrying will not help.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/products/{productId}": {
      "get": {
        "tags": [
          "Products"
        ],
        "operationId": "getProduct",
        "summary": "Fetch one product",
        "description": "Requires the `products` grant.\n\nAn id that is not a valid identifier answers `404` rather than `400`, so probing cannot distinguish a malformed id from one belonging to another tenant.",
        "x-required-grant": "products",
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "required": true,
            "description": "The `id` from a product, not your own identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "description": "Language for resolved labels.",
            "schema": {
              "type": "string",
              "enum": [
                "deDE",
                "enUS"
              ],
              "default": "deDE"
            }
          },
          {
            "name": "classifications",
            "in": "query",
            "required": false,
            "description": "Which classifications to include. Never changes which products come back, only what each one carries.\n\n- `all` - every classification the grant allows. The default.\n- `none` - leave them out, and look nothing up.\n- `etim` - one system, in every version the product carries.\n- `etim:10.0` - one system in one version.\n- `etim:10.0,internal` - any of the above, comma-separated.\n\nA page carrying classifications counts double against the limit on requests in flight.",
            "schema": {
              "type": "string",
              "default": "all"
            },
            "examples": {
              "everything": {
                "value": "all"
              },
              "oneRelease": {
                "value": "etim:10.0"
              },
              "off": {
                "value": "none"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The product.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Product"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6a7dd5db7dbde26ed3b2bcaf",
                    "sku": "GSR18V-60C-2",
                    "externalId": "AS400-88213",
                    "type": "variant",
                    "parentId": "6a7dd5db7dbde26ed3b2bca0",
                    "status": "ACTIVE",
                    "name": "Bosch Akkuschrauber GSR 18V-60 C, 2 Akkus",
                    "manufacturer": {
                      "id": "6a1f0c2d9a4b1e7f2c3d4e5f",
                      "name": "Bosch"
                    },
                    "suppliers": [
                      {
                        "id": "6a2b3c4d5e6f7a8b9c0d1e2f",
                        "name": "Bosch Professional GmbH",
                        "articleNumber": "BP-88213",
                        "isDefault": true
                      }
                    ],
                    "categories": [
                      {
                        "id": "6a3c4d5e6f7a8b9c0d1e2f30",
                        "code": "akkuschrauber",
                        "name": "Akkuschrauber"
                      }
                    ],
                    "images": [
                      {
                        "url": "https://media.flowcraftdb.de/6a1f/gsr18v-60c-front.jpg",
                        "name": "gsr18v-60c-front.jpg",
                        "isFeatured": true
                      }
                    ],
                    "documents": [
                      {
                        "url": "https://media.flowcraftdb.de/6a1f/GSR18V-60C_datenblatt.pdf",
                        "name": "GSR18V-60C_datenblatt.pdf",
                        "mimeType": "application/pdf",
                        "size": 1398504,
                        "mediaId": "6a1834b293de0f524bfafdae",
                        "attributeCode": "technisches_datenblatt"
                      }
                    ],
                    "attributeSets": [
                      {
                        "code": "technik",
                        "name": "Technische Daten",
                        "attributes": [
                          {
                            "code": "gtin",
                            "label": "EAN / GTIN",
                            "value": "4059952521534",
                            "display": "4059952521534",
                            "isVariant": false,
                            "isFeature": true
                          },
                          {
                            "code": "akkuspannung",
                            "label": "Akkuspannung",
                            "value": 18,
                            "display": "18 V",
                            "unit": "V",
                            "isVariant": false,
                            "isFeature": true
                          },
                          {
                            "code": "akkukapazitaet",
                            "label": "Akkukapazität",
                            "value": "2324",
                            "display": "2,0 Ah",
                            "isVariant": true,
                            "isFeature": false
                          },
                          {
                            "code": "koffer_enthalten",
                            "label": "Koffer enthalten",
                            "value": true,
                            "display": "Ja",
                            "isVariant": false,
                            "isFeature": false
                          }
                        ]
                      },
                      {
                        "code": "kaufmaennisch",
                        "name": "Kaufmännische Daten",
                        "attributes": [
                          {
                            "code": "uvp",
                            "label": "UVP",
                            "value": 249.9,
                            "display": "249.9",
                            "currency": "EUR",
                            "isVariant": false,
                            "isFeature": false
                          },
                          {
                            "code": "technisches_datenblatt",
                            "label": "Technisches Datenblatt",
                            "value": [
                              {
                                "mediaId": "6a1834b293de0f524bfafdae",
                                "url": "https://media.flowcraftdb.de/6a1f/GSR18V-60C_datenblatt.pdf",
                                "name": "GSR18V-60C_datenblatt.pdf",
                                "size": 1398504,
                                "type": "application/pdf"
                              }
                            ],
                            "display": "GSR18V-60C_datenblatt.pdf",
                            "isVariant": false,
                            "isFeature": false
                          }
                        ]
                      }
                    ],
                    "attributes": {
                      "gtin": {
                        "code": "gtin",
                        "label": "EAN / GTIN",
                        "value": "4059952521534",
                        "display": "4059952521534",
                        "isVariant": false,
                        "isFeature": true
                      },
                      "akkukapazitaet": {
                        "code": "akkukapazitaet",
                        "label": "Akkukapazität",
                        "value": "2324",
                        "display": "2,0 Ah",
                        "isVariant": true,
                        "isFeature": false
                      }
                    },
                    "classifications": [
                      {
                        "system": "etim",
                        "version": "10.0",
                        "code": "EC001371",
                        "name": "Bohrschrauber (Akku)",
                        "properties": [
                          {
                            "code": "EF000002",
                            "label": "Nennspannung",
                            "value": 18,
                            "display": "18 V",
                            "unit": "V"
                          },
                          {
                            "code": "EF001985",
                            "label": "Akkukapazität",
                            "value": 2,
                            "display": "2 Ah",
                            "unit": "Ah"
                          },
                          {
                            "code": "EF004351",
                            "label": "Akkutechnologie",
                            "value": "EV007507",
                            "display": "Lithium-Ionen"
                          },
                          {
                            "code": "EF026203",
                            "label": "Mit Ladegerät",
                            "value": true,
                            "display": "Ja"
                          }
                        ]
                      },
                      {
                        "system": "internal",
                        "properties": [
                          {
                            "code": "einsatzbereich",
                            "label": "Einsatzbereich",
                            "attributeId": "6a4d5e6f7a8b9c0d1e2f3055",
                            "value": "4471",
                            "display": "Innenausbau"
                          }
                        ]
                      }
                    ],
                    "createdAt": "2026-02-11T09:14:03.221Z",
                    "updatedAt": "2026-08-14T16:02:55.109Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid locale or classification scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Bad Request",
                  "message": "limit must be a positive integer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential was not granted `products`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "404": {
            "description": "No such product for this credential's tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Product not found"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/categories": {
      "get": {
        "tags": [
          "Reference data"
        ],
        "operationId": "listCategories",
        "summary": "List categories",
        "description": "Requires the `categories` grant.\n\nEach category carries its full ancestor `path`, so the tree can be rebuilt without loading the whole collection first.",
        "x-required-grant": "categories",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "description": "From the previous response's `nextCursor`. Opaque - do not construct one.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Values above 500 are clamped to 500.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Return only records changed at or after this instant. Combine with `cursor` to page a delta pull. Deletions are NOT reported here - poll `/deletions` as well, or deleted records linger in your system forever.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "description": "Language for resolved labels.",
            "schema": {
              "type": "string",
              "enum": [
                "deDE",
                "enUS"
              ],
              "default": "deDE"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Category"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass back as `?cursor=` for the next page. Null on the last page."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "6a3c4d5e6f7a8b9c0d1e2f30",
                      "code": "akkuschrauber",
                      "name": "Akkuschrauber",
                      "parentId": "6a3c4d5e6f7a8b9c0d1e2f2a",
                      "path": [
                        {
                          "id": "6a3c4d5e6f7a8b9c0d1e2f01",
                          "code": "werkzeug",
                          "name": "Werkzeug"
                        },
                        {
                          "id": "6a3c4d5e6f7a8b9c0d1e2f2a",
                          "code": "elektrowerkzeug",
                          "name": "Elektrowerkzeug"
                        }
                      ],
                      "updatedAt": "2026-07-30T11:20:41.882Z"
                    }
                  ],
                  "nextCursor": "MjAyNi0wOC0xNFQxNjowMjo1NS4xMDlafDZhN2RkNWRiN2RiZGUyNmVkM2IyYmNhZg"
                }
              }
            }
          },
          "400": {
            "description": "Invalid cursor, limit, date or locale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Bad Request",
                  "message": "limit must be a positive integer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential was not granted this object. Ask the tenant to add it - retrying will not help.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/categories/{categoryId}": {
      "get": {
        "tags": [
          "Reference data"
        ],
        "operationId": "getCategory",
        "summary": "Fetch one category",
        "description": "Requires the `categories` grant.\n\nField for field what the category looks like in the list, so a consumer resolving one id from a product does not have to page a collection to do it.\n\nAn id that is not a valid identifier answers `404` rather than `400`, so probing cannot distinguish a malformed id from one belonging to another tenant.",
        "x-required-grant": "categories",
        "parameters": [
          {
            "name": "categoryId",
            "in": "path",
            "required": true,
            "description": "The `id` as published, not your own identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "description": "Language for resolved labels.",
            "schema": {
              "type": "string",
              "enum": [
                "deDE",
                "enUS"
              ],
              "default": "deDE"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The category.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Category"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6a3c4d5e6f7a8b9c0d1e2f30",
                    "code": "akkuschrauber",
                    "name": "Akkuschrauber",
                    "parentId": "6a3c4d5e6f7a8b9c0d1e2f2a",
                    "path": [
                      {
                        "id": "6a3c4d5e6f7a8b9c0d1e2f01",
                        "code": "werkzeug",
                        "name": "Werkzeug"
                      },
                      {
                        "id": "6a3c4d5e6f7a8b9c0d1e2f2a",
                        "code": "elektrowerkzeug",
                        "name": "Elektrowerkzeug"
                      }
                    ],
                    "updatedAt": "2026-07-30T11:20:41.882Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid locale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Bad Request",
                  "message": "limit must be a positive integer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential was not granted `categories`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "404": {
            "description": "No such category for this credential's tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Product not found"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/attributes": {
      "get": {
        "tags": [
          "Reference data"
        ],
        "operationId": "listAttributes",
        "summary": "List attribute definitions",
        "description": "Requires the `attributes` grant. **Pull this before products.**\n\n`dataType` is what tells you how to read every product `value`, and the option lists let you map a value set before you touch the catalogue. An attribute reused across sets contributes its options once, de-duplicated by value.",
        "x-required-grant": "attributes",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "description": "From the previous response's `nextCursor`. Opaque - do not construct one.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Values above 500 are clamped to 500.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Return only records changed at or after this instant. Combine with `cursor` to page a delta pull. Deletions are NOT reported here - poll `/deletions` as well, or deleted records linger in your system forever.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "description": "Language for resolved labels.",
            "schema": {
              "type": "string",
              "enum": [
                "deDE",
                "enUS"
              ],
              "default": "deDE"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Attribute"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass back as `?cursor=` for the next page. Null on the last page."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "6a4d5e6f7a8b9c0d1e2f3041",
                      "code": "akkukapazitaet",
                      "name": "Akkukapazität",
                      "dataType": "select_single",
                      "options": [
                        {
                          "value": "2324",
                          "label": "2,0 Ah"
                        },
                        {
                          "value": "2325",
                          "label": "4,0 Ah"
                        }
                      ],
                      "updatedAt": "2026-06-02T08:11:19.004Z"
                    }
                  ],
                  "nextCursor": "MjAyNi0wOC0xNFQxNjowMjo1NS4xMDlafDZhN2RkNWRiN2RiZGUyNmVkM2IyYmNhZg"
                }
              }
            }
          },
          "400": {
            "description": "Invalid cursor, limit, date or locale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Bad Request",
                  "message": "limit must be a positive integer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential was not granted this object. Ask the tenant to add it - retrying will not help.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/attributes/{attributeId}": {
      "get": {
        "tags": [
          "Reference data"
        ],
        "operationId": "getAttribute",
        "summary": "Fetch one attribute definition",
        "description": "Requires the `attributes` grant.\n\nField for field what the attribute looks like in the list, so a consumer resolving one id from a product does not have to page a collection to do it.\n\nAn id that is not a valid identifier answers `404` rather than `400`, so probing cannot distinguish a malformed id from one belonging to another tenant.",
        "x-required-grant": "attributes",
        "parameters": [
          {
            "name": "attributeId",
            "in": "path",
            "required": true,
            "description": "The `id` as published, not your own identifier.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "locale",
            "in": "query",
            "description": "Language for resolved labels.",
            "schema": {
              "type": "string",
              "enum": [
                "deDE",
                "enUS"
              ],
              "default": "deDE"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The attribute.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Attribute"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6a4d5e6f7a8b9c0d1e2f3041",
                    "code": "akkukapazitaet",
                    "name": "Akkukapazität",
                    "dataType": "select_single",
                    "options": [
                      {
                        "value": "2324",
                        "label": "2,0 Ah"
                      },
                      {
                        "value": "2325",
                        "label": "4,0 Ah"
                      }
                    ],
                    "updatedAt": "2026-06-02T08:11:19.004Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid locale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Bad Request",
                  "message": "limit must be a positive integer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential was not granted `attributes`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "404": {
            "description": "No such attribute for this credential's tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Product not found"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/manufacturers": {
      "get": {
        "tags": [
          "Reference data"
        ],
        "operationId": "listManufacturers",
        "summary": "List manufacturers",
        "description": "Requires the `manufacturers` grant. Join to a product through `manufacturer.id`.",
        "x-required-grant": "manufacturers",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "description": "From the previous response's `nextCursor`. Opaque - do not construct one.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Values above 500 are clamped to 500.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Return only records changed at or after this instant. Combine with `cursor` to page a delta pull. Deletions are NOT reported here - poll `/deletions` as well, or deleted records linger in your system forever.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NamedRecord"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass back as `?cursor=` for the next page. Null on the last page."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "6a1f0c2d9a4b1e7f2c3d4e5f",
                      "name": "Bosch",
                      "updatedAt": "2026-05-19T07:45:12.640Z"
                    }
                  ],
                  "nextCursor": "MjAyNi0wOC0xNFQxNjowMjo1NS4xMDlafDZhN2RkNWRiN2RiZGUyNmVkM2IyYmNhZg"
                }
              }
            }
          },
          "400": {
            "description": "Invalid cursor, limit, date or locale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Bad Request",
                  "message": "limit must be a positive integer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential was not granted this object. Ask the tenant to add it - retrying will not help.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/manufacturers/{manufacturerId}": {
      "get": {
        "tags": [
          "Reference data"
        ],
        "operationId": "getManufacturer",
        "summary": "Fetch one manufacturer",
        "description": "Requires the `manufacturers` grant.\n\nField for field what the manufacturer looks like in the list, so a consumer resolving one id from a product does not have to page a collection to do it.\n\nAn id that is not a valid identifier answers `404` rather than `400`, so probing cannot distinguish a malformed id from one belonging to another tenant.",
        "x-required-grant": "manufacturers",
        "parameters": [
          {
            "name": "manufacturerId",
            "in": "path",
            "required": true,
            "description": "The `id` as published, not your own identifier.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The manufacturer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NamedRecord"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6a1f0c2d9a4b1e7f2c3d4e5f",
                    "name": "Bosch",
                    "updatedAt": "2026-05-19T07:45:12.640Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential was not granted `manufacturers`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "404": {
            "description": "No such manufacturer for this credential's tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Product not found"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/suppliers": {
      "get": {
        "tags": [
          "Reference data"
        ],
        "operationId": "listSuppliers",
        "summary": "List suppliers",
        "description": "Requires the `suppliers` grant. Join to a product through the `suppliers` array on it, which also carries that supplier's own article number.\n\nName and id only. Contact details are deliberately not part of this contract.",
        "x-required-grant": "suppliers",
        "parameters": [
          {
            "name": "cursor",
            "in": "query",
            "description": "From the previous response's `nextCursor`. Opaque - do not construct one.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Values above 500 are clamped to 500.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Return only records changed at or after this instant. Combine with `cursor` to page a delta pull. Deletions are NOT reported here - poll `/deletions` as well, or deleted records linger in your system forever.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NamedRecord"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass back as `?cursor=` for the next page. Null on the last page."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "6a2b3c4d5e6f7a8b9c0d1e2f",
                      "name": "Bosch Professional GmbH",
                      "updatedAt": "2026-04-02T13:31:58.114Z"
                    }
                  ],
                  "nextCursor": "MjAyNi0wOC0xNFQxNjowMjo1NS4xMDlafDZhN2RkNWRiN2RiZGUyNmVkM2IyYmNhZg"
                }
              }
            }
          },
          "400": {
            "description": "Invalid cursor, limit, date or locale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Bad Request",
                  "message": "limit must be a positive integer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential was not granted this object. Ask the tenant to add it - retrying will not help.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/suppliers/{supplierId}": {
      "get": {
        "tags": [
          "Reference data"
        ],
        "operationId": "getSupplier",
        "summary": "Fetch one supplier",
        "description": "Requires the `suppliers` grant.\n\nField for field what the supplier looks like in the list, so a consumer resolving one id from a product does not have to page a collection to do it.\n\nAn id that is not a valid identifier answers `404` rather than `400`, so probing cannot distinguish a malformed id from one belonging to another tenant.",
        "x-required-grant": "suppliers",
        "parameters": [
          {
            "name": "supplierId",
            "in": "path",
            "required": true,
            "description": "The `id` as published, not your own identifier.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The supplier.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NamedRecord"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6a1f0c2d9a4b1e7f2c3d4e60",
                    "name": "Bosch Professional GmbH",
                    "updatedAt": "2026-05-19T07:45:12.640Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential was not granted `suppliers`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "404": {
            "description": "No such supplier for this credential's tenant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Product not found"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    },
    "/deletions": {
      "get": {
        "tags": [
          "Sync"
        ],
        "operationId": "listDeletions",
        "summary": "Records that have been deleted",
        "description": "**Poll this alongside every delta pull.** `updatedSince` can only report records that still exist, so without this feed anything deleted here stays in your system forever.\n\nAuthorized per row rather than per route: it reports deletions of the resources your credential may read, and answers `403` only when it may read none of them.\n\nRecords are kept for 90 days. If you have been offline longer than that, start again from a full walk.",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "description": "Return deletions at or after this instant.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "resource",
            "in": "query",
            "description": "Restrict to one resource type.",
            "schema": {
              "type": "string",
              "enum": [
                "product",
                "category",
                "attribute",
                "manufacturer",
                "supplier"
              ]
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "From the previous response's `nextCursor`. Opaque - do not construct one.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size. Values above 500 are clamped to 500.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Deletion"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass back as `?cursor=` for the next page. Null on the last page."
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "6a7dd5db7dbde26ed3b2bcaf",
                      "resource": "product",
                      "deletedAt": "2026-08-15T09:12:44.771Z"
                    }
                  ],
                  "nextCursor": "MjAyNi0wOC0xNFQxNjowMjo1NS4xMDlafDZhN2RkNWRiN2RiZGUyNmVkM2IyYmNhZg"
                }
              }
            }
          },
          "400": {
            "description": "Invalid cursor, limit, date or locale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Bad Request",
                  "message": "limit must be a positive integer"
                }
              }
            }
          },
          "401": {
            "description": "Missing, malformed, revoked or expired credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Invalid API credential"
                }
              }
            }
          },
          "403": {
            "description": "The credential grants no readable deletable resource, or the requested resource is not one it may read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Forbidden"
                }
              }
            }
          },
          "429": {
            "description": "Over the request budget. Wait the number of seconds in `Retry-After`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "success": false,
                  "error": "Rate limit exceeded"
                }
              }
            }
          }
        }
      }
    }
  }
}