{
  "info": {
    "name": "cocoPIM · Public API v1 (REST + GraphQL)",
    "description": "Collection generated from the **cocoPIM OpenAPI specification** (`GET /api/v1/openapi.json`), itself produced from the code at API startup: the routes, parameters and schemas below cannot diverge from the implementation.\n\n## Authentication: a key, no sign-in\n\n**There is no authentication endpoint**: no sign-in page, no token exchange, no OAuth. The API key is the identity, presented on every call in `Authorization: Bearer cpk_live_<prefix>_<secret>` (`X-Api-Key: <key>` is tolerated). This collection sets that header at collection level: no request carries it on its own. The \"Start here\" folder contains a \"Check my API key\" request that names the exact cause of a refusal.\n\n## Get started in three minutes\n\n1. Import the environment file provided next to this collection, and select it at the top right.\n2. Create an API key in cocoPIM: **Developers → API keys**. Tick the scopes you need: `catalog:read` to read, `catalog:write` to create and edit, `catalog:delete` to delete.\n3. Paste the key into the `apiKey` variable of the environment. **It is shown only once, at creation**: if you lose it, you have to generate another one.\n4. Run \"List products\". You should receive a page of results.\n\n## What to know before you click\n\n**Authentication** is set at collection level (`Authorization: Bearer {{apiKey}}`): no request carries an authentication header of its own.\n\n**Idempotency is mandatory on creations.** Every `POST` carries an `Idempotency-Key` header set to `{{$guid}}`, which produces a fresh key on every send. To observe replay, replace it with a fixed value: same key and same body return the same response without creating anything a second time; same key and a different body return `idempotency_key_reused`.\n\n**Identifiers propagate by themselves.** A successful creation stores the obtained identifier in a collection variable (`productId`, `categoryId`…), which the following requests of the folder reuse. So chain create → read → edit → delete in order.\n\n**Pagination is cursor-based, never by page number.** Copy `meta.next_cursor` into the `cursor` parameter of the next call; `meta.has_more` tells whether anything is left.\n\n**Errors follow RFC 9457** (`application/problem+json`): read the `code` field, which is stable across versions, rather than the text of `detail`. The main ones: `invalid_api_key`, `api_key_expired`, `insufficient_scope`, `not_found`, `validation_failed`, `rate_limited`, `missing_idempotency_key`.\n\n## Limits to know\n\n- **20 writes per minute per key.** Beyond that: `429 rate_limited`. A bulk import must be spread out or go through the interface's file import.\n- **Monthly quota per key**, if you set one at creation.\n- **Pro pack or higher** for the REST API, **Growth or higher** for GraphQL. Below that: `403 PACK_FEATURE_REQUIRED`.\n\n## Variables to fill in\n\n`baseUrl` and `apiKey` are enough to read. For writes, also fill in `familyCode` (an existing family of your space, required to create a product), and depending on the folder `attributeCode`, `optionListCode`, `optionValueCode`.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "_postman_id": "b083c33a6b52a964603300c68ec589c9"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Safeguard: a call without a key returns a 401 that takes ten minutes",
          "// to diagnose. Better to say so right away, in the console.",
          "if (!pm.environment.get(\"apiKey\") && !pm.collectionVariables.get(\"apiKey\")) {",
          "  console.warn(",
          "    \"[cocoPIM] The apiKey variable is empty: select the environment and paste your key (Developers → API keys).\"",
          "  );",
          "}"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "familyCode",
      "value": "",
      "description": "Code of an existing family in your space. Required to create a product."
    },
    {
      "key": "attributeCode",
      "value": "",
      "description": "Code of an existing attribute."
    },
    {
      "key": "optionListCode",
      "value": "",
      "description": "Code of an existing option list."
    },
    {
      "key": "optionValueCode",
      "value": "",
      "description": "Code of an existing option value."
    },
    {
      "key": "productId",
      "value": "",
      "description": "Filled in automatically on creation."
    },
    {
      "key": "categoryId",
      "value": "",
      "description": "Filled in automatically on creation."
    },
    {
      "key": "attributeId",
      "value": "",
      "description": "Filled in automatically on creation."
    },
    {
      "key": "familyId",
      "value": "",
      "description": "Filled in automatically on creation."
    },
    {
      "key": "optionListId",
      "value": "",
      "description": "Filled in automatically on creation."
    }
  ],
  "item": [
    {
      "name": "Start here",
      "description": "**There is no authentication endpoint, and none is missing.**\n\nThe cocoPIM public API has no sign-in page, no token exchange and no OAuth flow: the API key **is** the identity. Present it on every call in the header `Authorization: Bearer cpk_live_<prefix>_<secret>` (the header `X-Api-Key: <key>` is tolerated, without the word `Bearer`). Nothing to renew, nothing to refresh.\n\nThis collection sets that header **at collection level**: it applies to every request, and none carries it on its own.\n\nDo not look for the 6-digit code received by email either: that is how **humans** sign in to the web interface, a separate system that gives no access to the API.\n\nThe key is created in cocoPIM under **Developers → API keys** and is shown **only once**, at creation. Store it in a password manager: if you lose it, you have to generate another one.",
      "item": [
        {
          "name": "Check my API key",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/products?limit=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "1"
                }
              ]
            },
            "description": "The smallest possible authenticated call: the equivalent of \"am I signed in?\". The attached test names the exact cause of a refusal, instead of leaving you with a silent 401."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const byCode = {",
                  "  invalid_api_key: \"Unknown or malformed key. Check the apiKey variable of the environment (format cpk_live_<prefix>_<secret>).\",",
                  "  api_key_expired: \"Expired key: its expiry date has passed. Generate a new one.\",",
                  "  insufficient_scope: \"Valid key, but without the catalog:read scope. Scopes are chosen when the key is CREATED and cannot be added afterwards.\",",
                  "  PACK_FEATURE_REQUIRED: \"Valid key, but the organization's pack does not open the public API (Pro pack minimum; Growth for GraphQL).\",",
                  "  rate_limited: \"Too many calls: wait for the window given by the Retry-After header.\",",
                  "  monthly_quota_exceeded: \"Monthly quota of this key exhausted.\",",
                  "};",
                  "",
                  "if (pm.response.code === 200) {",
                  "  pm.test(\"Valid key: the API answers\", () => {",
                  "    pm.expect(pm.response.json()).to.have.property(\"data\");",
                  "  });",
                  "} else {",
                  "  const problem = pm.response.json();",
                  "  const hint = byCode[problem.code] || problem.detail;",
                  "  pm.test(\"Refus : \" + problem.code, () => pm.expect.fail(hint));",
                  "}"
                ]
              }
            }
          ],
          "response": [],
          "id": "902d6ae8a45494de4fe2f1ad3f016ddf"
        },
        {
          "name": "OpenAPI specification (no key)",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/openapi.json",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "openapi.json"
              ]
            },
            "description": "Full contract, generated from the code at API startup, so it cannot diverge from the routes. The only public API route exempt from a key and a pack entitlement."
          },
          "response": [],
          "id": "d9ef0c7ba8867d228ddfe4ef7110df56"
        }
      ],
      "id": "b57e91c480d3f0a1f887d3ce659bdb03"
    },
    {
      "name": "Products",
      "description": "Read and write the product catalog. Attribute values live in `values`: a single model, no side columns.",
      "item": [
        {
          "name": "List products",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/products?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "products"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque cursor returned by the previous response.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Number of items per page. Default: 50, max: 100.",
                  "disabled": false
                },
                {
                  "key": "status",
                  "value": "",
                  "description": "Filtrer par statut produit.",
                  "disabled": true
                },
                {
                  "key": "familyCode",
                  "value": "",
                  "description": "Filter by family code.",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "Text search on SKU/name.",
                  "disabled": true
                },
                {
                  "key": "updatedAfter",
                  "value": "",
                  "description": "Returns only the products modified after this ISO 8601 date.",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "description": "List products\nCursor-only pagination. Required scopes: catalog:read.\nRequired scope: `catalog:read`."
          },
          "response": [],
          "id": "649497b8b1b92dabb7a01b5273130926"
        },
        {
          "name": "Get a product",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/products/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "products",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{productId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Get a product\nRequired scope: `catalog:read`."
          },
          "response": [],
          "id": "8756b4e37eed311e3a3106583c101243"
        },
        {
          "name": "Variants of a master product",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/products/:id/variants?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "products",
                ":id",
                "variants"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque cursor returned by the previous response.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Number of items per page. Default: 50, max: 100.",
                  "disabled": false
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "{{productId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Variants of a master product\nRequired scope: `catalog:read`."
          },
          "response": [],
          "id": "e7db6cfcb36912d043ede877d62f3215"
        },
        {
          "name": "Create a product",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Idempotency key (required)"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "products"
              ],
              "query": [],
              "variable": []
            },
            "description": "Create a product\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sku\": \"SKU-DEMO-001\",\n  \"familyCode\": \"{{familyCode}}\",\n  \"name\": \"Demo label\",\n  \"nameTranslations\": {\n    \"fr_FR\": \"Libellé de démonstration\"\n  },\n  \"status\": \"DRAFT\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"201 Created\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  if (body.id) {",
                  "    pm.collectionVariables.set(\"productId\", body.id);",
                  "    console.log(\"productId =\", body.id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "id": "58e08f1506aa68843225597a151954fb"
        },
        {
          "name": "Update a product",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/products/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "products",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{productId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Update a product\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Demo label\",\n  \"nameTranslations\": {\n    \"fr_FR\": \"Libellé de démonstration\"\n  },\n  \"status\": \"DRAFT\",\n  \"values\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "id": "cddec8194e5a19ed8120f556c63e424d"
        },
        {
          "name": "Delete a product",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/products/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "products",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{productId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Delete a product\nRequired scope: `catalog:delete`."
          },
          "response": [],
          "id": "ff5ed0332b1056f7bc87bd0142dc8546"
        }
      ],
      "id": "6949443b9eb9efa7282a2aaebf8020c5"
    },
    {
      "name": "Categories",
      "description": "Category tree. An empty `parentCode` means a root category.",
      "item": [
        {
          "name": "List categories",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/categories?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "categories"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque cursor returned by the previous response.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Number of items per page. Default: 50, max: 100.",
                  "disabled": false
                }
              ],
              "variable": []
            },
            "description": "List categories\nRequired scope: `catalog:read`."
          },
          "response": [],
          "id": "d8d9666b1ccfada29447f49241138bed"
        },
        {
          "name": "Create a category",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Idempotency key (required)"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "categories"
              ],
              "query": [],
              "variable": []
            },
            "description": "Create a category\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"demo-code\",\n  \"name\": \"Demo label\",\n  \"description\": \"value\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"201 Created\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  if (body.id) {",
                  "    pm.collectionVariables.set(\"categoryId\", body.id);",
                  "    console.log(\"categoryId =\", body.id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "id": "87e2db372dd002b1ba59c39799136d0f"
        },
        {
          "name": "Get a category",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/categories/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "categories",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{categoryId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Get a category\nRequired scope: `catalog:read`."
          },
          "response": [],
          "id": "8d2508158c255055c39519afddc54bb7"
        },
        {
          "name": "Update a category",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/categories/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "categories",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{categoryId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Update a category\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Demo label\",\n  \"description\": \"value\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "id": "c1de24fb45fb5b944f58577187ec52e9"
        },
        {
          "name": "Delete a category",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/categories/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "categories",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{categoryId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Delete a category\nRequired scope: `catalog:delete`."
          },
          "response": [],
          "id": "71e1ebb3a7471c6f74a06ba1f8bb51d8"
        }
      ],
      "id": "64f69709d9664feccbe8ff023c97b19a"
    },
    {
      "name": "Attributes",
      "description": "Attribute definitions (the schema), not their values. 13 types; EMAIL and URL are TEXT with a regular expression, never separate types.",
      "item": [
        {
          "name": "List attributes",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attributes?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attributes"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque cursor returned by the previous response.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Number of items per page. Default: 50, max: 100.",
                  "disabled": false
                }
              ],
              "variable": []
            },
            "description": "List attributes\nRequired scope: `catalog:read`."
          },
          "response": [],
          "id": "6532b7619d745944a8ad592632388044"
        },
        {
          "name": "Create an attribute",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Idempotency key (required)"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attributes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attributes"
              ],
              "query": [],
              "variable": []
            },
            "description": "Create an attribute\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"demo-code\",\n  \"name\": \"Demo label\",\n  \"groupCode\": \"value\",\n  \"type\": \"value\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"201 Created\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  if (body.id) {",
                  "    pm.collectionVariables.set(\"attributeId\", body.id);",
                  "    console.log(\"attributeId =\", body.id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "id": "42c55d826963c5764de39b9ace48c452"
        },
        {
          "name": "Update an attribute",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attributes/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attributes",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{attributeId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Update an attribute\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Demo label\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "id": "88704fe0281cc5027eba09a234baf5a9"
        },
        {
          "name": "Delete an attribute",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attributes/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attributes",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{attributeId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Delete an attribute\nRequired scope: `catalog:delete`."
          },
          "response": [],
          "id": "78d076142e7e22aa5dd56e525c63040f"
        }
      ],
      "id": "c2aa24fb9cc27b6ca3c42d76097c5116"
    },
    {
      "name": "Attribute families",
      "description": "Schema templates: a family groups the attributes a product carries. Family↔attribute links are managed here.",
      "item": [
        {
          "name": "List attribute families",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attribute-families?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attribute-families"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque cursor returned by the previous response.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Number of items per page. Default: 50, max: 100.",
                  "disabled": false
                }
              ],
              "variable": []
            },
            "description": "List attribute families\nRequired scope: `catalog:read`."
          },
          "response": [],
          "id": "08c711ade1ac703d3ac2e18ff1e34758"
        },
        {
          "name": "Create a family",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Idempotency key (required)"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attribute-families",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attribute-families"
              ],
              "query": [],
              "variable": []
            },
            "description": "Create a family\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"demo-code\",\n  \"name\": \"Demo label\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"201 Created\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  if (body.id) {",
                  "    pm.collectionVariables.set(\"familyId\", body.id);",
                  "    console.log(\"familyId =\", body.id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "id": "61970d80e4a850d471cfd38bfa1ea7c3"
        },
        {
          "name": "Update a family",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attribute-families/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attribute-families",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{familyId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Update a family\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Demo label\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "id": "925c2137b9106ae4627a9a8e161f2262"
        },
        {
          "name": "Delete a family",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attribute-families/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attribute-families",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{familyId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Delete a family\nRequired scope: `catalog:delete`."
          },
          "response": [],
          "id": "b80f4f22e46058d9828ea928b9cbe769"
        },
        {
          "name": "Assign an attribute to a family",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Idempotency key (required)"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attribute-families/:familyCode/attributes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attribute-families",
                ":familyCode",
                "attributes"
              ],
              "query": [],
              "variable": [
                {
                  "key": "familyCode",
                  "value": "{{familyCode}}",
                  "description": "Identifier used in the path (familyCode)."
                }
              ]
            },
            "description": "Assign an attribute to a family\nIdempotent: if the attribute is already assigned to the family, returns 200 (no-op) instead of creating a duplicate.\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"attributeCode\": \"{{attributeCode}}\",\n  \"labels\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"201 Created\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  if (body.id) {",
                  "    pm.collectionVariables.set(\"familyId\", body.id);",
                  "    console.log(\"familyId =\", body.id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "id": "b9ee5f3c53155c03243aaadedb102745"
        },
        {
          "name": "Update the labels of a family-attribute link",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attribute-families/:familyCode/attributes/:attributeCode",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attribute-families",
                ":familyCode",
                "attributes",
                ":attributeCode"
              ],
              "query": [],
              "variable": [
                {
                  "key": "familyCode",
                  "value": "{{familyCode}}",
                  "description": "Identifier used in the path (familyCode)."
                },
                {
                  "key": "attributeCode",
                  "value": "{{attributeCode}}",
                  "description": "Identifier used in the path (attributeCode)."
                }
              ]
            },
            "description": "Update the labels of a family-attribute link\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"labels\": {}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "id": "30184dc5f39ea8adb780e482e9300873"
        },
        {
          "name": "Remove an attribute from a family",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/attribute-families/:familyCode/attributes/:attributeCode",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "attribute-families",
                ":familyCode",
                "attributes",
                ":attributeCode"
              ],
              "query": [],
              "variable": [
                {
                  "key": "familyCode",
                  "value": "{{familyCode}}",
                  "description": "Identifier used in the path (familyCode)."
                },
                {
                  "key": "attributeCode",
                  "value": "{{attributeCode}}",
                  "description": "Identifier used in the path (attributeCode)."
                }
              ]
            },
            "description": "Remove an attribute from a family\nRequired scope: `catalog:delete`."
          },
          "response": [],
          "id": "faf41cd876bb377a72647a125cbed4bc"
        }
      ],
      "id": "3c2fa50e2d42ed73b068f0100eba3021"
    },
    {
      "name": "Option lists",
      "description": "Reusable value lists for SELECT and MULTI_SELECT attributes.",
      "item": [
        {
          "name": "List option lists (master only)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/option-lists?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "option-lists"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque cursor returned by the previous response.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Number of items per page. Default: 50, max: 100.",
                  "disabled": false
                }
              ],
              "variable": []
            },
            "description": "List option lists (master only)\nRequired scope: `catalog:read`."
          },
          "response": [],
          "id": "dd1aa4ea91b849bb009fe3946d81fb6a"
        },
        {
          "name": "Create an option list",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Idempotency key (required)"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/option-lists",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "option-lists"
              ],
              "query": [],
              "variable": []
            },
            "description": "Create an option list\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"demo-code\",\n  \"name\": \"Demo label\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"201 Created\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  if (body.id) {",
                  "    pm.collectionVariables.set(\"optionListId\", body.id);",
                  "    console.log(\"optionListId =\", body.id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "id": "90bc16509569c272fae2a6e6b5ad144f"
        },
        {
          "name": "Values of an option list by code",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/option-lists/:code/options?limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "option-lists",
                ":code",
                "options"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque cursor returned by the previous response.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Number of items per page. Default: 50, max: 100.",
                  "disabled": false
                }
              ],
              "variable": [
                {
                  "key": "code",
                  "value": "{{optionListCode}}",
                  "description": "Identifier used in the path (code)."
                }
              ]
            },
            "description": "Values of an option list by code\nRequired scope: `catalog:read`."
          },
          "response": [],
          "id": "8a88d2a522697f00dbf63116f11f1005"
        },
        {
          "name": "Update an option list",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/option-lists/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "option-lists",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{optionListId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Update an option list\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Demo label\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "id": "82449c220d497f4d07347d373885c8ab"
        },
        {
          "name": "Delete an option list",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/option-lists/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "option-lists",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "{{optionListId}}",
                  "description": "Identifier used in the path (id)."
                }
              ]
            },
            "description": "Delete an option list\nRequired scope: `catalog:delete`."
          },
          "response": [],
          "id": "6ad3f4b63b334794a02b733647d74f70"
        },
        {
          "name": "Add a value to an option list",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Idempotency key (required)"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/option-lists/:listCode/values",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "option-lists",
                ":listCode",
                "values"
              ],
              "query": [],
              "variable": [
                {
                  "key": "listCode",
                  "value": "{{optionListCode}}",
                  "description": "Identifier used in the path (listCode)."
                }
              ]
            },
            "description": "Add a value to an option list\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"demo-code\",\n  \"labels\": {},\n  \"color\": \"value\",\n  \"isActive\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test(\"201 Created\", () => pm.response.to.have.status(201));",
                  "if (pm.response.code === 201) {",
                  "  const body = pm.response.json();",
                  "  if (body.id) {",
                  "    pm.collectionVariables.set(\"optionListId\", body.id);",
                  "    console.log(\"optionListId =\", body.id);",
                  "  }",
                  "}"
                ]
              }
            }
          ],
          "id": "25754157c8e0de2c38994d30582d7152"
        },
        {
          "name": "Update a value (labels/colour/activation)",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/option-lists/:listCode/values/:valueCode",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "option-lists",
                ":listCode",
                "values",
                ":valueCode"
              ],
              "query": [],
              "variable": [
                {
                  "key": "listCode",
                  "value": "{{optionListCode}}",
                  "description": "Identifier used in the path (listCode)."
                },
                {
                  "key": "valueCode",
                  "value": "{{optionValueCode}}",
                  "description": "Identifier used in the path (valueCode)."
                }
              ]
            },
            "description": "Update a value (labels/colour/activation)\nRequired scope: `catalog:write`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"labels\": {},\n  \"color\": \"value\",\n  \"isActive\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "id": "9acd5d5afcaaec55ab4579c56a10f1e7"
        },
        {
          "name": "Delete a value",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/option-lists/:listCode/values/:valueCode",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "option-lists",
                ":listCode",
                "values",
                ":valueCode"
              ],
              "query": [],
              "variable": [
                {
                  "key": "listCode",
                  "value": "{{optionListCode}}",
                  "description": "Identifier used in the path (listCode)."
                },
                {
                  "key": "valueCode",
                  "value": "{{optionValueCode}}",
                  "description": "Identifier used in the path (valueCode)."
                }
              ]
            },
            "description": "Delete a value\nRequired scope: `catalog:delete`."
          },
          "response": [],
          "id": "df8b866cc2bea1d7f4f53ece94cfd18a"
        }
      ],
      "id": "93b7e465505dc3406a159fb767385381"
    },
    {
      "name": "GraphQL",
      "description": "Single entry point `POST /api/v1/graphql`, same API key and same monthly quota as REST.\n\n**Growth or Enterprise pack**: below that, the route answers `403 PACK_FEATURE_REQUIRED`.\n\n### Two error envelopes, not to be confused\n\nAn **execution** error (missing scope, validation, missing resource) arrives as **HTTP 200**, in `errors[].extensions.code`. A test that only checks for status 200 therefore validates nothing: the tests attached to each request also check `errors`.\n\nAn error **before execution** (syntax, depth, complexity, invalid key, quota) arrives with its **real HTTP status** and an `application/problem+json` body, without a `data`/`errors` envelope.\n\n### The safeguards that make an over-ambitious request fail\n\n- **Maximum depth 10**, **complexity budget ~1000 points**, 50,000 nodes, 30 fragments.\n- **One operation per document**, and **one root field per `mutation`** (a `query` can carry several). No batching.\n- A list field **without an explicit `limit` counts as 50** in the complexity calculation: two nested levels without a limit exceed the budget and are rejected before execution. Always give a `limit`.\n- Execution capped at 5 s, body at 100 KB, JSON depth at 32.\n\n### Idempotency\n\nSeven mutations require the `Idempotency-Key` header: the six `create*` and `assignFamilyAttribute`. `update*` and `delete*` do not need it.",
      "item": [
        {
          "name": "SDL schema (no key)",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/graphql/sdl",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "graphql",
                "sdl"
              ]
            },
            "description": "The plain schema, served without an API key: the GraphQL equivalent of the OpenAPI specification. It is the source of truth: if a request in this collection ever diverges, this file is right."
          },
          "response": [],
          "id": "a5ced9d4693173b8c1ec81fc5fe8985f"
        },
        {
          "name": "Reads",
          "description": "Scope `catalog:read`. Sept queries, plus l'introspection.",
          "item": [
            {
              "name": "Products: filterable list",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "query Products($limit: Int, $cursor: String, $status: String, $familyCode: String, $search: String, $updatedAfter: DateTime) {\n  products(limit: $limit, cursor: $cursor, status: $status, familyCode: $familyCode, search: $search, updatedAfter: $updatedAfter) {\n    nodes {\n      id\n      sku\n      name\n      nameTranslations\n      status\n      productType\n      familyCode\n      completenessScore\n      mainImage {\n        url\n        source\n      }\n      values\n      createdAt\n      updatedAt\n    }\n    pageInfo {\n      endCursor\n      hasNextPage\n    }\n    limit\n  }\n}",
                    "variables": "{\n  \"limit\": 20,\n  \"cursor\": null,\n  \"status\": null,\n  \"familyCode\": null,\n  \"search\": null,\n  \"updatedAfter\": null\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "All the filters of the REST list, in a single call. Pagination is cursor-based: copy `pageInfo.endCursor` into `cursor`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "63ba09f572638ed509093153faa75ee1"
            },
            {
              "name": "Product: full sheet (the point of GraphQL)",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "query Product($id: ID!, $variantLimit: Int) {\n  product(id: $id) {\n    id\n    sku\n    name\n    nameTranslations\n    status\n    productType\n    familyCode\n    familyLabels\n    completenessScore\n    mainAssetId\n    mainImage {\n      url\n      source\n    }\n    values\n    createdAt\n    updatedAt\n    family {\n      id\n      code\n      name\n      description\n      attributeCodes\n      attributes {\n        code\n        labels\n      }\n    }\n    variants(limit: $variantLimit) {\n      nodes {\n        id\n        sku\n        name\n        status\n      }\n      pageInfo {\n        endCursor\n        hasNextPage\n      }\n      limit\n    }\n  }\n}",
                    "variables": "{\n  \"id\": \"{{productId}}\",\n  \"variantLimit\": 10\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Product + image + family + attribute links + variants in ONE call, where REST needs three or four. This is the query that justifies GraphQL.\n\n`variants` is a connection: always give it an explicit `limit`; without one, the complexity calculator applies 50 and the request may be rejected before it even runs."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "7052276eda1570879569eb7b0fe05e01"
            },
            {
              "name": "Categories: list",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "query Categories($limit: Int, $cursor: String) {\n  categories(limit: $limit, cursor: $cursor) {\n    nodes {\n      id\n      code\n      name\n      nameTranslations\n      parentCode\n      description\n      sortOrder\n      isActive\n      createdAt\n      updatedAt\n    }\n    pageInfo {\n      endCursor\n      hasNextPage\n    }\n    limit\n  }\n}",
                    "variables": "{\n  \"limit\": 50,\n  \"cursor\": null\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": ""
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "942e25e7d0128e8c479fffe27e337a08"
            },
            {
              "name": "Category by identifier",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "query Category($id: ID!) {\n  category(id: $id) {\n    id\n    code\n    name\n    nameTranslations\n    parentCode\n    description\n    sortOrder\n    isActive\n    createdAt\n    updatedAt\n  }\n}",
                    "variables": "{\n  \"id\": \"{{categoryId}}\"\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": ""
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "35e8f1ba1bab868a43dc1c9a9f3871ff"
            },
            {
              "name": "Attributes: list",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "query Attributes($limit: Int, $cursor: String) {\n  attributes(limit: $limit, cursor: $cursor) {\n    nodes {\n      id\n      code\n      name\n      nameTranslations\n      type\n      config\n      isTranslatable\n      isScopable\n      isRequired\n      attributeGroupCode\n      createdAt\n      updatedAt\n    }\n    pageInfo {\n      endCursor\n      hasNextPage\n    }\n    limit\n  }\n}",
                    "variables": "{\n  \"limit\": 50,\n  \"cursor\": null\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "The attribute schema, not their values. `config` is a JSON scalar: its shape depends on the attribute `type`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "0a5cb7518a731101ce8c03ce8cfb1578"
            },
            {
              "name": "Families: list with their attribute links",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "query Families($limit: Int, $cursor: String) {\n  families(limit: $limit, cursor: $cursor) {\n    nodes {\n      id\n      code\n      name\n      nameTranslations\n      description\n      attributeCodes\n      attributes {\n        code\n        labels\n      }\n      createdAt\n      updatedAt\n    }\n    pageInfo {\n      endCursor\n      hasNextPage\n    }\n    limit\n  }\n}",
                    "variables": "{\n  \"limit\": 50,\n  \"cursor\": null\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "⚠️ `attributes` is a **plain list** (`[FamilyAttributeLink!]!`), not a connection: no `nodes`, no `pageInfo`, no `limit`. Each link only carries `code` and `labels`; for the type or the required flag of an attribute, query `attributes`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "df563692b3e2a2eb26b39aa4ce75d3d6"
            },
            {
              "name": "Option lists: with their values",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "query OptionLists($limit: Int, $cursor: String, $valueLimit: Int) {\n  optionLists(limit: $limit, cursor: $cursor) {\n    nodes {\n      id\n      code\n      name\n      nameTranslations\n      sortMode\n      values(limit: $valueLimit) {\n        nodes {\n          code\n          labels\n          color\n          icon\n          isActive\n          sortOrder\n        }\n        pageInfo {\n          endCursor\n          hasNextPage\n        }\n        limit\n      }\n      createdAt\n      updatedAt\n    }\n    pageInfo {\n      endCursor\n      hasNextPage\n    }\n    limit\n  }\n}",
                    "variables": "{\n  \"limit\": 5,\n  \"cursor\": null,\n  \"valueLimit\": 50\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "There is no singular `optionList` query: go through the list. `values` is a connection resolved in batches: only its `limit` is honoured, its `cursor` is ignored by the resolver, so do not build deep pagination on it."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "f88bd25608ed049b186f642b81662035"
            },
            {
              "name": "Schema introspection",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "query IntrospectTypes {\n  __schema {\n    queryType {\n      name\n    }\n    mutationType {\n      name\n    }\n    types {\n      name\n      kind\n    }\n  }\n}",
                    "variables": ""
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "What typed-client generators do. Requires a valid key, like everything else. Introspection depth is excluded from the cap of 10, but it counts in the complexity budget."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "2afc0dc225493776ea2042525638bc9b"
            }
          ],
          "id": "5125f7901b8534d85991bdf3500bf9b1"
        },
        {
          "name": "Writes",
          "description": "The 21 mutations of the schema. Scope `catalog:write`, except deletions and attribute detachment which require `catalog:delete`.",
          "item": [
            {
              "name": "Create a product",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Required on this mutation. {{$guid}} produces a fresh key on every send, which does NOT test idempotency: to observe it, replace it with a fixed value and send twice."
                  }
                ],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation CreateProduct($input: CreateProductInput!) {\n  createProduct(input: $input) {\n    id\n    sku\n  }\n}",
                    "variables": "{\n  \"input\": {\n    \"sku\": \"SKU-POSTMAN-001\",\n    \"familyCode\": \"{{familyCode}}\",\n    \"name\": \"Demo product\",\n    \"nameTranslations\": {\n      \"fr_FR\": \"Produit de démonstration\"\n    },\n    \"status\": \"DRAFT\"\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "The payload only returns `id` and `sku`: no mutation payload returns the full resource, a deliberate choice to avoid a systematic re-read.\n\n`CreateProductInput` **does not carry `values`**: create the product, then fill in its values with `updateProduct`.\nRequired scope: `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "20ba03e8cb5b445e3af329977a7c3351"
            },
            {
              "name": "Edit a product (and its values)",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation UpdateProduct($id: ID!, $input: UpdateProductInput!) {\n  updateProduct(id: $id, input: $input) {\n    id\n  }\n}",
                    "variables": "{\n  \"id\": \"{{productId}}\",\n  \"input\": {\n    \"name\": \"Demo product v2\",\n    \"status\": \"VALIDATED\",\n    \"values\": {\n      \"description\": [\n        {\n          \"locale\": \"en_GB\",\n          \"scope\": null,\n          \"value\": \"Demo description.\"\n        }\n      ]\n    }\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "`values` follows the single model `{ [code]: [{ locale, scope, value }] }`: `locale: null` for a non-translatable attribute, `scope: null` for a value shared by all channels.\n\nValues are validated server-side against the family schema: an invalid value returns `validation_failed` and **nothing is written**, the transaction is rolled back.\nRequired scope: `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "5e7f1836d94835f218f6641c3aefbc69"
            },
            {
              "name": "Delete a product",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation DeleteProduct($id: ID!) {\n  deleteProduct(id: $id)\n}",
                    "variables": "{\n  \"id\": \"{{productId}}\"\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Returns a bare `Boolean!`: no selection block. Scope `catalog:delete`, distinct from writing: a key that only creates and edits has no need for it.\nRequired scope: `catalog:delete`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "141dc5be3eb12c5f0e4f67190c4c702b"
            },
            {
              "name": "Create a category",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Required on this mutation. {{$guid}} produces a fresh key on every send, which does NOT test idempotency: to observe it, replace it with a fixed value and send twice."
                  }
                ],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation CreateCategory($input: CreateCategoryInput!) {\n  createCategory(input: $input) {\n    id\n    code\n  }\n}",
                    "variables": "{\n  \"input\": {\n    \"code\": \"cat-postman\",\n    \"name\": \"Demo category\",\n    \"parentCode\": null,\n    \"description\": \"Created from the Postman collection.\"\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "`parentCode` empty or absent means a root category.\nRequired scope: `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "d811f074c44a0f61f3fbdd5e273f9f92"
            },
            {
              "name": "Edit a category",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation UpdateCategory($id: ID!, $input: UpdateCategoryInput!) {\n  updateCategory(id: $id, input: $input) {\n    id\n  }\n}",
                    "variables": "{\n  \"id\": \"{{categoryId}}\",\n  \"input\": {\n    \"name\": \"Demo category v2\",\n    \"description\": \"Updated.\"\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "86dcf0b52be2c3eafcefeefe8083296b"
            },
            {
              "name": "Delete a category",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation DeleteCategory($id: ID!) {\n  deleteCategory(id: $id)\n}",
                    "variables": "{\n  \"id\": \"{{categoryId}}\"\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:delete`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "b7fa66d8a4acb50b272d94124c74c34f"
            },
            {
              "name": "Create an attribute",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Required on this mutation. {{$guid}} produces a fresh key on every send, which does NOT test idempotency: to observe it, replace it with a fixed value and send twice."
                  }
                ],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation CreateAttribute($input: CreateAttributeInput!) {\n  createAttribute(input: $input) {\n    id\n    code\n  }\n}",
                    "variables": "{\n  \"input\": {\n    \"code\": \"material\",\n    \"name\": \"Material\",\n    \"groupCode\": \"general\",\n    \"type\": \"TEXT\"\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "`groupCode` is **required**: an attribute always belongs to a group. `type` defaults to `TEXT`; EMAIL and URL do not exist as types, they are TEXT with a regular expression.\nRequired scope: `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "00e6b17956427cccfe0bec9f761650a0"
            },
            {
              "name": "Edit an attribute",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation UpdateAttribute($id: ID!, $input: UpdateAttributeInput!) {\n  updateAttribute(id: $id, input: $input) {\n    id\n  }\n}",
                    "variables": "{\n  \"id\": \"{{attributeId}}\",\n  \"input\": {\n    \"name\": \"Main material\"\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "559733e8184c7fe9d90657b19e0cb805"
            },
            {
              "name": "Delete an attribute",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation DeleteAttribute($id: ID!) {\n  deleteAttribute(id: $id)\n}",
                    "variables": "{\n  \"id\": \"{{attributeId}}\"\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:delete`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "b781cd3d27acf20215f46e1faec907b6"
            },
            {
              "name": "Create a family",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Required on this mutation. {{$guid}} produces a fresh key on every send, which does NOT test idempotency: to observe it, replace it with a fixed value and send twice."
                  }
                ],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation CreateFamily($input: CreateFamilyInput!) {\n  createFamily(input: $input) {\n    id\n    code\n  }\n}",
                    "variables": "{\n  \"input\": {\n    \"code\": \"demo-family\",\n    \"name\": \"Demo family\"\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "cebb84243761a116751e03f44eb11682"
            },
            {
              "name": "Edit a family",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation UpdateFamily($id: ID!, $input: UpdateFamilyInput!) {\n  updateFamily(id: $id, input: $input) {\n    id\n  }\n}",
                    "variables": "{\n  \"id\": \"{{familyId}}\",\n  \"input\": {\n    \"name\": \"Demo family v2\"\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "2f96fad35dfba9bb65f7a96e6a6635af"
            },
            {
              "name": "Delete a family",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation DeleteFamily($id: ID!) {\n  deleteFamily(id: $id)\n}",
                    "variables": "{\n  \"id\": \"{{familyId}}\"\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:delete`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "ce807f6f9b37c419bbfeab8d87100cfd"
            },
            {
              "name": "Link an attribute to a family",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Required on this mutation. {{$guid}} produces a fresh key on every send, which does NOT test idempotency: to observe it, replace it with a fixed value and send twice."
                  }
                ],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation AssignFamilyAttribute($familyCode: String!, $input: AssignAttributeInput!) {\n  assignFamilyAttribute(familyCode: $familyCode, input: $input) {\n    created\n    code\n    labels\n  }\n}",
                    "variables": "{\n  \"familyCode\": \"{{familyCode}}\",\n  \"input\": {\n    \"attributeCode\": \"{{attributeCode}}\",\n    \"labels\": {\n      \"en_US\": \"Material\",\n      \"fr_FR\": \"Matière\"\n    }\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Idempotent by nature: linking an already linked attribute again is not an error, the response carries `created: false`.\n\n`labels` is a JSON scalar: request it **without a selection block**, and send it as an object (60 keys maximum, each value ≤ 255 characters).\nRequired scope: `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "4e00e751d2e567544b4e5c87c78eb668"
            },
            {
              "name": "Edit the label of a family↔attribute link",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation UpdateFamilyAttributeLink($familyCode: String!, $attributeCode: String!, $input: UpdateFamilyAttributeInput!) {\n  updateFamilyAttributeLink(familyCode: $familyCode, attributeCode: $attributeCode, input: $input) {\n    code\n    labels\n  }\n}",
                    "variables": "{\n  \"familyCode\": \"{{familyCode}}\",\n  \"attributeCode\": \"{{attributeCode}}\",\n  \"input\": {\n    \"labels\": {\n      \"fr_FR\": \"Composition\"\n    }\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "The label of an attribute can differ from one family to another: that is exactly what this link allows.\nRequired scope: `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "2ef92431ec82ac14a1bb94d1b5a45fb4"
            },
            {
              "name": "Detach an attribute from a family",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation UnassignFamilyAttribute($familyCode: String!, $attributeCode: String!) {\n  unassignFamilyAttribute(familyCode: $familyCode, attributeCode: $attributeCode)\n}",
                    "variables": "{\n  \"familyCode\": \"{{familyCode}}\",\n  \"attributeCode\": \"{{attributeCode}}\"\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:delete`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "1afb8ebd49e04e7fea58cff6691c3772"
            },
            {
              "name": "Create an option list",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Required on this mutation. {{$guid}} produces a fresh key on every send, which does NOT test idempotency: to observe it, replace it with a fixed value and send twice."
                  }
                ],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation CreateOptionList($input: CreateOptionListInput!) {\n  createOptionList(input: $input) {\n    id\n    code\n  }\n}",
                    "variables": "{\n  \"input\": {\n    \"code\": \"couleurs\",\n    \"name\": \"Couleurs\"\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "f4877402679f8a42323e85efc96157a3"
            },
            {
              "name": "Edit an option list",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation UpdateOptionList($id: ID!, $input: UpdateOptionListInput!) {\n  updateOptionList(id: $id, input: $input) {\n    id\n  }\n}",
                    "variables": "{\n  \"id\": \"{{optionListId}}\",\n  \"input\": {\n    \"name\": \"Couleurs produit\"\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "34ab8c7bef02aca9ba6d030a75b08933"
            },
            {
              "name": "Delete an option list",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation DeleteOptionList($id: ID!) {\n  deleteOptionList(id: $id)\n}",
                    "variables": "{\n  \"id\": \"{{optionListId}}\"\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:delete`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "2b415b5ed25cd29f7402eb626c5d6949"
            },
            {
              "name": "Add a value to an option list",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Idempotency-Key",
                    "value": "{{$guid}}",
                    "description": "Required on this mutation. {{$guid}} produces a fresh key on every send, which does NOT test idempotency: to observe it, replace it with a fixed value and send twice."
                  }
                ],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation CreateOptionValue($listCode: String!, $input: CreateOptionValueInput!) {\n  createOptionValue(listCode: $listCode, input: $input) {\n    code\n    labels\n    color\n    isActive\n  }\n}",
                    "variables": "{\n  \"listCode\": \"{{optionListCode}}\",\n  \"input\": {\n    \"code\": \"rouge\",\n    \"labels\": {\n      \"fr_FR\": \"Rouge\",\n      \"en_US\": \"Red\"\n    },\n    \"color\": \"#E10600\",\n    \"isActive\": true\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "`labels` is **required** here (`JSON!`). A code already present in the list returns `option_value_code_exists` (409).\nRequired scope: `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "bab7642613bf495bf38e08bec6289a05"
            },
            {
              "name": "Edit an option value",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation UpdateOptionValue($listCode: String!, $valueCode: String!, $input: UpdateOptionValueInput!) {\n  updateOptionValue(listCode: $listCode, valueCode: $valueCode, input: $input) {\n    code\n    labels\n    color\n    isActive\n  }\n}",
                    "variables": "{\n  \"listCode\": \"{{optionListCode}}\",\n  \"valueCode\": \"{{optionValueCode}}\",\n  \"input\": {\n    \"labels\": {\n      \"fr_FR\": \"Rouge vif\"\n    },\n    \"color\": \"#FF0000\",\n    \"isActive\": true\n  }\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "A value used as a variant axis is locked: `option_value_in_use` (409).\nRequired scope: `catalog:write`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "5ee4c8cc726172f3479a6d239e1fcbbf"
            },
            {
              "name": "Delete an option value",
              "request": {
                "method": "POST",
                "header": [],
                "body": {
                  "mode": "graphql",
                  "graphql": {
                    "query": "mutation DeleteOptionValue($listCode: String!, $valueCode: String!) {\n  deleteOptionValue(listCode: $listCode, valueCode: $valueCode)\n}",
                    "variables": "{\n  \"listCode\": \"{{optionListCode}}\",\n  \"valueCode\": \"{{optionValueCode}}\"\n}"
                  }
                },
                "url": {
                  "raw": "{{baseUrl}}/api/v1/graphql",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "graphql"
                  ]
                },
                "description": "Scope requis : `catalog:delete`."
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// GraphQL returns 200 even when the operation fails: errors[] is what counts.",
                      "// A status other than 200 is an error BEFORE execution, in the format",
                      "// problem+json (syntax, depth, complexity, key, quota).",
                      "if (pm.response.code !== 200) {",
                      "  const problem = pm.response.json();",
                      "  pm.test(\"rejected before execution: \" + (problem.code || pm.response.code), () => {",
                      "    pm.expect.fail(problem.code + \": \" + problem.detail);",
                      "  });",
                      "} else {",
                      "  const body = pm.response.json();",
                      "  pm.test(\"no GraphQL error\", () => {",
                      "    if (body.errors) {",
                      "      // A graphql-js validation error (unknown field, missing argument)",
                      "      // has NO extensions.code: do not show \"undefined\" to the reader.",
                      "      const first = body.errors[0];",
                      "      const code = (first.extensions || {}).code || \"document validation error\";",
                      "      pm.expect.fail(code + \": \" + first.message);",
                      "    }",
                      "  });",
                      "",
                      "  // Propagate the created identifiers to the following requests.",
                      "  const d = body.data || {};",
                      "  const keep = {",
                      "    createProduct: \"productId\",",
                      "    createCategory: \"categoryId\",",
                      "    createAttribute: \"attributeId\",",
                      "    createFamily: \"familyId\",",
                      "    createOptionList: \"optionListId\",",
                      "  };",
                      "  for (const [op, variable] of Object.entries(keep)) {",
                      "    if (d[op] && d[op].id) {",
                      "      pm.collectionVariables.set(variable, d[op].id);",
                      "      console.log(variable + \" = \" + d[op].id);",
                      "    }",
                      "  }",
                      "  if (d.createOptionValue && d.createOptionValue.code) {",
                      "    pm.collectionVariables.set(\"optionValueCode\", d.createOptionValue.code);",
                      "  }",
                      "}"
                    ]
                  }
                }
              ],
              "id": "a9d05ad2f73cbde6ac284f944914eeb4"
            }
          ],
          "id": "4bbda3480e5c810db5ef56a43fb81b61"
        }
      ],
      "id": "b6266ddf93d62aa6f8c56f6735158b68"
    }
  ]
}