{
  "info": {
    "name": "myne Connect Brands API",
    "_postman_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "description": "Partner-facing Connect Brands API for `connect.myne.network`.\n\nDocs: https://docs.myne.network/api/brands/index.html\n\n**Setup**\n1. Import this collection and a companion environment.\n2. Set `client_id` and `client_secret` from **API Credentials** (brand admin).\n3. Call **Authorisation → Get credential scope** (`GET /v1/me`, collection Basic) or **Get an access token (this brand)** (`POST /v1/token`, `client_credentials`). Both save `brand_id` on success.\n4. For other brands: open the authorize URL in a browser, paste `authorization_code`, run **Get an access token (authorization code)**, then **Get credential scope with Bearer**. Use **Refresh an access token** before the hour is up.\n5. Other resource calls use collection Basic, or set Authorization to Bearer `{{access_token}}`.\n\nAuthorize for other brands is a browser flow (`https://app.myne.network/oauth/authorize`) and cannot run in Postman.\n\nToken errors: `{ \"error\", \"error_description\" }`. Resource success: `{ \"message\", \"response\" }`.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "basic",
    "basic": [
      {
        "key": "username",
        "value": "{{client_id}}",
        "type": "string"
      },
      {
        "key": "password",
        "value": "{{client_secret}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://connect.myne.network"
    },
    {
      "key": "brand_id",
      "value": ""
    },
    {
      "key": "client_id",
      "value": ""
    },
    {
      "key": "client_secret",
      "value": ""
    },
    {
      "key": "customer_id",
      "value": ""
    },
    {
      "key": "external_id",
      "value": "crm-contact-1001"
    },
    {
      "key": "location_id",
      "value": ""
    },
    {
      "key": "location_external_id",
      "value": ""
    },
    {
      "key": "product_id",
      "value": ""
    },
    {
      "key": "product_external_id",
      "value": ""
    },
    {
      "key": "promotion_id",
      "value": ""
    },
    {
      "key": "business_id",
      "value": ""
    },
    {
      "key": "transaction_id",
      "value": ""
    },
    {
      "key": "task_id",
      "value": ""
    },
    {
      "key": "group_id",
      "value": ""
    },
    {
      "key": "category_id",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "Authorisation",
      "item": [
        {
          "name": "Get credential scope and brand identity",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "me"
              ]
            },
            "description": "Confirm your API Credentials (HTTP Basic **or** Bearer from `POST /v1/token`) and discover `brand_id`, partner-safe brand display settings, **External source**, and—when this brand is in a Head office group—`brand.organisation` (role, Head office parent, sibling venues).\n\nCall this first when wiring a new integration. You do not pass `brand_id` on this path — the credential or access token selects the brand (creating brand for Basic / client_credentials; granted brand for authorization-code tokens).\n\nRead `response.brand.organisation` to understand multi-brand relationships. Paths on other routes stay scoped to `response.brand_id` — see **Brand** and **Organisation (Head office group)** in the glossary.\n\nSend `Authorization: Basic` **or** `Authorization: Bearer` on every request to the `connect.myne.network` `/v1` URL. Do not rely on following redirects for auth.\n\nSee glossary: Brand, Organisation (Head office group), API Credentials, External source.\n\nCollection auth is HTTP Basic. To call the same path with a token, use **Get credential scope with Bearer**."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code !== 200) { return; }",
                  "const json = pm.response.json();",
                  "const brandId = json.response && json.response.brand_id;",
                  "if (brandId != null) { pm.environment.set('brand_id', String(brandId)); }"
                ]
              }
            }
          ],
          "response": [
            {
              "name": "200 Credential scope plus partner-safe brand display settings. Call the connect.myne.network /v1 URL directly with Authorization (Basic or Bearer) on every request; do not depend on following redirects.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/me",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "me"
                  ]
                },
                "description": "Confirm your API Credentials (HTTP Basic **or** Bearer from `POST /v1/token`) and discover `brand_id`, partner-safe brand display settings, **External source**, and—when this brand is in a Head office group—`brand.organisation` (role, Head office parent, sibling venues).\n\nCall this first when wiring a new integration. You do not pass `brand_id` on this path — the credential or access token selects the brand (creating brand for Basic / client_credentials; granted brand for authorization-code tokens).\n\nRead `response.brand.organisation` to understand multi-brand relationships. Paths on other routes stay scoped to `response.brand_id` — see **Brand** and **Organisation (Head office group)** in the glossary.\n\nSend `Authorization: Basic` **or** `Authorization: Bearer` on every request to the `connect.myne.network` `/v1` URL. Do not rely on following redirects for auth.\n\nSee glossary: Brand, Organisation (Head office group), API Credentials, External source.\n\nCollection auth is HTTP Basic. To call the same path with a token, use **Get credential scope with Bearer**."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Authenticated successfully\",\n  \"response\": {\n    \"brand_id\": 42,\n    \"client_id\": \"myne_api_7f3a2b1c\",\n    \"credential_kind\": \"brand_basic_auth\",\n    \"label\": \"CRM sync\",\n    \"source\": \"crm-sync\",\n    \"brand\": {\n      \"id\": 42,\n      \"name\": \"Harbour Cafe Co\",\n      \"description\": \"Neighbourhood cafe group\",\n      \"website\": \"https://harbour.example.com\",\n      \"industry\": \"hospitality\",\n      \"background_url\": null,\n      \"light_logo_url\": \"https://assets.dev.myne.network/demo/light-logo.png\",\n      \"dark_logo_url\": \"https://assets.dev.myne.network/demo/dark-logo.png\",\n      \"square_logo_url\": \"https://assets.dev.myne.network/demo/square-logo.png\",\n      \"primary_color\": \"#1A1A1A\",\n      \"contrast_color\": \"#FFFFFF\",\n      \"text_color\": \"#1A1A1A\",\n      \"button_color\": \"#C45C26\",\n      \"primary_heading\": \"Welcome back\",\n      \"subheading\": \"Earn rewards every visit\",\n      \"call_to_action\": \"Join now\",\n      \"redirect_url\": \"https://harbour.example.com/account\",\n      \"legal_name\": \"Harbour Cafe Co Pty Ltd\",\n      \"contact_email\": \"hello@harbour.example.com\",\n      \"business_address\": \"1 Harbour St, Sydney NSW 2000\",\n      \"governing_state\": \"NSW\",\n      \"organisation\": {\n        \"id\": 42,\n        \"name\": \"Harbour Group\",\n        \"role\": \"venue\",\n        \"head_office_brand\": {\n          \"id\": 100,\n          \"name\": \"Harbour Head Office\"\n        },\n        \"sibling_brands\": [\n          {\n            \"id\": 101,\n            \"name\": \"Harbour Bondi\"\n          },\n          {\n            \"id\": 102,\n            \"name\": \"Harbour Newtown\"\n          }\n        ]\n      }\n    }\n  }\n}"
            }
          ]
        },
        {
          "name": "Get an access token (authorization code)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "token"
              ]
            },
            "description": "Browser authorize cannot run in Postman. Open `https://app.myne.network/oauth/authorize?client_id={{client_id}}&redirect_uri={{redirect_uri}}&response_type=code&state=YOUR_STATE` (use app.dev on development), Allow as a brand admin, then paste the `code` into `authorization_code`. `redirect_uri` must match a saved API Credentials URL exactly.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"grant_type\": \"authorization_code\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\",\n  \"code\": \"{{authorization_code}}\",\n  \"redirect_uri\": \"{{redirect_uri}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code !== 200) { return; }",
                  "const json = pm.response.json();",
                  "if (json.access_token) { pm.environment.set('access_token', json.access_token); }",
                  "if (json.brand_id != null) { pm.environment.set('brand_id', String(json.brand_id)); }",
                  "if (json.refresh_token) { pm.environment.set('refresh_token', json.refresh_token); }"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Refresh an access token",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "token"
              ]
            },
            "description": "Only authorization-code installs return a refresh token. The refresh token you send is rotated; other refresh tokens for the same brand stay valid. Saves `access_token`, `refresh_token`, and `brand_id` on HTTP 200.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"grant_type\": \"refresh_token\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\",\n  \"refresh_token\": \"{{refresh_token}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code !== 200) { return; }",
                  "const json = pm.response.json();",
                  "if (json.access_token) { pm.environment.set('access_token', json.access_token); }",
                  "if (json.brand_id != null) { pm.environment.set('brand_id', String(json.brand_id)); }",
                  "if (json.refresh_token) { pm.environment.set('refresh_token', json.refresh_token); }"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "Get credential scope with Bearer",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "me"
              ]
            },
            "description": "Same as Get credential scope, using `access_token` from a token request instead of collection Basic. `response.brand_id` is the creating brand (client_credentials) or the granted brand (authorization_code / refresh).",
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{access_token}}",
                  "type": "string"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code !== 200) { return; }",
                  "const json = pm.response.json();",
                  "const brandId = json.response && json.response.brand_id;",
                  "if (brandId != null) { pm.environment.set('brand_id', String(brandId)); }"
                ]
              }
            }
          ],
          "response": []
        }
      ]
    },
    {
      "name": "Brand",
      "item": [
        {
          "name": "Get brand bootstrap configuration",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/brand",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "brand"
              ]
            },
            "description": "Load partner-safe brand settings—display name, logos, colours, headings, contact fields, and organisation hierarchy when present.\n\nThis is the same brand object as `response.brand` on `GET …/me`. Use when you need brand config without credential metadata.\n\nWhen the brand is in a Head office group, inspect `organisation.role`, `organisation.head_office_brand`, and `organisation.sibling_brands`. Solo brands return `organisation: null`. Paths stay brand-scoped.\n\nReturns one brand object in `response` as a single-item array. Returns 404 when the brand is not found.\n\nSee glossary: Brand, Organisation (Head office group)."
          },
          "response": [
            {
              "name": "200 Brand bootstrap configuration. response contains exactly one ConnectBrand object.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/brand",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "brand"
                  ]
                },
                "description": "Load partner-safe brand settings—display name, logos, colours, headings, contact fields, and organisation hierarchy when present.\n\nThis is the same brand object as `response.brand` on `GET …/me`. Use when you need brand config without credential metadata.\n\nWhen the brand is in a Head office group, inspect `organisation.role`, `organisation.head_office_brand`, and `organisation.sibling_brands`. Solo brands return `organisation: null`. Paths stay brand-scoped.\n\nReturns one brand object in `response` as a single-item array. Returns 404 when the brand is not found.\n\nSee glossary: Brand, Organisation (Head office group)."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Brands fetched successfully\",\n  \"response\": [\n    {\n      \"id\": 42,\n      \"name\": \"Harbour Cafe Co\",\n      \"description\": \"Neighbourhood cafe group\",\n      \"website\": \"https://harbour.example.com\",\n      \"industry\": \"hospitality\",\n      \"background_url\": null,\n      \"light_logo_url\": \"https://assets.dev.myne.network/demo/light-logo.png\",\n      \"dark_logo_url\": \"https://assets.dev.myne.network/demo/dark-logo.png\",\n      \"square_logo_url\": \"https://assets.dev.myne.network/demo/square-logo.png\",\n      \"primary_color\": \"#1A1A1A\",\n      \"contrast_color\": \"#FFFFFF\",\n      \"text_color\": \"#1A1A1A\",\n      \"button_color\": \"#C45C26\",\n      \"primary_heading\": \"Welcome back\",\n      \"subheading\": \"Earn rewards every visit\",\n      \"call_to_action\": \"Join now\",\n      \"redirect_url\": \"https://harbour.example.com/account\",\n      \"legal_name\": \"Harbour Cafe Co Pty Ltd\",\n      \"contact_email\": \"hello@harbour.example.com\",\n      \"business_address\": \"1 Harbour St, Sydney NSW 2000\",\n      \"governing_state\": \"NSW\",\n      \"organisation\": {\n        \"id\": 42,\n        \"name\": \"Harbour Group\",\n        \"role\": \"venue\",\n        \"head_office_brand\": {\n          \"id\": 100,\n          \"name\": \"Harbour Head Office\"\n        },\n        \"sibling_brands\": [\n          {\n            \"id\": 101,\n            \"name\": \"Harbour Bondi\"\n          },\n          {\n            \"id\": 102,\n            \"name\": \"Harbour Newtown\"\n          }\n        ]\n      }\n    }\n  ]\n}"
            },
            {
              "name": "404 Brand not found.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/brand",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "brand"
                  ]
                },
                "description": "Load partner-safe brand settings—display name, logos, colours, headings, contact fields, and organisation hierarchy when present.\n\nThis is the same brand object as `response.brand` on `GET …/me`. Use when you need brand config without credential metadata.\n\nWhen the brand is in a Head office group, inspect `organisation.role`, `organisation.head_office_brand`, and `organisation.sibling_brands`. Solo brands return `organisation: null`. Paths stay brand-scoped.\n\nReturns one brand object in `response` as a single-item array. Returns 404 when the brand is not found.\n\nSee glossary: Brand, Organisation (Head office group)."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Brand not found\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Integrations",
      "item": [
        {
          "name": "List connected integrations for a brand",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/integrations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "integrations"
              ]
            },
            "description": "Return every app connected to the brand—the same integrations staff see in myne Settings.\n\nEach row includes `id`, `access_name`, `app_name`, `label`, `source`, `category`, and whether it matches your API credentials (`is_self`).\n\nSecrets, OAuth tokens, and raw integration payloads are never included.\n\nSee glossary: External source, API Credentials."
          },
          "response": [
            {
              "name": "200 Connected integrations for the brand. Secrets, OAuth tokens, and raw integration payloads are never included.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/integrations",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "integrations"
                  ]
                },
                "description": "Return every app connected to the brand—the same integrations staff see in myne Settings.\n\nEach row includes `id`, `access_name`, `app_name`, `label`, `source`, `category`, and whether it matches your API credentials (`is_self`).\n\nSecrets, OAuth tokens, and raw integration payloads are never included.\n\nSee glossary: External source, API Credentials."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Integrations fetched successfully\",\n  \"response\": [\n    {\n      \"id\": 42,\n      \"access_name\": \"monday\",\n      \"app_name\": \"monday\",\n      \"label\": \"Monday CRM\",\n      \"source\": \"monday-crm\",\n      \"state\": \"connected\",\n      \"category\": \"crm\",\n      \"is_self\": false\n    },\n    {\n      \"id\": 99,\n      \"access_name\": \"myne_api_abc123\",\n      \"app_name\": \"myne_api_abc123\",\n      \"label\": \"Partner sync\",\n      \"source\": \"partner-sync\",\n      \"state\": \"connected\",\n      \"category\": \"custom\",\n      \"is_self\": true\n    }\n  ]\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Location",
      "item": [
        {
          "name": "List locations for a brand",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/locations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "locations"
              ],
              "query": [
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Return every venue for the brand—the same rows staff see in myne.\n\nUse location IDs in customer browse filters, sync payloads, and promotion scoping.\n\nSee glossary: Location, Brand."
          },
          "response": [
            {
              "name": "200 All locations for the brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/locations",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "locations"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Return every venue for the brand—the same rows staff see in myne.\n\nUse location IDs in customer browse filters, sync payloads, and promotion scoping.\n\nSee glossary: Location, Brand."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Locations fetched successfully\",\n  \"response\": [\n    {\n      \"id\": 3,\n      \"brand_id\": 42,\n      \"name\": \"Harbour Cafe — Circular Quay\",\n      \"phone\": \"+61291234567\",\n      \"address\": \"1 Alfred St, Sydney NSW 2000\",\n      \"uuid\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n      \"valid\": true,\n      \"new_check_in_enabled\": true\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Get a location by id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/locations/{{location_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "locations",
                "{{location_id}}"
              ]
            },
            "description": "Load one venue by `location_id` for the brand.\n\nReturns partner-safe fields such as name, phone, address, uuid, valid, and `new_check_in_enabled`.\n\nUse after listing locations or when resolving a venue from sync payloads. Returns 404 when the ID is not in this brand.\n\nSee glossary: Location."
          },
          "response": [
            {
              "name": "200 Single venue for the brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/locations/{{location_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "locations",
                    "{{location_id}}"
                  ]
                },
                "description": "Load one venue by `location_id` for the brand.\n\nReturns partner-safe fields such as name, phone, address, uuid, valid, and `new_check_in_enabled`.\n\nUse after listing locations or when resolving a venue from sync payloads. Returns 404 when the ID is not in this brand.\n\nSee glossary: Location."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Location fetched successfully\",\n  \"response\": {\n    \"id\": 3,\n    \"brand_id\": 42,\n    \"name\": \"Harbour Cafe — Circular Quay\",\n    \"phone\": \"+61291234567\",\n    \"address\": \"1 Alfred St, Sydney NSW 2000\",\n    \"uuid\": \"a1b2c3d4-e5f6-7890-abcd-ef1234567890\",\n    \"valid\": true,\n    \"new_check_in_enabled\": true\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id or location_id.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/locations/{{location_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "locations",
                    "{{location_id}}"
                  ]
                },
                "description": "Load one venue by `location_id` for the brand.\n\nReturns partner-safe fields such as name, phone, address, uuid, valid, and `new_check_in_enabled`.\n\nUse after listing locations or when resolving a venue from sync payloads. Returns 404 when the ID is not in this brand.\n\nSee glossary: Location."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or location_id\"\n}"
            },
            {
              "name": "404 Location not found.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/locations/{{location_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "locations",
                    "{{location_id}}"
                  ]
                },
                "description": "Load one venue by `location_id` for the brand.\n\nReturns partner-safe fields such as name, phone, address, uuid, valid, and `new_check_in_enabled`.\n\nUse after listing locations or when resolving a venue from sync payloads. Returns 404 when the ID is not in this brand.\n\nSee glossary: Location."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Location not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/locations/{{location_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "locations",
                    "{{location_id}}"
                  ]
                },
                "description": "Load one venue by `location_id` for the brand.\n\nReturns partner-safe fields such as name, phone, address, uuid, valid, and `new_check_in_enabled`.\n\nUse after listing locations or when resolving a venue from sync payloads. Returns 404 when the ID is not in this brand.\n\nSee glossary: Location."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Customer",
      "item": [
        {
          "name": "Get a customer by external id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "by-external-id"
              ],
              "query": [
                {
                  "key": "external_id",
                  "value": "{{external_id}}",
                  "description": "Your integration's identifier for the customer.",
                  "disabled": false
                },
                {
                  "key": "source",
                  "value": "",
                  "description": "Origin system for the external_id. When omitted, defaults to your Custom API app name (Integrations label). Read-only filter for cross-integration lookups.",
                  "disabled": true
                }
              ]
            },
            "description": "Resolve a myne customer from your CRM or PMS `external_id` without scanning browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (the label shown in Integrations)—the same source stamped when you synced or upserted the contact. Pass `source` explicitly only when you need IDs synced under another integration.\n\nReturns the customer with connected-system identities (`extended[]`), pass id, Brand Points, myne cashback, lifetime stats, and loyalty join status (`connection_type` — `connected` when the customer has joined loyalty, `known` when not, `unknown` for an anonymous website visitor), or 404 when no match exists for this brand. `connection_type` matches the customer list and the get-customer-by-id endpoint. Identities are not a single top-level source/external_id. Cashback here is the internal cashback ledger, not a live Wrapped or Giftit credit GET.\n\nEvaluate/apply use this same external_id path when you omit `customer_id`, but they do not load identities or loyalty.\n\nSee glossary: Customer, external_id and external_data, External source, Credit, Points."
          },
          "response": [
            {
              "name": "200 Customer, connected-system identities, loyalty balances, and lifetime stats.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Your integration's identifier for the customer.",
                      "disabled": false
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Origin system for the external_id. When omitted, defaults to your Custom API app name (Integrations label). Read-only filter for cross-integration lookups.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Resolve a myne customer from your CRM or PMS `external_id` without scanning browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (the label shown in Integrations)—the same source stamped when you synced or upserted the contact. Pass `source` explicitly only when you need IDs synced under another integration.\n\nReturns the customer with connected-system identities (`extended[]`), pass id, Brand Points, myne cashback, lifetime stats, and loyalty join status (`connection_type` — `connected` when the customer has joined loyalty, `known` when not, `unknown` for an anonymous website visitor), or 404 when no match exists for this brand. `connection_type` matches the customer list and the get-customer-by-id endpoint. Identities are not a single top-level source/external_id. Cashback here is the internal cashback ledger, not a live Wrapped or Giftit credit GET.\n\nEvaluate/apply use this same external_id path when you omit `customer_id`, but they do not load identities or loyalty.\n\nSee glossary: Customer, external_id and external_data, External source, Credit, Points."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer fetched successfully\",\n  \"response\": {\n    \"customer\": {\n      \"id\": 104823,\n      \"brand_id\": 42,\n      \"location_id\": 3,\n      \"phone\": \"+61412345678\",\n      \"email\": \"jordan.lee@example.com\",\n      \"created_at\": \"2025-02-15T01:23:45.000Z\",\n      \"updated_at\": \"2026-06-21T03:15:00.000Z\",\n      \"accepts_marketing\": true,\n      \"first_name\": \"Jordan\",\n      \"last_name\": \"Lee\",\n      \"archived_at\": null,\n      \"frequency\": \"Frequent\",\n      \"engagement\": \"engaged\",\n      \"connection_type\": \"connected\",\n      \"pass_id\": \"ABC123XYZ\",\n      \"points\": 120,\n      \"cashback\": 18.5\n    },\n    \"stats\": {\n      \"transaction_count\": 17,\n      \"total_spent\": 542.5,\n      \"last_transaction_date\": \"2026-06-21T03:15:00.000Z\"\n    },\n    \"extended\": [\n      {\n        \"source\": \"lightspeed\",\n        \"external_id\": \"ext-abc123\",\n        \"json_data\": {\n          \"pos_id\": \"LS-98765\"\n        },\n        \"updated_at\": \"2026-06-21T03:15:00.000Z\"\n      },\n      {\n        \"source\": \"monday-crm\",\n        \"external_id\": \"crm-001\",\n        \"json_data\": {},\n        \"updated_at\": \"2026-05-01T00:00:00.000Z\"\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 external_id query parameter is missing.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Your integration's identifier for the customer.",
                      "disabled": false
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Origin system for the external_id. When omitted, defaults to your Custom API app name (Integrations label). Read-only filter for cross-integration lookups.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Resolve a myne customer from your CRM or PMS `external_id` without scanning browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (the label shown in Integrations)—the same source stamped when you synced or upserted the contact. Pass `source` explicitly only when you need IDs synced under another integration.\n\nReturns the customer with connected-system identities (`extended[]`), pass id, Brand Points, myne cashback, lifetime stats, and loyalty join status (`connection_type` — `connected` when the customer has joined loyalty, `known` when not, `unknown` for an anonymous website visitor), or 404 when no match exists for this brand. `connection_type` matches the customer list and the get-customer-by-id endpoint. Identities are not a single top-level source/external_id. Cashback here is the internal cashback ledger, not a live Wrapped or Giftit credit GET.\n\nEvaluate/apply use this same external_id path when you omit `customer_id`, but they do not load identities or loyalty.\n\nSee glossary: Customer, external_id and external_data, External source, Credit, Points."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"external_id query parameter is required\"\n}"
            },
            {
              "name": "404 No customer matches the external id for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Your integration's identifier for the customer.",
                      "disabled": false
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Origin system for the external_id. When omitted, defaults to your Custom API app name (Integrations label). Read-only filter for cross-integration lookups.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Resolve a myne customer from your CRM or PMS `external_id` without scanning browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (the label shown in Integrations)—the same source stamped when you synced or upserted the contact. Pass `source` explicitly only when you need IDs synced under another integration.\n\nReturns the customer with connected-system identities (`extended[]`), pass id, Brand Points, myne cashback, lifetime stats, and loyalty join status (`connection_type` — `connected` when the customer has joined loyalty, `known` when not, `unknown` for an anonymous website visitor), or 404 when no match exists for this brand. `connection_type` matches the customer list and the get-customer-by-id endpoint. Identities are not a single top-level source/external_id. Cashback here is the internal cashback ledger, not a live Wrapped or Giftit credit GET.\n\nEvaluate/apply use this same external_id path when you omit `customer_id`, but they do not load identities or loyalty.\n\nSee glossary: Customer, external_id and external_data, External source, Credit, Points."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            }
          ]
        },
        {
          "name": "Upsert extended customer data by external id",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/extended",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "by-external-id",
                "extended"
              ]
            },
            "description": "Merge JSON extended fields for a customer identified by `external_id`.\n\nUse this when you want partner metadata or alternate source ids stored under your Custom API app without relying only on top-level email/phone/name. Data is stored under your app source only—do not send `source` in the body.\n\nIf no customer exists for that `external_id` yet, one is created. You may optionally pass email, phone, first_name, and last_name on create.\n\nSee glossary: Customer, Extended profile, External source, external_id and external_data.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"data\": {\n    \"preferred_venue\": \"Sydney CBD\",\n    \"notes\": \"VIP\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Extended data merged for the customer under your API app source. Creates the customer when missing.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id",
                    "extended"
                  ]
                },
                "description": "Merge JSON extended fields for a customer identified by `external_id`.\n\nUse this when you want partner metadata or alternate source ids stored under your Custom API app without relying only on top-level email/phone/name. Data is stored under your app source only—do not send `source` in the body.\n\nIf no customer exists for that `external_id` yet, one is created. You may optionally pass email, phone, first_name, and last_name on create.\n\nSee glossary: Customer, Extended profile, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"data\": {\n    \"preferred_venue\": \"Sydney CBD\",\n    \"notes\": \"VIP\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer extended data upserted successfully\",\n  \"response\": {\n    \"customer_id\": 104823,\n    \"external_id\": \"crm-001\",\n    \"source\": \"monday-crm\",\n    \"created_customer\": true\n  }\n}"
            },
            {
              "name": "400 Invalid JSON body or missing external_id/data.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id",
                    "extended"
                  ]
                },
                "description": "Merge JSON extended fields for a customer identified by `external_id`.\n\nUse this when you want partner metadata or alternate source ids stored under your Custom API app without relying only on top-level email/phone/name. Data is stored under your app source only—do not send `source` in the body.\n\nIf no customer exists for that `external_id` yet, one is created. You may optionally pass email, phone, first_name, and last_name on create.\n\nSee glossary: Customer, Extended profile, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"data\": {\n    \"preferred_venue\": \"Sydney CBD\",\n    \"notes\": \"VIP\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"data must be a JSON object\"\n}"
            },
            {
              "name": "403 Request included source (or contact_source / business_source)—these are set from your API credentials.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id",
                    "extended"
                  ]
                },
                "description": "Merge JSON extended fields for a customer identified by `external_id`.\n\nUse this when you want partner metadata or alternate source ids stored under your Custom API app without relying only on top-level email/phone/name. Data is stored under your app source only—do not send `source` in the body.\n\nIf no customer exists for that `external_id` yet, one is created. You may optionally pass email, phone, first_name, and last_name on create.\n\nSee glossary: Customer, Extended profile, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"data\": {\n    \"preferred_venue\": \"Sydney CBD\",\n    \"notes\": \"VIP\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"source is managed by your API credentials and cannot be set on write requests\"\n}"
            },
            {
              "name": "404 No customer matches the external id for this brand and source.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id",
                    "extended"
                  ]
                },
                "description": "Merge JSON extended fields for a customer identified by `external_id`.\n\nUse this when you want partner metadata or alternate source ids stored under your Custom API app without relying only on top-level email/phone/name. Data is stored under your app source only—do not send `source` in the body.\n\nIf no customer exists for that `external_id` yet, one is created. You may optionally pass email, phone, first_name, and last_name on create.\n\nSee glossary: Customer, Extended profile, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"data\": {\n    \"preferred_venue\": \"Sydney CBD\",\n    \"notes\": \"VIP\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id",
                    "extended"
                  ]
                },
                "description": "Merge JSON extended fields for a customer identified by `external_id`.\n\nUse this when you want partner metadata or alternate source ids stored under your Custom API app without relying only on top-level email/phone/name. Data is stored under your app source only—do not send `source` in the body.\n\nIf no customer exists for that `external_id` yet, one is created. You may optionally pass email, phone, first_name, and last_name on create.\n\nSee glossary: Customer, Extended profile, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"data\": {\n    \"preferred_venue\": \"Sydney CBD\",\n    \"notes\": \"VIP\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "List declared contact-extended properties for your source",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/extended/properties",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "extended",
                "properties"
              ]
            },
            "description": "Return the top-level contact-extended fields your app has declared for this brand under your **External source**.\n\nThis is the partner-declared catalog only (not staff-added extras). Values still live in `customer_extended.data` via extended upsert; declaring properties does not validate or strip undeclared keys on write.\n\nSee glossary: Customer, Extended profile, External source."
          },
          "response": [
            {
              "name": "200 Partner-declared contact-extended property catalog for your External source.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/extended/properties",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "extended",
                    "properties"
                  ]
                },
                "description": "Return the top-level contact-extended fields your app has declared for this brand under your **External source**.\n\nThis is the partner-declared catalog only (not staff-added extras). Values still live in `customer_extended.data` via extended upsert; declaring properties does not validate or strip undeclared keys on write.\n\nSee glossary: Customer, Extended profile, External source."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer extended properties fetched successfully\",\n  \"response\": {\n    \"source\": \"pocketpass\",\n    \"properties\": [\n      {\n        \"key\": \"pocketpass_link\",\n        \"type\": \"link\",\n        \"label\": \"Pocketpass link\"\n      },\n      {\n        \"key\": \"pocketpass_installed\",\n        \"type\": \"boolean\",\n        \"label\": \"Pocketpass installed\"\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/extended/properties",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "extended",
                    "properties"
                  ]
                },
                "description": "Return the top-level contact-extended fields your app has declared for this brand under your **External source**.\n\nThis is the partner-declared catalog only (not staff-added extras). Values still live in `customer_extended.data` via extended upsert; declaring properties does not validate or strip undeclared keys on write.\n\nSee glossary: Customer, Extended profile, External source."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id\"\n}"
            },
            {
              "name": "403 Path brand_id does not match the credential brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/extended/properties",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "extended",
                    "properties"
                  ]
                },
                "description": "Return the top-level contact-extended fields your app has declared for this brand under your **External source**.\n\nThis is the partner-declared catalog only (not staff-added extras). Values still live in `customer_extended.data` via extended upsert; declaring properties does not validate or strip undeclared keys on write.\n\nSee glossary: Customer, Extended profile, External source."
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"brand_id does not match authenticated brand\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/extended/properties",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "extended",
                    "properties"
                  ]
                },
                "description": "Return the top-level contact-extended fields your app has declared for this brand under your **External source**.\n\nThis is the partner-declared catalog only (not staff-added extras). Values still live in `customer_extended.data` via extended upsert; declaring properties does not validate or strip undeclared keys on write.\n\nSee glossary: Customer, Extended profile, External source."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Replace declared contact-extended properties for your source",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/extended/properties",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "extended",
                "properties"
              ]
            },
            "description": "Full-replace the top-level contact-extended fields your app stands behind for this brand.\n\nSend `properties` as an array of `{ key, type, label?, description? }` (max 50). Types: `string`, `number`, `boolean`, `link`, `date`. Keys must match `^[a-z][a-z0-9_]*$` (max 64) and the top-level JSON keys you write on extended upsert. Empty `properties: []` clears your partner-declared catalog for this source.\n\nSource is always your Custom API app—do not send `source`. This does not change how extended upsert merges JSON.\n\nSee glossary: Customer, Extended profile, External source.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"properties\": [\n    {\n      \"key\": \"pocketpass_link\",\n      \"type\": \"link\",\n      \"label\": \"Pocketpass link\",\n      \"description\": \"URL to the member portal\"\n    },\n    {\n      \"key\": \"pocketpass_installed\",\n      \"type\": \"boolean\",\n      \"label\": \"Pocketpass installed\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Partner-declared catalog replaced for your External source.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/extended/properties",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "extended",
                    "properties"
                  ]
                },
                "description": "Full-replace the top-level contact-extended fields your app stands behind for this brand.\n\nSend `properties` as an array of `{ key, type, label?, description? }` (max 50). Types: `string`, `number`, `boolean`, `link`, `date`. Keys must match `^[a-z][a-z0-9_]*$` (max 64) and the top-level JSON keys you write on extended upsert. Empty `properties: []` clears your partner-declared catalog for this source.\n\nSource is always your Custom API app—do not send `source`. This does not change how extended upsert merges JSON.\n\nSee glossary: Customer, Extended profile, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"properties\": [\n    {\n      \"key\": \"pocketpass_link\",\n      \"type\": \"link\",\n      \"label\": \"Pocketpass link\",\n      \"description\": \"URL to the member portal\"\n    },\n    {\n      \"key\": \"pocketpass_installed\",\n      \"type\": \"boolean\",\n      \"label\": \"Pocketpass installed\"\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer extended properties replaced successfully\",\n  \"response\": {\n    \"source\": \"pocketpass\",\n    \"properties\": [\n      {\n        \"key\": \"pocketpass_link\",\n        \"type\": \"link\",\n        \"label\": \"Pocketpass link\"\n      },\n      {\n        \"key\": \"pocketpass_installed\",\n        \"type\": \"boolean\",\n        \"label\": \"Pocketpass installed\"\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id, invalid JSON body, unknown top-level fields, missing properties, or invalid key/type/count.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/extended/properties",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "extended",
                    "properties"
                  ]
                },
                "description": "Full-replace the top-level contact-extended fields your app stands behind for this brand.\n\nSend `properties` as an array of `{ key, type, label?, description? }` (max 50). Types: `string`, `number`, `boolean`, `link`, `date`. Keys must match `^[a-z][a-z0-9_]*$` (max 64) and the top-level JSON keys you write on extended upsert. Empty `properties: []` clears your partner-declared catalog for this source.\n\nSource is always your Custom API app—do not send `source`. This does not change how extended upsert merges JSON.\n\nSee glossary: Customer, Extended profile, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"properties\": [\n    {\n      \"key\": \"pocketpass_link\",\n      \"type\": \"link\",\n      \"label\": \"Pocketpass link\",\n      \"description\": \"URL to the member portal\"\n    },\n    {\n      \"key\": \"pocketpass_installed\",\n      \"type\": \"boolean\",\n      \"label\": \"Pocketpass installed\"\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Request body may only include properties\"\n}"
            },
            {
              "name": "403 Path brand_id mismatch, or request included source (managed by credentials).",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/extended/properties",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "extended",
                    "properties"
                  ]
                },
                "description": "Full-replace the top-level contact-extended fields your app stands behind for this brand.\n\nSend `properties` as an array of `{ key, type, label?, description? }` (max 50). Types: `string`, `number`, `boolean`, `link`, `date`. Keys must match `^[a-z][a-z0-9_]*$` (max 64) and the top-level JSON keys you write on extended upsert. Empty `properties: []` clears your partner-declared catalog for this source.\n\nSource is always your Custom API app—do not send `source`. This does not change how extended upsert merges JSON.\n\nSee glossary: Customer, Extended profile, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"properties\": [\n    {\n      \"key\": \"pocketpass_link\",\n      \"type\": \"link\",\n      \"label\": \"Pocketpass link\",\n      \"description\": \"URL to the member portal\"\n    },\n    {\n      \"key\": \"pocketpass_installed\",\n      \"type\": \"boolean\",\n      \"label\": \"Pocketpass installed\"\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"source is managed by your API credentials and cannot be set on write requests\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/extended/properties",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "extended",
                    "properties"
                  ]
                },
                "description": "Full-replace the top-level contact-extended fields your app stands behind for this brand.\n\nSend `properties` as an array of `{ key, type, label?, description? }` (max 50). Types: `string`, `number`, `boolean`, `link`, `date`. Keys must match `^[a-z][a-z0-9_]*$` (max 64) and the top-level JSON keys you write on extended upsert. Empty `properties: []` clears your partner-declared catalog for this source.\n\nSource is always your Custom API app—do not send `source`. This does not change how extended upsert merges JSON.\n\nSee glossary: Customer, Extended profile, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"properties\": [\n    {\n      \"key\": \"pocketpass_link\",\n      \"type\": \"link\",\n      \"label\": \"Pocketpass link\",\n      \"description\": \"URL to the member portal\"\n    },\n    {\n      \"key\": \"pocketpass_installed\",\n      \"type\": \"boolean\",\n      \"label\": \"Pocketpass installed\"\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Upsert a customer by external id",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers"
              ]
            },
            "description": "Create or update a customer from your website, CRM, or PMS using `external_id`.\n\nSend top-level profile fields when you have them (email, phone, name). Source is always your Custom API app—do not send `source`.\n\nSend `connection_type: \"connected\"` when the person is already a loyalty member in your app — myne marks them as joined (requires an email or phone on the request, or one already stored for the customer). Omitting it, or sending `known` / `unknown`, never changes their connection status.\n\nThat `external_id` is what you use later for promotion evaluate/apply and by-external-id lookups under your **External source**. For additional partner ids or opaque metadata, also write extended data (`POST …/customers/by-external-id/extended` or sync `contact_extended`).\n\nPrefer this for single registrations; use **Sync batch** for bulk imports.\n\nSee glossary: Customer, external_id and external_data, Sync batch.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"phone\": \"+61400000000\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"traits\": {\n    \"loyalty_tier\": \"gold\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Customer created or updated under your External source.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers"
                  ]
                },
                "description": "Create or update a customer from your website, CRM, or PMS using `external_id`.\n\nSend top-level profile fields when you have them (email, phone, name). Source is always your Custom API app—do not send `source`.\n\nSend `connection_type: \"connected\"` when the person is already a loyalty member in your app — myne marks them as joined (requires an email or phone on the request, or one already stored for the customer). Omitting it, or sending `known` / `unknown`, never changes their connection status.\n\nThat `external_id` is what you use later for promotion evaluate/apply and by-external-id lookups under your **External source**. For additional partner ids or opaque metadata, also write extended data (`POST …/customers/by-external-id/extended` or sync `contact_extended`).\n\nPrefer this for single registrations; use **Sync batch** for bulk imports.\n\nSee glossary: Customer, external_id and external_data, Sync batch.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"phone\": \"+61400000000\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"traits\": {\n    \"loyalty_tier\": \"gold\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer upserted successfully\",\n  \"response\": {\n    \"customer_id\": 104823,\n    \"external_id\": \"web-user-001\",\n    \"source\": \"monday-crm\",\n    \"created\": true\n  }\n}"
            },
            {
              "name": "400 Invalid JSON, missing external_id, or connection_type=connected with no resolvable email or phone.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers"
                  ]
                },
                "description": "Create or update a customer from your website, CRM, or PMS using `external_id`.\n\nSend top-level profile fields when you have them (email, phone, name). Source is always your Custom API app—do not send `source`.\n\nSend `connection_type: \"connected\"` when the person is already a loyalty member in your app — myne marks them as joined (requires an email or phone on the request, or one already stored for the customer). Omitting it, or sending `known` / `unknown`, never changes their connection status.\n\nThat `external_id` is what you use later for promotion evaluate/apply and by-external-id lookups under your **External source**. For additional partner ids or opaque metadata, also write extended data (`POST …/customers/by-external-id/extended` or sync `contact_extended`).\n\nPrefer this for single registrations; use **Sync batch** for bulk imports.\n\nSee glossary: Customer, external_id and external_data, Sync batch.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"phone\": \"+61400000000\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"traits\": {\n    \"loyalty_tier\": \"gold\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"external_id (or user_id) is required\"\n}"
            },
            {
              "name": "403 Request included a source field (not allowed on writes).",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers"
                  ]
                },
                "description": "Create or update a customer from your website, CRM, or PMS using `external_id`.\n\nSend top-level profile fields when you have them (email, phone, name). Source is always your Custom API app—do not send `source`.\n\nSend `connection_type: \"connected\"` when the person is already a loyalty member in your app — myne marks them as joined (requires an email or phone on the request, or one already stored for the customer). Omitting it, or sending `known` / `unknown`, never changes their connection status.\n\nThat `external_id` is what you use later for promotion evaluate/apply and by-external-id lookups under your **External source**. For additional partner ids or opaque metadata, also write extended data (`POST …/customers/by-external-id/extended` or sync `contact_extended`).\n\nPrefer this for single registrations; use **Sync batch** for bulk imports.\n\nSee glossary: Customer, external_id and external_data, Sync batch.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"phone\": \"+61400000000\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"traits\": {\n    \"loyalty_tier\": \"gold\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"source is managed by your API credentials and cannot be set on write requests\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers"
                  ]
                },
                "description": "Create or update a customer from your website, CRM, or PMS using `external_id`.\n\nSend top-level profile fields when you have them (email, phone, name). Source is always your Custom API app—do not send `source`.\n\nSend `connection_type: \"connected\"` when the person is already a loyalty member in your app — myne marks them as joined (requires an email or phone on the request, or one already stored for the customer). Omitting it, or sending `known` / `unknown`, never changes their connection status.\n\nThat `external_id` is what you use later for promotion evaluate/apply and by-external-id lookups under your **External source**. For additional partner ids or opaque metadata, also write extended data (`POST …/customers/by-external-id/extended` or sync `contact_extended`).\n\nPrefer this for single registrations; use **Sync batch** for bulk imports.\n\nSee glossary: Customer, external_id and external_data, Sync batch.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"email\": \"alex@example.com\",\n  \"phone\": \"+61400000000\",\n  \"first_name\": \"Alex\",\n  \"last_name\": \"Nguyen\",\n  \"traits\": {\n    \"loyalty_tier\": \"gold\"\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Browse customers for a brand",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "15",
                  "description": "Page size. Clamped to 1–100.",
                  "disabled": false
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Opaque pagination cursor returned as `response.next_cursor` from the previous page. Omit for the first page.",
                  "disabled": true
                },
                {
                  "key": "query",
                  "value": "",
                  "description": "Free-text search across name, email, phone, and wallet/credit identifiers.",
                  "disabled": true
                },
                {
                  "key": "include_visitors",
                  "value": "false",
                  "description": "Accepts `true`, `1`, or `yes`. When truthy, include unidentified visitor records; otherwise only known customers are returned (default).",
                  "disabled": true
                },
                {
                  "key": "sort_by",
                  "value": "total_spent",
                  "description": "Sort key for ranking customers.",
                  "disabled": true
                },
                {
                  "key": "sort_direction",
                  "value": "desc",
                  "description": "Sort direction.",
                  "disabled": true
                },
                {
                  "key": "locations",
                  "value": "",
                  "description": "Comma-separated business/location IDs to restrict the browse to (e.g. `12,34`).",
                  "disabled": true
                },
                {
                  "key": "connection",
                  "value": "",
                  "description": "Filter by connection state.",
                  "disabled": true
                },
                {
                  "key": "frequency",
                  "value": "",
                  "description": "Filter by brand frequency bucket.",
                  "disabled": true
                },
                {
                  "key": "engagement",
                  "value": "",
                  "description": "Filter by engagement classification when available.",
                  "disabled": true
                },
                {
                  "key": "customer_group_id",
                  "value": "",
                  "description": "Restrict browse to members of one customer group.",
                  "disabled": true
                },
                {
                  "key": "search_fields",
                  "value": "",
                  "description": "Comma-separated search field keys to limit free-text `query` matching.",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only customers with updated_at on or after this instant are returned (compared in UTC).",
                  "disabled": true
                }
              ]
            },
            "description": "Search and page through customers for a brand. Use `cursor` from `response.next_cursor` for the next page.\n\nFilter by location, **Connection state**, or **Frequency**. Set `include_visitors=true` to include unidentified **Visitor** records.\n\nPass `updated_since` (ISO-8601, compared in UTC) to poll only customers changed on or after that timestamp.\n\nTypical first step before loading a single profile or extended timeline.\n\nSee glossary: Customer, Visitor, Connection state, Frequency, Engagement."
          },
          "response": [
            {
              "name": "200 A page of customers. `response.next_cursor` is null when there are no more pages.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "Page size. Clamped to 1–100.",
                      "disabled": false
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "Opaque pagination cursor returned as `response.next_cursor` from the previous page. Omit for the first page.",
                      "disabled": true
                    },
                    {
                      "key": "query",
                      "value": "",
                      "description": "Free-text search across name, email, phone, and wallet/credit identifiers.",
                      "disabled": true
                    },
                    {
                      "key": "include_visitors",
                      "value": "false",
                      "description": "Accepts `true`, `1`, or `yes`. When truthy, include unidentified visitor records; otherwise only known customers are returned (default).",
                      "disabled": true
                    },
                    {
                      "key": "sort_by",
                      "value": "total_spent",
                      "description": "Sort key for ranking customers.",
                      "disabled": true
                    },
                    {
                      "key": "sort_direction",
                      "value": "desc",
                      "description": "Sort direction.",
                      "disabled": true
                    },
                    {
                      "key": "locations",
                      "value": "",
                      "description": "Comma-separated business/location IDs to restrict the browse to (e.g. `12,34`).",
                      "disabled": true
                    },
                    {
                      "key": "connection",
                      "value": "",
                      "description": "Filter by connection state.",
                      "disabled": true
                    },
                    {
                      "key": "frequency",
                      "value": "",
                      "description": "Filter by brand frequency bucket.",
                      "disabled": true
                    },
                    {
                      "key": "engagement",
                      "value": "",
                      "description": "Filter by engagement classification when available.",
                      "disabled": true
                    },
                    {
                      "key": "customer_group_id",
                      "value": "",
                      "description": "Restrict browse to members of one customer group.",
                      "disabled": true
                    },
                    {
                      "key": "search_fields",
                      "value": "",
                      "description": "Comma-separated search field keys to limit free-text `query` matching.",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only customers with updated_at on or after this instant are returned (compared in UTC).",
                      "disabled": true
                    }
                  ]
                },
                "description": "Search and page through customers for a brand. Use `cursor` from `response.next_cursor` for the next page.\n\nFilter by location, **Connection state**, or **Frequency**. Set `include_visitors=true` to include unidentified **Visitor** records.\n\nPass `updated_since` (ISO-8601, compared in UTC) to poll only customers changed on or after that timestamp.\n\nTypical first step before loading a single profile or extended timeline.\n\nSee glossary: Customer, Visitor, Connection state, Frequency, Engagement."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer records fetched successfully (known only)\",\n  \"response\": {\n    \"data\": [\n      {\n        \"id\": 104823,\n        \"external_data\": null,\n        \"first_name\": \"Jordan\",\n        \"last_name\": \"Lee\",\n        \"email\": \"jordan.lee@example.com\",\n        \"phone\": \"+61412345678\",\n        \"location_id\": null,\n        \"brand_id\": 42,\n        \"uuid\": \"c0b1f7a4-2e3d-4f5a-9b6c-7d8e9f0a1b2c\",\n        \"predicted_name\": null,\n        \"type\": \"customer\",\n        \"transaction_count\": 17,\n        \"total_spent\": 542.5,\n        \"tenure\": 412,\n        \"latest_transaction\": \"2026-06-21T03:15:00.000Z\",\n        \"last_seen\": \"2026-06-21T03:15:00.000Z\",\n        \"frequency\": \"Frequent\",\n        \"engagement\": \"engaged\",\n        \"connection_type\": \"connected\"\n      }\n    ],\n    \"has_more\": true,\n    \"next_cursor\": \"1|0|2026-06-21T03:15:00.000Z|104823\",\n    \"total_count\": 1287\n  }\n}"
            },
            {
              "name": "400 brand_id is missing or not a positive integer.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "Page size. Clamped to 1–100.",
                      "disabled": false
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "Opaque pagination cursor returned as `response.next_cursor` from the previous page. Omit for the first page.",
                      "disabled": true
                    },
                    {
                      "key": "query",
                      "value": "",
                      "description": "Free-text search across name, email, phone, and wallet/credit identifiers.",
                      "disabled": true
                    },
                    {
                      "key": "include_visitors",
                      "value": "false",
                      "description": "Accepts `true`, `1`, or `yes`. When truthy, include unidentified visitor records; otherwise only known customers are returned (default).",
                      "disabled": true
                    },
                    {
                      "key": "sort_by",
                      "value": "total_spent",
                      "description": "Sort key for ranking customers.",
                      "disabled": true
                    },
                    {
                      "key": "sort_direction",
                      "value": "desc",
                      "description": "Sort direction.",
                      "disabled": true
                    },
                    {
                      "key": "locations",
                      "value": "",
                      "description": "Comma-separated business/location IDs to restrict the browse to (e.g. `12,34`).",
                      "disabled": true
                    },
                    {
                      "key": "connection",
                      "value": "",
                      "description": "Filter by connection state.",
                      "disabled": true
                    },
                    {
                      "key": "frequency",
                      "value": "",
                      "description": "Filter by brand frequency bucket.",
                      "disabled": true
                    },
                    {
                      "key": "engagement",
                      "value": "",
                      "description": "Filter by engagement classification when available.",
                      "disabled": true
                    },
                    {
                      "key": "customer_group_id",
                      "value": "",
                      "description": "Restrict browse to members of one customer group.",
                      "disabled": true
                    },
                    {
                      "key": "search_fields",
                      "value": "",
                      "description": "Comma-separated search field keys to limit free-text `query` matching.",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only customers with updated_at on or after this instant are returned (compared in UTC).",
                      "disabled": true
                    }
                  ]
                },
                "description": "Search and page through customers for a brand. Use `cursor` from `response.next_cursor` for the next page.\n\nFilter by location, **Connection state**, or **Frequency**. Set `include_visitors=true` to include unidentified **Visitor** records.\n\nPass `updated_since` (ISO-8601, compared in UTC) to poll only customers changed on or after that timestamp.\n\nTypical first step before loading a single profile or extended timeline.\n\nSee glossary: Customer, Visitor, Connection state, Frequency, Engagement."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id\"\n}"
            },
            {
              "name": "500 Unexpected server error. Details are logged server-side; the body is a generic message.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "Page size. Clamped to 1–100.",
                      "disabled": false
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "Opaque pagination cursor returned as `response.next_cursor` from the previous page. Omit for the first page.",
                      "disabled": true
                    },
                    {
                      "key": "query",
                      "value": "",
                      "description": "Free-text search across name, email, phone, and wallet/credit identifiers.",
                      "disabled": true
                    },
                    {
                      "key": "include_visitors",
                      "value": "false",
                      "description": "Accepts `true`, `1`, or `yes`. When truthy, include unidentified visitor records; otherwise only known customers are returned (default).",
                      "disabled": true
                    },
                    {
                      "key": "sort_by",
                      "value": "total_spent",
                      "description": "Sort key for ranking customers.",
                      "disabled": true
                    },
                    {
                      "key": "sort_direction",
                      "value": "desc",
                      "description": "Sort direction.",
                      "disabled": true
                    },
                    {
                      "key": "locations",
                      "value": "",
                      "description": "Comma-separated business/location IDs to restrict the browse to (e.g. `12,34`).",
                      "disabled": true
                    },
                    {
                      "key": "connection",
                      "value": "",
                      "description": "Filter by connection state.",
                      "disabled": true
                    },
                    {
                      "key": "frequency",
                      "value": "",
                      "description": "Filter by brand frequency bucket.",
                      "disabled": true
                    },
                    {
                      "key": "engagement",
                      "value": "",
                      "description": "Filter by engagement classification when available.",
                      "disabled": true
                    },
                    {
                      "key": "customer_group_id",
                      "value": "",
                      "description": "Restrict browse to members of one customer group.",
                      "disabled": true
                    },
                    {
                      "key": "search_fields",
                      "value": "",
                      "description": "Comma-separated search field keys to limit free-text `query` matching.",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only customers with updated_at on or after this instant are returned (compared in UTC).",
                      "disabled": true
                    }
                  ]
                },
                "description": "Search and page through customers for a brand. Use `cursor` from `response.next_cursor` for the next page.\n\nFilter by location, **Connection state**, or **Frequency**. Set `include_visitors=true` to include unidentified **Visitor** records.\n\nPass `updated_since` (ISO-8601, compared in UTC) to poll only customers changed on or after that timestamp.\n\nTypical first step before loading a single profile or extended timeline.\n\nSee glossary: Customer, Visitor, Connection state, Frequency, Engagement."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            },
            {
              "name": "503 The underlying browse query timed out. Narrow the search (e.g. add `query`, `locations`, or reduce `limit`) and retry.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "Page size. Clamped to 1–100.",
                      "disabled": false
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "Opaque pagination cursor returned as `response.next_cursor` from the previous page. Omit for the first page.",
                      "disabled": true
                    },
                    {
                      "key": "query",
                      "value": "",
                      "description": "Free-text search across name, email, phone, and wallet/credit identifiers.",
                      "disabled": true
                    },
                    {
                      "key": "include_visitors",
                      "value": "false",
                      "description": "Accepts `true`, `1`, or `yes`. When truthy, include unidentified visitor records; otherwise only known customers are returned (default).",
                      "disabled": true
                    },
                    {
                      "key": "sort_by",
                      "value": "total_spent",
                      "description": "Sort key for ranking customers.",
                      "disabled": true
                    },
                    {
                      "key": "sort_direction",
                      "value": "desc",
                      "description": "Sort direction.",
                      "disabled": true
                    },
                    {
                      "key": "locations",
                      "value": "",
                      "description": "Comma-separated business/location IDs to restrict the browse to (e.g. `12,34`).",
                      "disabled": true
                    },
                    {
                      "key": "connection",
                      "value": "",
                      "description": "Filter by connection state.",
                      "disabled": true
                    },
                    {
                      "key": "frequency",
                      "value": "",
                      "description": "Filter by brand frequency bucket.",
                      "disabled": true
                    },
                    {
                      "key": "engagement",
                      "value": "",
                      "description": "Filter by engagement classification when available.",
                      "disabled": true
                    },
                    {
                      "key": "customer_group_id",
                      "value": "",
                      "description": "Restrict browse to members of one customer group.",
                      "disabled": true
                    },
                    {
                      "key": "search_fields",
                      "value": "",
                      "description": "Comma-separated search field keys to limit free-text `query` matching.",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only customers with updated_at on or after this instant are returned (compared in UTC).",
                      "disabled": true
                    }
                  ]
                },
                "description": "Search and page through customers for a brand. Use `cursor` from `response.next_cursor` for the next page.\n\nFilter by location, **Connection state**, or **Frequency**. Set `include_visitors=true` to include unidentified **Visitor** records.\n\nPass `updated_since` (ISO-8601, compared in UTC) to poll only customers changed on or after that timestamp.\n\nTypical first step before loading a single profile or extended timeline.\n\nSee glossary: Customer, Visitor, Connection state, Frequency, Engagement."
              },
              "status": "Error",
              "code": 503,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Service temporarily unavailable. Please narrow your search or try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Record a customer event",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/events",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "events"
              ]
            },
            "description": "Append an event to the customer timeline (for example a form submission or in-app action).\n\nRequires `event_name`. Optional: `event_data`, `event_time`, `location_id`, and `external_id` for idempotency.\n\nEvents are stamped with your **External source** from credentials. This is not the same as recording a CRM **Activity** (notes and calls).\n\nSee glossary: Activity, External source.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Event recorded on the customer timeline.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/events",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "events"
                  ]
                },
                "description": "Append an event to the customer timeline (for example a form submission or in-app action).\n\nRequires `event_name`. Optional: `event_data`, `event_time`, `location_id`, and `external_id` for idempotency.\n\nEvents are stamped with your **External source** from credentials. This is not the same as recording a CRM **Activity** (notes and calls).\n\nSee glossary: Activity, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer event recorded successfully\",\n  \"response\": {\n    \"id\": 9001,\n    \"event_name\": \"form_submitted\",\n    \"event_time\": \"2026-07-10T08:00:00.000Z\",\n    \"source\": \"monday-crm\",\n    \"external_id\": \"evt-abc\"\n  }\n}"
            },
            {
              "name": "400 Invalid JSON or missing event_name.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/events",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "events"
                  ]
                },
                "description": "Append an event to the customer timeline (for example a form submission or in-app action).\n\nRequires `event_name`. Optional: `event_data`, `event_time`, `location_id`, and `external_id` for idempotency.\n\nEvents are stamped with your **External source** from credentials. This is not the same as recording a CRM **Activity** (notes and calls).\n\nSee glossary: Activity, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"event_name is required\"\n}"
            },
            {
              "name": "403 Request included a source field (not allowed on writes).",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/events",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "events"
                  ]
                },
                "description": "Append an event to the customer timeline (for example a form submission or in-app action).\n\nRequires `event_name`. Optional: `event_data`, `event_time`, `location_id`, and `external_id` for idempotency.\n\nEvents are stamped with your **External source** from credentials. This is not the same as recording a CRM **Activity** (notes and calls).\n\nSee glossary: Activity, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"source is managed by your API credentials and cannot be set on write requests\"\n}"
            },
            {
              "name": "404 Customer not found for this brand.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/events",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "events"
                  ]
                },
                "description": "Append an event to the customer timeline (for example a form submission or in-app action).\n\nRequires `event_name`. Optional: `event_data`, `event_time`, `location_id`, and `external_id` for idempotency.\n\nEvents are stamped with your **External source** from credentials. This is not the same as recording a CRM **Activity** (notes and calls).\n\nSee glossary: Activity, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/events",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "events"
                  ]
                },
                "description": "Append an event to the customer timeline (for example a form submission or in-app action).\n\nRequires `event_name`. Optional: `event_data`, `event_time`, `location_id`, and `external_id` for idempotency.\n\nEvents are stamped with your **External source** from credentials. This is not the same as recording a CRM **Activity** (notes and calls).\n\nSee glossary: Activity, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Record a customer event by external id",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/events",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "by-external-id",
                "events"
              ],
              "query": [
                {
                  "key": "external_id",
                  "value": "{{external_id}}",
                  "description": "Contact external_id. Prefer the body field; query is accepted for convenience.",
                  "disabled": false
                }
              ]
            },
            "description": "Same as recording a customer event, but resolve the person with your website `external_id` instead of `customer_id`.\n\nPass `external_id` (contact) plus `event_name`. Use `event_external_id` for the event’s own id.\n\nLookup uses your credential **External source**.\n\nSee glossary: external_id and external_data, External source, Activity.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Event recorded; response includes resolved customer_id.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/events",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id",
                    "events"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Contact external_id. Prefer the body field; query is accepted for convenience.",
                      "disabled": false
                    }
                  ]
                },
                "description": "Same as recording a customer event, but resolve the person with your website `external_id` instead of `customer_id`.\n\nPass `external_id` (contact) plus `event_name`. Use `event_external_id` for the event’s own id.\n\nLookup uses your credential **External source**.\n\nSee glossary: external_id and external_data, External source, Activity.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer event recorded successfully\",\n  \"response\": {\n    \"id\": 9001,\n    \"customer_id\": 104823,\n    \"event_name\": \"form_submitted\",\n    \"event_time\": \"2026-07-10T08:00:00.000Z\",\n    \"source\": \"monday-crm\",\n    \"external_id\": \"evt-abc\"\n  }\n}"
            },
            {
              "name": "400 Invalid JSON or missing external_id / event_name.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/events",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id",
                    "events"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Contact external_id. Prefer the body field; query is accepted for convenience.",
                      "disabled": false
                    }
                  ]
                },
                "description": "Same as recording a customer event, but resolve the person with your website `external_id` instead of `customer_id`.\n\nPass `external_id` (contact) plus `event_name`. Use `event_external_id` for the event’s own id.\n\nLookup uses your credential **External source**.\n\nSee glossary: external_id and external_data, External source, Activity.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"event_name is required\"\n}"
            },
            {
              "name": "403 Request included a source field (not allowed on writes).",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/events",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id",
                    "events"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Contact external_id. Prefer the body field; query is accepted for convenience.",
                      "disabled": false
                    }
                  ]
                },
                "description": "Same as recording a customer event, but resolve the person with your website `external_id` instead of `customer_id`.\n\nPass `external_id` (contact) plus `event_name`. Use `event_external_id` for the event’s own id.\n\nLookup uses your credential **External source**.\n\nSee glossary: external_id and external_data, External source, Activity.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"source is managed by your API credentials and cannot be set on write requests\"\n}"
            },
            {
              "name": "404 No customer for this external_id under your External source.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/events",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id",
                    "events"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Contact external_id. Prefer the body field; query is accepted for convenience.",
                      "disabled": false
                    }
                  ]
                },
                "description": "Same as recording a customer event, but resolve the person with your website `external_id` instead of `customer_id`.\n\nPass `external_id` (contact) plus `event_name`. Use `event_external_id` for the event’s own id.\n\nLookup uses your credential **External source**.\n\nSee glossary: external_id and external_data, External source, Activity.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/by-external-id/events",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "by-external-id",
                    "events"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Contact external_id. Prefer the body field; query is accepted for convenience.",
                      "disabled": false
                    }
                  ]
                },
                "description": "Same as recording a customer event, but resolve the person with your website `external_id` instead of `customer_id`.\n\nPass `external_id` (contact) plus `event_name`. Use `event_external_id` for the event’s own id.\n\nLookup uses your credential **External source**.\n\nSee glossary: external_id and external_data, External source, Activity.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"external_id\": \"crm-contact-1001\",\n  \"event_name\": \"form_submitted\",\n  \"event_time\": \"2026-08-05T06:00:00.000Z\",\n  \"event_data\": {\n    \"form\": \"newsletter\"\n  },\n  \"location_id\": {{location_id}},\n  \"is_visible_to_customer\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Search customers with advanced filters",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "search"
              ]
            },
            "description": "Search and page through customers using a JSON body with the same filters as `GET …/customers` (cursor, limit, query, locations, sort_by, sort_direction, connection, frequency, engagement, customer_group_id, search_fields, updated_since, include_visitors).\n\nPrefer `GET …/customers` for simple query-string browse; use this when filters are easier to express in JSON.\n\nSee glossary: Customer, Visitor, Connection state.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"limit\": 15,\n  \"query\": \"alex\",\n  \"include_visitors\": false,\n  \"sort_by\": \"total_spent\",\n  \"sort_direction\": \"desc\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 A page of customers. response.next_cursor is null when there are no more pages.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "search"
                  ]
                },
                "description": "Search and page through customers using a JSON body with the same filters as `GET …/customers` (cursor, limit, query, locations, sort_by, sort_direction, connection, frequency, engagement, customer_group_id, search_fields, updated_since, include_visitors).\n\nPrefer `GET …/customers` for simple query-string browse; use this when filters are easier to express in JSON.\n\nSee glossary: Customer, Visitor, Connection state.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"query\": \"alex\",\n  \"include_visitors\": false,\n  \"sort_by\": \"total_spent\",\n  \"sort_direction\": \"desc\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer records fetched successfully (known only)\",\n  \"response\": {\n    \"data\": [\n      {\n        \"id\": 104823,\n        \"external_data\": null,\n        \"first_name\": \"Jordan\",\n        \"last_name\": \"Lee\",\n        \"email\": \"jordan.lee@example.com\",\n        \"phone\": \"+61412345678\",\n        \"location_id\": 3,\n        \"brand_id\": 42,\n        \"uuid\": \"c0b1f7a4-2e3d-4f5a-9b6c-7d8e9f0a1b2c\",\n        \"predicted_name\": null,\n        \"type\": \"customer\",\n        \"transaction_count\": 17,\n        \"total_spent\": 542.5,\n        \"tenure\": 412,\n        \"latest_transaction\": \"2026-06-21T03:15:00.000Z\",\n        \"last_seen\": \"2026-06-21T03:15:00.000Z\",\n        \"frequency\": \"Frequent\",\n        \"engagement\": \"engaged\",\n        \"connection_type\": \"connected\"\n      }\n    ],\n    \"has_more\": true,\n    \"next_cursor\": \"1|0|2026-06-21T03:15:00.000Z|104823\",\n    \"total_count\": 1287\n  }\n}"
            },
            {
              "name": "400 Invalid JSON body or updated_since.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "search"
                  ]
                },
                "description": "Search and page through customers using a JSON body with the same filters as `GET …/customers` (cursor, limit, query, locations, sort_by, sort_direction, connection, frequency, engagement, customer_group_id, search_fields, updated_since, include_visitors).\n\nPrefer `GET …/customers` for simple query-string browse; use this when filters are easier to express in JSON.\n\nSee glossary: Customer, Visitor, Connection state.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"query\": \"alex\",\n  \"include_visitors\": false,\n  \"sort_by\": \"total_spent\",\n  \"sort_direction\": \"desc\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"updated_since must be a valid ISO-8601 timestamp\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "search"
                  ]
                },
                "description": "Search and page through customers using a JSON body with the same filters as `GET …/customers` (cursor, limit, query, locations, sort_by, sort_direction, connection, frequency, engagement, customer_group_id, search_fields, updated_since, include_visitors).\n\nPrefer `GET …/customers` for simple query-string browse; use this when filters are easier to express in JSON.\n\nSee glossary: Customer, Visitor, Connection state.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"query\": \"alex\",\n  \"include_visitors\": false,\n  \"sort_by\": \"total_spent\",\n  \"sort_direction\": \"desc\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get a customer by id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}"
              ]
            },
            "description": "Load one customer profile with connected-system identities (`extended[]`), pass id, Brand Points, myne cashback, lifetime transaction stats, and loyalty join status (`connection_type`).\n\n`connection_type` is `connected` when the customer has joined loyalty, `known` when myne has the customer but they have not joined, and `unknown` for an anonymous website visitor. It matches the `connection_type` on the customer list.\n\nIdentities are listed in `extended[]`, not as a single top-level source/external_id. Cashback is the internal cashback ledger, not a live Wrapped or Giftit credit GET. Visit totals sit on `stats`.\n\nUse the `customer_id` from browse, sync batch, or your POS. Returns 404 when the ID is not in this brand.\n\nSee glossary: Customer, External source, Credit, Points."
          },
          "response": [
            {
              "name": "200 A customer returned with identities, loyalty balances, and lifetime stats.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}"
                  ]
                },
                "description": "Load one customer profile with connected-system identities (`extended[]`), pass id, Brand Points, myne cashback, lifetime transaction stats, and loyalty join status (`connection_type`).\n\n`connection_type` is `connected` when the customer has joined loyalty, `known` when myne has the customer but they have not joined, and `unknown` for an anonymous website visitor. It matches the `connection_type` on the customer list.\n\nIdentities are listed in `extended[]`, not as a single top-level source/external_id. Cashback is the internal cashback ledger, not a live Wrapped or Giftit credit GET. Visit totals sit on `stats`.\n\nUse the `customer_id` from browse, sync batch, or your POS. Returns 404 when the ID is not in this brand.\n\nSee glossary: Customer, External source, Credit, Points."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer fetched successfully\",\n  \"response\": {\n    \"customer\": {\n      \"id\": 104823,\n      \"brand_id\": 42,\n      \"location_id\": 3,\n      \"phone\": \"+61412345678\",\n      \"email\": \"jordan.lee@example.com\",\n      \"created_at\": \"2025-02-15T01:23:45.000Z\",\n      \"updated_at\": \"2026-06-21T03:15:00.000Z\",\n      \"accepts_marketing\": true,\n      \"first_name\": \"Jordan\",\n      \"last_name\": \"Lee\",\n      \"archived_at\": null,\n      \"frequency\": \"Frequent\",\n      \"engagement\": \"engaged\",\n      \"connection_type\": \"connected\",\n      \"pass_id\": \"ABC123XYZ\",\n      \"points\": 120,\n      \"cashback\": 18.5\n    },\n    \"stats\": {\n      \"transaction_count\": 17,\n      \"total_spent\": 542.5,\n      \"last_transaction_date\": \"2026-06-21T03:15:00.000Z\"\n    },\n    \"extended\": [\n      {\n        \"source\": \"lightspeed\",\n        \"external_id\": \"ext-abc123\",\n        \"json_data\": {\n          \"pos_id\": \"LS-98765\"\n        },\n        \"updated_at\": \"2026-06-21T03:15:00.000Z\"\n      },\n      {\n        \"source\": \"monday-crm\",\n        \"external_id\": \"crm-001\",\n        \"json_data\": {},\n        \"updated_at\": \"2026-05-01T00:00:00.000Z\"\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 brand_id or customer_id is missing or not a positive integer.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}"
                  ]
                },
                "description": "Load one customer profile with connected-system identities (`extended[]`), pass id, Brand Points, myne cashback, lifetime transaction stats, and loyalty join status (`connection_type`).\n\n`connection_type` is `connected` when the customer has joined loyalty, `known` when myne has the customer but they have not joined, and `unknown` for an anonymous website visitor. It matches the `connection_type` on the customer list.\n\nIdentities are listed in `extended[]`, not as a single top-level source/external_id. Cashback is the internal cashback ledger, not a live Wrapped or Giftit credit GET. Visit totals sit on `stats`.\n\nUse the `customer_id` from browse, sync batch, or your POS. Returns 404 when the ID is not in this brand.\n\nSee glossary: Customer, External source, Credit, Points."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or customer_id\"\n}"
            },
            {
              "name": "404 No customer with the given id exists for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}"
                  ]
                },
                "description": "Load one customer profile with connected-system identities (`extended[]`), pass id, Brand Points, myne cashback, lifetime transaction stats, and loyalty join status (`connection_type`).\n\n`connection_type` is `connected` when the customer has joined loyalty, `known` when myne has the customer but they have not joined, and `unknown` for an anonymous website visitor. It matches the `connection_type` on the customer list.\n\nIdentities are listed in `extended[]`, not as a single top-level source/external_id. Cashback is the internal cashback ledger, not a live Wrapped or Giftit credit GET. Visit totals sit on `stats`.\n\nUse the `customer_id` from browse, sync batch, or your POS. Returns 404 when the ID is not in this brand.\n\nSee glossary: Customer, External source, Credit, Points."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error. Details are logged server-side.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}"
                  ]
                },
                "description": "Load one customer profile with connected-system identities (`extended[]`), pass id, Brand Points, myne cashback, lifetime transaction stats, and loyalty join status (`connection_type`).\n\n`connection_type` is `connected` when the customer has joined loyalty, `known` when myne has the customer but they have not joined, and `unknown` for an anonymous website visitor. It matches the `connection_type` on the customer list.\n\nIdentities are listed in `extended[]`, not as a single top-level source/external_id. Cashback is the internal cashback ledger, not a live Wrapped or Giftit credit GET. Visit totals sit on `stats`.\n\nUse the `customer_id` from browse, sync batch, or your POS. Returns 404 when the ID is not in this brand.\n\nSee glossary: Customer, External source, Credit, Points."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get extended customer profile",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/extended",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "extended"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "15",
                  "description": "Maximum timeline records to return per collection (activities, transactions).",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "",
                  "description": "Zero-based offset into the timeline.",
                  "disabled": true
                },
                {
                  "key": "payments",
                  "value": "",
                  "description": "When true, include payment detail objects inside each transaction record. Defaults to false.",
                  "disabled": true
                }
              ]
            },
            "description": "Fetch a customer's timeline: external POS records, marketing activities, and transactions.\n\nPaginate with `limit` and `offset`. Set `payments=true` to include payment line items on each transaction.\n\nUse when you need history beyond the summary fields on the main customer endpoint.\n\nSee glossary: Extended profile, Customer."
          },
          "response": [
            {
              "name": "200 Customer extended profile with timeline.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "extended"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "Maximum timeline records to return per collection (activities, transactions).",
                      "disabled": false
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "description": "Zero-based offset into the timeline.",
                      "disabled": true
                    },
                    {
                      "key": "payments",
                      "value": "",
                      "description": "When true, include payment detail objects inside each transaction record. Defaults to false.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Fetch a customer's timeline: external POS records, marketing activities, and transactions.\n\nPaginate with `limit` and `offset`. Set `payments=true` to include payment line items on each transaction.\n\nUse when you need history beyond the summary fields on the main customer endpoint.\n\nSee glossary: Extended profile, Customer."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer extended data fetched successfully\",\n  \"response\": {\n    \"external_records\": [],\n    \"activities\": [\n      {\n        \"type\": \"event\",\n        \"source\": \"lightspeed\",\n        \"created_at\": \"2026-06-21T03:15:00.000Z\",\n        \"data\": {\n          \"name\": \"checkin\"\n        }\n      }\n    ],\n    \"transactions\": [\n      {\n        \"type\": \"transaction\",\n        \"id\": 5501,\n        \"amount\": 42.5,\n        \"transaction_date\": \"2026-06-21T03:15:00.000Z\",\n        \"payments\": []\n      }\n    ],\n    \"pagination\": {\n      \"limit\": 25,\n      \"offset\": 0\n    }\n  }\n}"
            },
            {
              "name": "400 brand_id or customer_id is missing or not a positive integer.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "extended"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "Maximum timeline records to return per collection (activities, transactions).",
                      "disabled": false
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "description": "Zero-based offset into the timeline.",
                      "disabled": true
                    },
                    {
                      "key": "payments",
                      "value": "",
                      "description": "When true, include payment detail objects inside each transaction record. Defaults to false.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Fetch a customer's timeline: external POS records, marketing activities, and transactions.\n\nPaginate with `limit` and `offset`. Set `payments=true` to include payment line items on each transaction.\n\nUse when you need history beyond the summary fields on the main customer endpoint.\n\nSee glossary: Extended profile, Customer."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or customer_id\"\n}"
            },
            {
              "name": "404 No customer with the given id exists for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "extended"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "Maximum timeline records to return per collection (activities, transactions).",
                      "disabled": false
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "description": "Zero-based offset into the timeline.",
                      "disabled": true
                    },
                    {
                      "key": "payments",
                      "value": "",
                      "description": "When true, include payment detail objects inside each transaction record. Defaults to false.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Fetch a customer's timeline: external POS records, marketing activities, and transactions.\n\nPaginate with `limit` and `offset`. Set `payments=true` to include payment line items on each transaction.\n\nUse when you need history beyond the summary fields on the main customer endpoint.\n\nSee glossary: Extended profile, Customer."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error. Details are logged server-side.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "extended"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "Maximum timeline records to return per collection (activities, transactions).",
                      "disabled": false
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "description": "Zero-based offset into the timeline.",
                      "disabled": true
                    },
                    {
                      "key": "payments",
                      "value": "",
                      "description": "When true, include payment detail objects inside each transaction record. Defaults to false.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Fetch a customer's timeline: external POS records, marketing activities, and transactions.\n\nPaginate with `limit` and `offset`. Set `payments=true` to include payment line items on each transaction.\n\nUse when you need history beyond the summary fields on the main customer endpoint.\n\nSee glossary: Extended profile, Customer."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Customer groups",
      "item": [
        {
          "name": "List customer groups",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/filter-groups",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "filter-groups"
              ],
              "query": [
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Return every active customer group for the brand (id and name), including **Claimed - …** groups for live offers.\n\nUse customer group IDs when exporting membership or scoping customer browse (`customer_group_id`). Deleted customer groups are omitted. Membership may be maintained by dynamic refresh, scheduled refresh, import replace, or automation-driven changes—see glossary. Claimed-group membership is join-page claim, not an API write.\n\nSee glossary: Customer group."
          },
          "response": [
            {
              "name": "200 Active customer groups for the brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/filter-groups",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "filter-groups"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Return every active customer group for the brand (id and name), including **Claimed - …** groups for live offers.\n\nUse customer group IDs when exporting membership or scoping customer browse (`customer_group_id`). Deleted customer groups are omitted. Membership may be maintained by dynamic refresh, scheduled refresh, import replace, or automation-driven changes—see glossary. Claimed-group membership is join-page claim, not an API write.\n\nSee glossary: Customer group."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Filter groups fetched successfully\",\n  \"response\": [\n    {\n      \"id\": 12,\n      \"group_name\": \"VIP\"\n    },\n    {\n      \"id\": 7,\n      \"group_name\": \"Recent visitors\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Browse customers in a customer group",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/filter-groups/{{group_id}}/customers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "filter-groups",
                "{{group_id}}",
                "customers"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "15",
                  "description": "",
                  "disabled": false
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "Pagination cursor from response.next_cursor.",
                  "disabled": true
                },
                {
                  "key": "sort_by",
                  "value": "total_spent",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "sort_direction",
                  "value": "desc",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Page through known customers who belong to one customer group, including **Claimed - …** groups (read-only membership from join-page claim).\n\nUses the same cursor and limit pagination as customer browse. Pass `sort_by` and `sort_direction` for ranking within the customer group.\n\nSee glossary: Customer group, Customer."
          },
          "response": [
            {
              "name": "200 Known customers in the customer group. Pagination matches customer browse.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/filter-groups/{{group_id}}/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "filter-groups",
                    "{{group_id}}",
                    "customers"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "Pagination cursor from response.next_cursor.",
                      "disabled": true
                    },
                    {
                      "key": "sort_by",
                      "value": "total_spent",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "sort_direction",
                      "value": "desc",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through known customers who belong to one customer group, including **Claimed - …** groups (read-only membership from join-page claim).\n\nUses the same cursor and limit pagination as customer browse. Pass `sort_by` and `sort_direction` for ranking within the customer group.\n\nSee glossary: Customer group, Customer."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer records fetched successfully (known only)\",\n  \"response\": {\n    \"data\": [\n      {\n        \"id\": 104823,\n        \"external_data\": null,\n        \"first_name\": \"Jordan\",\n        \"last_name\": \"Lee\",\n        \"email\": \"jordan.lee@example.com\",\n        \"phone\": \"+61412345678\",\n        \"location_id\": 3,\n        \"brand_id\": 42,\n        \"uuid\": \"c0b1f7a4-2e3d-4f5a-9b6c-7d8e9f0a1b2c\",\n        \"predicted_name\": null,\n        \"type\": \"customer\",\n        \"transaction_count\": 17,\n        \"total_spent\": 542.5,\n        \"tenure\": 412,\n        \"latest_transaction\": \"2026-06-21T03:15:00.000Z\",\n        \"last_seen\": \"2026-06-21T03:15:00.000Z\",\n        \"frequency\": \"Frequent\",\n        \"engagement\": \"engaged\",\n        \"connection_type\": \"connected\"\n      }\n    ],\n    \"has_more\": true,\n    \"next_cursor\": \"1|0|2026-06-21T03:15:00.000Z|104823\",\n    \"total_count\": 1287\n  }\n}"
            },
            {
              "name": "404 Customer group not found for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/filter-groups/{{group_id}}/customers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "filter-groups",
                    "{{group_id}}",
                    "customers"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "cursor",
                      "value": "",
                      "description": "Pagination cursor from response.next_cursor.",
                      "disabled": true
                    },
                    {
                      "key": "sort_by",
                      "value": "total_spent",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "sort_direction",
                      "value": "desc",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through known customers who belong to one customer group, including **Claimed - …** groups (read-only membership from join-page claim).\n\nUses the same cursor and limit pagination as customer browse. Pass `sort_by` and `sort_direction` for ranking within the customer group.\n\nSee glossary: Customer group, Customer."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Filter group not found\"\n}"
            }
          ]
        },
        {
          "name": "List customer groups for a customer",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/filter-groups",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "filter-groups"
              ],
              "query": [
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Return the customer groups a customer currently belongs to.\n\nUseful for checking offer eligibility, personalising messaging, or syncing customer-group membership to an external CRM.\n\nSee glossary: Customer group, Show vs available."
          },
          "response": [
            {
              "name": "200 List of customer groups this customer belongs to.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/filter-groups",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "filter-groups"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Return the customer groups a customer currently belongs to.\n\nUseful for checking offer eligibility, personalising messaging, or syncing customer-group membership to an external CRM.\n\nSee glossary: Customer group, Show vs available."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Successfully fetched customer filter groups\",\n  \"response\": [\n    {\n      \"id\": 12,\n      \"group_name\": \"VIP\"\n    },\n    {\n      \"id\": 7,\n      \"group_name\": \"Recent visitors\"\n    }\n  ]\n}"
            },
            {
              "name": "400 brand_id or customer_id is missing or not a positive integer. Invalid updated_since.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/filter-groups",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "filter-groups"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Return the customer groups a customer currently belongs to.\n\nUseful for checking offer eligibility, personalising messaging, or syncing customer-group membership to an external CRM.\n\nSee glossary: Customer group, Show vs available."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or customer_id\"\n}"
            },
            {
              "name": "404 No customer with the given id exists for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/filter-groups",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "filter-groups"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Return the customer groups a customer currently belongs to.\n\nUseful for checking offer eligibility, personalising messaging, or syncing customer-group membership to an external CRM.\n\nSee glossary: Customer group, Show vs available."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error. Details are logged server-side.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/filter-groups",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "filter-groups"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Return the customer groups a customer currently belongs to.\n\nUseful for checking offer eligibility, personalising messaging, or syncing customer-group membership to an external CRM.\n\nSee glossary: Customer group, Show vs available."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Credit",
      "item": [
        {
          "name": "Get customer credit balance and wallet pass identifiers",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "credit"
              ]
            },
            "description": "Look up a customer's credit balance and **Wallet pass** identifiers (Apple Wallet and Google Wallet).\n\nBalance is fetched live from the loyalty partner when credit is linked. Otherwise `giftcard_code` is null and balance is 0.\n\nSee glossary: Credit, Wallet pass."
          },
          "response": [
            {
              "name": "200 Customer credit and wallet pass information. When no credit is linked, giftcard_code is null and balance is 0.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit"
                  ]
                },
                "description": "Look up a customer's credit balance and **Wallet pass** identifiers (Apple Wallet and Google Wallet).\n\nBalance is fetched live from the loyalty partner when credit is linked. Otherwise `giftcard_code` is null and balance is 0.\n\nSee glossary: Credit, Wallet pass."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"ok\",\n  \"response\": {\n    \"giftcard_code\": \"ABC123XYZ\",\n    \"balance\": 75,\n    \"balance_fetched_at\": \"2026-06-21T03:15:00.000Z\",\n    \"apple_wallet\": [\n      {\n        \"pass_type_identifier\": \"pass.com.myne.network.gift\",\n        \"serial_number\": \"abc123def456\"\n      }\n    ],\n    \"google_wallet\": [\n      {\n        \"issuer_id\": \"3388000000022345678\",\n        \"class_suffix\": \"giftcard_v1\",\n        \"object_suffix\": \"104823\"\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 brand_id or customer_id is missing or not a positive integer.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit"
                  ]
                },
                "description": "Look up a customer's credit balance and **Wallet pass** identifiers (Apple Wallet and Google Wallet).\n\nBalance is fetched live from the loyalty partner when credit is linked. Otherwise `giftcard_code` is null and balance is 0.\n\nSee glossary: Credit, Wallet pass."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or customer_id\"\n}"
            },
            {
              "name": "404 No customer with the given id exists for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit"
                  ]
                },
                "description": "Look up a customer's credit balance and **Wallet pass** identifiers (Apple Wallet and Google Wallet).\n\nBalance is fetched live from the loyalty partner when credit is linked. Otherwise `giftcard_code` is null and balance is 0.\n\nSee glossary: Credit, Wallet pass."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error. Details are logged server-side.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit"
                  ]
                },
                "description": "Look up a customer's credit balance and **Wallet pass** identifiers (Apple Wallet and Google Wallet).\n\nBalance is fetched live from the loyalty partner when credit is linked. Otherwise `giftcard_code` is null and balance is 0.\n\nSee glossary: Credit, Wallet pass."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Add credit to a customer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "credit"
              ]
            },
            "description": "Add stored value to a customer's linked credit.\n\nSend `amount` and `reason` in the body. Reason is stored for audit and appears in the credit ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate credits are not applied. The customer must already have linked credit.\n\nSee glossary: Credit.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": 10,\n  \"reason\": \"Birthday bonus\",\n  \"idempotency_key\": \"credit-topup-001\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Credit successfully added.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit"
                  ]
                },
                "description": "Add stored value to a customer's linked credit.\n\nSend `amount` and `reason` in the body. Reason is stored for audit and appears in the credit ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate credits are not applied. The customer must already have linked credit.\n\nSee glossary: Credit.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"amount\": 10,\n  \"reason\": \"Birthday bonus\",\n  \"idempotency_key\": \"credit-topup-001\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"ok\",\n  \"response\": {\n    \"giftcard_code\": \"ABC123XYZ\",\n    \"balance\": 85,\n    \"loyalty_system\": \"wrapped\",\n    \"customer_giftcard_id\": 567,\n    \"adjusted_at\": \"2026-06-21T03:15:00.000Z\"\n  }\n}"
            },
            {
              "name": "Invalid brand/customer id",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit"
                  ]
                },
                "description": "Add stored value to a customer's linked credit.\n\nSend `amount` and `reason` in the body. Reason is stored for audit and appears in the credit ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate credits are not applied. The customer must already have linked credit.\n\nSee glossary: Credit.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"amount\": 10,\n  \"reason\": \"Birthday bonus\",\n  \"idempotency_key\": \"credit-topup-001\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or customer_id\"\n}"
            },
            {
              "name": "Unparseable JSON body",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit"
                  ]
                },
                "description": "Add stored value to a customer's linked credit.\n\nSend `amount` and `reason` in the body. Reason is stored for audit and appears in the credit ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate credits are not applied. The customer must already have linked credit.\n\nSee glossary: Credit.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"amount\": 10,\n  \"reason\": \"Birthday bonus\",\n  \"idempotency_key\": \"credit-topup-001\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid JSON body\"\n}"
            },
            {
              "name": "Validation error on amount",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit"
                  ]
                },
                "description": "Add stored value to a customer's linked credit.\n\nSend `amount` and `reason` in the body. Reason is stored for audit and appears in the credit ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate credits are not applied. The customer must already have linked credit.\n\nSee glossary: Credit.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"amount\": 10,\n  \"reason\": \"Birthday bonus\",\n  \"idempotency_key\": \"credit-topup-001\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Amount must be a positive number\"\n}"
            },
            {
              "name": "404 Customer or credit account not found.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit"
                  ]
                },
                "description": "Add stored value to a customer's linked credit.\n\nSend `amount` and `reason` in the body. Reason is stored for audit and appears in the credit ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate credits are not applied. The customer must already have linked credit.\n\nSee glossary: Credit.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"amount\": 10,\n  \"reason\": \"Birthday bonus\",\n  \"idempotency_key\": \"credit-topup-001\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error. Details are logged server-side.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit"
                  ]
                },
                "description": "Add stored value to a customer's linked credit.\n\nSend `amount` and `reason` in the body. Reason is stored for audit and appears in the credit ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate credits are not applied. The customer must already have linked credit.\n\nSee glossary: Credit.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"amount\": 10,\n  \"reason\": \"Birthday bonus\",\n  \"idempotency_key\": \"credit-topup-001\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get customer credit ledger",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit/ledger",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "credit",
                "ledger"
              ],
              "query": [
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "List credit ledger entries for a customer, newest first: amount, running `balance_after`, `occurred_at`, reason, and the transaction or payment that triggered the entry when applicable.\n\nUse to reconcile top-ups added through `POST …/credit` or earned through purchases against myne's records.\n\nSee glossary: Credit."
          },
          "response": [
            {
              "name": "200 Ledger entries for the customer, newest first.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit/ledger",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit",
                    "ledger"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "List credit ledger entries for a customer, newest first: amount, running `balance_after`, `occurred_at`, reason, and the transaction or payment that triggered the entry when applicable.\n\nUse to reconcile top-ups added through `POST …/credit` or earned through purchases against myne's records.\n\nSee glossary: Credit."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"ok\",\n  \"response\": {\n    \"balance\": 75,\n    \"unit\": \"dollars\",\n    \"entries\": [\n      {\n        \"id\": 901,\n        \"amount\": 10,\n        \"balance_after\": 75,\n        \"occurred_at\": \"2026-06-21T03:15:00.000Z\",\n        \"reason\": \"Staff credit — Birthday bonus\",\n        \"unit\": \"dollars\",\n        \"transaction_id\": null,\n        \"payment_id\": null\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id, customer_id, or updated_since (ledger watermark is created_at).",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit/ledger",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit",
                    "ledger"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "List credit ledger entries for a customer, newest first: amount, running `balance_after`, `occurred_at`, reason, and the transaction or payment that triggered the entry when applicable.\n\nUse to reconcile top-ups added through `POST …/credit` or earned through purchases against myne's records.\n\nSee glossary: Credit."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or customer_id\"\n}"
            },
            {
              "name": "404 Customer not found for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit/ledger",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit",
                    "ledger"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "List credit ledger entries for a customer, newest first: amount, running `balance_after`, `occurred_at`, reason, and the transaction or payment that triggered the entry when applicable.\n\nUse to reconcile top-ups added through `POST …/credit` or earned through purchases against myne's records.\n\nSee glossary: Credit."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/credit/ledger",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "credit",
                    "ledger"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "List credit ledger entries for a customer, newest first: amount, running `balance_after`, `occurred_at`, reason, and the transaction or payment that triggered the entry when applicable.\n\nUse to reconcile top-ups added through `POST …/credit` or earned through purchases against myne's records.\n\nSee glossary: Credit."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Points",
      "item": [
        {
          "name": "Get customer points balance",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "points"
              ]
            },
            "description": "Return a customer's Brand Points balance and the brand's points label (for example \"Stars\").\n\n`active` is false and balance is 0 when Brand Points is not enabled for this brand.\n\nSee glossary: Points."
          },
          "response": [
            {
              "name": "200 Brand Points balance and label. When points are not enabled, active is false and balance is 0.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points"
                  ]
                },
                "description": "Return a customer's Brand Points balance and the brand's points label (for example \"Stars\").\n\n`active` is false and balance is 0 when Brand Points is not enabled for this brand.\n\nSee glossary: Points."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"ok\",\n  \"response\": {\n    \"balance\": 1250,\n    \"points_label\": \"Stars\",\n    \"active\": true\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id or customer_id.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points"
                  ]
                },
                "description": "Return a customer's Brand Points balance and the brand's points label (for example \"Stars\").\n\n`active` is false and balance is 0 when Brand Points is not enabled for this brand.\n\nSee glossary: Points."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or customer_id\"\n}"
            },
            {
              "name": "404 Customer not found for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points"
                  ]
                },
                "description": "Return a customer's Brand Points balance and the brand's points label (for example \"Stars\").\n\n`active` is false and balance is 0 when Brand Points is not enabled for this brand.\n\nSee glossary: Points."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points"
                  ]
                },
                "description": "Return a customer's Brand Points balance and the brand's points label (for example \"Stars\").\n\n`active` is false and balance is 0 when Brand Points is not enabled for this brand.\n\nSee glossary: Points."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Add points to a customer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "points"
              ]
            },
            "description": "Add Brand Points to a customer.\n\nSend `points_amount` and `reason` in the body. Reason is stored for audit and appears in the points ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate top-ups are not applied. Returns 400 when Brand Points is not enabled for this brand.\n\nSee glossary: Points.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"points_amount\": 100,\n  \"reason\": \"Compensation for delayed order\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Points successfully added.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points"
                  ]
                },
                "description": "Add Brand Points to a customer.\n\nSend `points_amount` and `reason` in the body. Reason is stored for audit and appears in the points ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate top-ups are not applied. Returns 400 when Brand Points is not enabled for this brand.\n\nSee glossary: Points.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"points_amount\": 100,\n  \"reason\": \"Compensation for delayed order\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"ok\",\n  \"response\": {\n    \"loyalty_system\": \"brand_points\",\n    \"points_amount\": 100,\n    \"balance_after\": 1350,\n    \"adjusted_at\": \"2026-06-21T03:15:00.000Z\"\n  }\n}"
            },
            {
              "name": "400 Invalid body or Brand Points is not enabled for this brand.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points"
                  ]
                },
                "description": "Add Brand Points to a customer.\n\nSend `points_amount` and `reason` in the body. Reason is stored for audit and appears in the points ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate top-ups are not applied. Returns 400 when Brand Points is not enabled for this brand.\n\nSee glossary: Points.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"points_amount\": 100,\n  \"reason\": \"Compensation for delayed order\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Brand Points is not enabled for this brand\"\n}"
            },
            {
              "name": "404 Customer not found for this brand.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points"
                  ]
                },
                "description": "Add Brand Points to a customer.\n\nSend `points_amount` and `reason` in the body. Reason is stored for audit and appears in the points ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate top-ups are not applied. Returns 400 when Brand Points is not enabled for this brand.\n\nSee glossary: Points.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"points_amount\": 100,\n  \"reason\": \"Compensation for delayed order\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points"
                  ]
                },
                "description": "Add Brand Points to a customer.\n\nSend `points_amount` and `reason` in the body. Reason is stored for audit and appears in the points ledger. `note` is accepted as a legacy alias for `reason` for one release.\n\nSupply `idempotency_key` when retrying so duplicate top-ups are not applied. Returns 400 when Brand Points is not enabled for this brand.\n\nSee glossary: Points.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"points_amount\": 100,\n  \"reason\": \"Compensation for delayed order\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get customer points ledger",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points/ledger",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "points",
                "ledger"
              ],
              "query": [
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "List points ledger entries for a customer, newest first: amount, running `balance_after`, `occurred_at`, reason, and the transaction or payment that triggered the entry when applicable.\n\nEmpty when Brand Points is not enabled for this brand.\n\nSee glossary: Points."
          },
          "response": [
            {
              "name": "200 Points ledger entries, or an empty ledger when Brand Points is not enabled.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points/ledger",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points",
                    "ledger"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "List points ledger entries for a customer, newest first: amount, running `balance_after`, `occurred_at`, reason, and the transaction or payment that triggered the entry when applicable.\n\nEmpty when Brand Points is not enabled for this brand.\n\nSee glossary: Points."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"ok\",\n  \"response\": {\n    \"balance\": 75,\n    \"unit\": \"points\",\n    \"entries\": [\n      {\n        \"id\": 901,\n        \"amount\": 10,\n        \"balance_after\": 75,\n        \"occurred_at\": \"2026-06-21T03:15:00.000Z\",\n        \"reason\": \"Staff credit — Birthday bonus\",\n        \"unit\": \"points\",\n        \"transaction_id\": null,\n        \"payment_id\": null\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id, customer_id, or updated_since (ledger watermark is created_at).",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points/ledger",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points",
                    "ledger"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "List points ledger entries for a customer, newest first: amount, running `balance_after`, `occurred_at`, reason, and the transaction or payment that triggered the entry when applicable.\n\nEmpty when Brand Points is not enabled for this brand.\n\nSee glossary: Points."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or customer_id\"\n}"
            },
            {
              "name": "404 Customer not found for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points/ledger",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points",
                    "ledger"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "List points ledger entries for a customer, newest first: amount, running `balance_after`, `occurred_at`, reason, and the transaction or payment that triggered the entry when applicable.\n\nEmpty when Brand Points is not enabled for this brand.\n\nSee glossary: Points."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/points/ledger",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "points",
                    "ledger"
                  ],
                  "query": [
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with created_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "List points ledger entries for a customer, newest first: amount, running `balance_after`, `occurred_at`, reason, and the transaction or payment that triggered the entry when applicable.\n\nEmpty when Brand Points is not enabled for this brand.\n\nSee glossary: Points."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Products",
      "item": [
        {
          "name": "Browse products for a brand",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "products"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "15",
                  "description": "",
                  "disabled": false
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "location_id",
                  "value": "{{location_id}}",
                  "description": "Include products scoped to this location (unscoped products still match).",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Page through the brand's sellable catalog.\n\nFilter by `location_id` to include products scoped to one venue (unscoped products still match). Use free-text search to narrow by name or description.\n\nProduct rows include partner-safe fields. Use get-by-id or by-external-id when you need integration `extended[]` payloads—especially surface-specific POS ids before evaluate.\n\nSee glossary: Location, Order line pos_id, Surface."
          },
          "response": [
            {
              "name": "200 Paginated product catalog for the brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "search",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "location_id",
                      "value": "{{location_id}}",
                      "description": "Include products scoped to this location (unscoped products still match).",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through the brand's sellable catalog.\n\nFilter by `location_id` to include products scoped to one venue (unscoped products still match). Use free-text search to narrow by name or description.\n\nProduct rows include partner-safe fields. Use get-by-id or by-external-id when you need integration `extended[]` payloads—especially surface-specific POS ids before evaluate.\n\nSee glossary: Location, Order line pos_id, Surface."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Products fetched successfully\",\n  \"response\": {\n    \"products\": [\n      {\n        \"id\": 501,\n        \"name\": \"Flat white\",\n        \"description\": \"Double-shot flat white\",\n        \"price\": 4.5,\n        \"is_option\": false,\n        \"sku\": \"FW-REG\",\n        \"image_url\": null,\n        \"created_at\": \"2025-11-01T00:00:00.000Z\",\n        \"updated_at\": \"2026-06-01T00:00:00.000Z\"\n      }\n    ],\n    \"total\": 1,\n    \"limit\": 20,\n    \"offset\": 0,\n    \"page\": 1\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id. Invalid updated_since.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "search",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "location_id",
                      "value": "{{location_id}}",
                      "description": "Include products scoped to this location (unscoped products still match).",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through the brand's sellable catalog.\n\nFilter by `location_id` to include products scoped to one venue (unscoped products still match). Use free-text search to narrow by name or description.\n\nProduct rows include partner-safe fields. Use get-by-id or by-external-id when you need integration `extended[]` payloads—especially surface-specific POS ids before evaluate.\n\nSee glossary: Location, Order line pos_id, Surface."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "search",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "location_id",
                      "value": "{{location_id}}",
                      "description": "Include products scoped to this location (unscoped products still match).",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through the brand's sellable catalog.\n\nFilter by `location_id` to include products scoped to one venue (unscoped products still match). Use free-text search to narrow by name or description.\n\nProduct rows include partner-safe fields. Use get-by-id or by-external-id when you need integration `extended[]` payloads—especially surface-specific POS ids before evaluate.\n\nSee glossary: Location, Order line pos_id, Surface."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get a product by id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/{{product_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "products",
                "{{product_id}}"
              ]
            },
            "description": "Load one catalog item by myne `product_id`.\n\nReturns core fields plus `extended[]` integration payloads (`source` and `external_id` per connected app).\n\nUse after browse or when resolving promotion rule references. For register evaluate, pick the `extended` id that matches your **Surface** (see **Order line pos_id**).\n\nSee glossary: Order line pos_id, Surface, External source."
          },
          "response": [
            {
              "name": "200 Single product with extended[] integration payloads.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/{{product_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "{{product_id}}"
                  ]
                },
                "description": "Load one catalog item by myne `product_id`.\n\nReturns core fields plus `extended[]` integration payloads (`source` and `external_id` per connected app).\n\nUse after browse or when resolving promotion rule references. For register evaluate, pick the `extended` id that matches your **Surface** (see **Order line pos_id**).\n\nSee glossary: Order line pos_id, Surface, External source."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Product fetched successfully\",\n  \"response\": {\n    \"id\": 501,\n    \"name\": \"Flat white\",\n    \"description\": \"Double-shot flat white\",\n    \"price\": 4.5,\n    \"is_option\": false,\n    \"sku\": \"FW-REG\",\n    \"image_url\": null,\n    \"created_at\": \"2025-11-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n    \"extended\": [\n      {\n        \"source\": \"lightspeed-k\",\n        \"external_id\": \"456\",\n        \"json_data\": {},\n        \"updated_at\": \"2026-06-01T00:00:00.000Z\"\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id or product_id.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/{{product_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "{{product_id}}"
                  ]
                },
                "description": "Load one catalog item by myne `product_id`.\n\nReturns core fields plus `extended[]` integration payloads (`source` and `external_id` per connected app).\n\nUse after browse or when resolving promotion rule references. For register evaluate, pick the `extended` id that matches your **Surface** (see **Order line pos_id**).\n\nSee glossary: Order line pos_id, Surface, External source."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or product_id\"\n}"
            },
            {
              "name": "404 Product not found.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/{{product_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "{{product_id}}"
                  ]
                },
                "description": "Load one catalog item by myne `product_id`.\n\nReturns core fields plus `extended[]` integration payloads (`source` and `external_id` per connected app).\n\nUse after browse or when resolving promotion rule references. For register evaluate, pick the `extended` id that matches your **Surface** (see **Order line pos_id**).\n\nSee glossary: Order line pos_id, Surface, External source."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Product not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/{{product_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "{{product_id}}"
                  ]
                },
                "description": "Load one catalog item by myne `product_id`.\n\nReturns core fields plus `extended[]` integration payloads (`source` and `external_id` per connected app).\n\nUse after browse or when resolving promotion rule references. For register evaluate, pick the `extended` id that matches your **Surface** (see **Order line pos_id**).\n\nSee glossary: Order line pos_id, Surface, External source."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get a product by external id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/by-external-id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "products",
                "by-external-id"
              ],
              "query": [
                {
                  "key": "external_id",
                  "value": "{{external_id}}",
                  "description": "",
                  "disabled": false
                },
                {
                  "key": "source",
                  "value": "",
                  "description": "Integration source key; defaults to your credential source.",
                  "disabled": true
                }
              ]
            },
            "description": "Resolve a myne product from your POS or menu `external_id` without paging browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (Integrations label). Pass `source` explicitly to read IDs synced under another integration.\n\nSee glossary: external_id and external_data, External source."
          },
          "response": [
            {
              "name": "200 Product resolved from external_id with extended[] payloads.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/by-external-id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "by-external-id"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Integration source key; defaults to your credential source.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Resolve a myne product from your POS or menu `external_id` without paging browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (Integrations label). Pass `source` explicitly to read IDs synced under another integration.\n\nSee glossary: external_id and external_data, External source."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Product fetched successfully\",\n  \"response\": {\n    \"id\": 501,\n    \"name\": \"Flat white\",\n    \"description\": \"Double-shot flat white\",\n    \"price\": 4.5,\n    \"is_option\": false,\n    \"sku\": \"FW-REG\",\n    \"image_url\": null,\n    \"created_at\": \"2025-11-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n    \"extended\": [\n      {\n        \"source\": \"integration_api\",\n        \"external_id\": \"POS-456\",\n        \"json_data\": {},\n        \"updated_at\": \"2026-06-01T00:00:00.000Z\"\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 external_id is required.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/by-external-id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "by-external-id"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Integration source key; defaults to your credential source.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Resolve a myne product from your POS or menu `external_id` without paging browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (Integrations label). Pass `source` explicitly to read IDs synced under another integration.\n\nSee glossary: external_id and external_data, External source."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"external_id query parameter is required\"\n}"
            },
            {
              "name": "404 Product not found.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/by-external-id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "by-external-id"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Integration source key; defaults to your credential source.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Resolve a myne product from your POS or menu `external_id` without paging browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (Integrations label). Pass `source` explicitly to read IDs synced under another integration.\n\nSee glossary: external_id and external_data, External source."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Product not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/by-external-id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "by-external-id"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Integration source key; defaults to your credential source.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Resolve a myne product from your POS or menu `external_id` without paging browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (Integrations label). Pass `source` explicitly to read IDs synced under another integration.\n\nSee glossary: external_id and external_data, External source."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Search products with advanced filters",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "products",
                "search"
              ]
            },
            "description": "Search the catalog with a JSON body: optional search text, `category_ids`, `source` (integration filter), `page`, and `limit`.\n\nPrefer `GET /products` for simple browse; use this when you need category or source scoping in one request.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"limit\": 15,\n  \"search\": \"coffee\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Paginated product search results.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "search"
                  ]
                },
                "description": "Search the catalog with a JSON body: optional search text, `category_ids`, `source` (integration filter), `page`, and `limit`.\n\nPrefer `GET /products` for simple browse; use this when you need category or source scoping in one request.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"search\": \"coffee\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Products fetched successfully\",\n  \"response\": {\n    \"products\": [\n      {\n        \"id\": 501,\n        \"name\": \"Flat white\",\n        \"description\": \"Double-shot flat white\",\n        \"price\": 4.5,\n        \"is_option\": false,\n        \"sku\": \"FW-REG\",\n        \"image_url\": null,\n        \"created_at\": \"2025-11-01T00:00:00.000Z\",\n        \"updated_at\": \"2026-06-01T00:00:00.000Z\"\n      }\n    ],\n    \"total\": 1,\n    \"limit\": 20,\n    \"offset\": 0,\n    \"page\": 1\n  }\n}"
            },
            {
              "name": "400 Invalid JSON body. Invalid updated_since.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "search"
                  ]
                },
                "description": "Search the catalog with a JSON body: optional search text, `category_ids`, `source` (integration filter), `page`, and `limit`.\n\nPrefer `GET /products` for simple browse; use this when you need category or source scoping in one request.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"search\": \"coffee\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid JSON body\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/products/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "products",
                    "search"
                  ]
                },
                "description": "Search the catalog with a JSON body: optional search text, `category_ids`, `source` (integration filter), `page`, and `limit`.\n\nPrefer `GET /products` for simple browse; use this when you need category or source scoping in one request.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"search\": \"coffee\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Browse product categories for a brand",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "categories"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "15",
                  "description": "",
                  "disabled": false
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "source",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Page through product categories for the brand. Optional `source` filter narrows to categories from one integration.\n\nEach row includes `product_count` when available. Use category ids in `POST /products/search` or when reading enriched promotion rules."
          },
          "response": [
            {
              "name": "200 Paginated product categories for the brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/categories",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "categories"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "search",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through product categories for the brand. Optional `source` filter narrows to categories from one integration.\n\nEach row includes `product_count` when available. Use category ids in `POST /products/search` or when reading enriched promotion rules."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Categories fetched successfully\",\n  \"response\": {\n    \"categories\": [\n      {\n        \"id\": 10,\n        \"name\": \"Coffee\",\n        \"description\": \"Espresso-based drinks\",\n        \"external_id\": \"cat-coffee\",\n        \"product_count\": 12,\n        \"created_at\": \"2025-11-01T00:00:00.000Z\",\n        \"updated_at\": \"2026-06-01T00:00:00.000Z\"\n      }\n    ],\n    \"total\": 1,\n    \"limit\": 20,\n    \"offset\": 0,\n    \"page\": 1\n  }\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/categories",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "categories"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "search",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through product categories for the brand. Optional `source` filter narrows to categories from one integration.\n\nEach row includes `product_count` when available. Use category ids in `POST /products/search` or when reading enriched promotion rules."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get a product category by id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/categories/{{category_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "categories",
                "{{category_id}}"
              ]
            },
            "description": "Load one product category by id with `product_count`.\n\nReturns partner-safe fields; `extended[]` is reserved for future category integration payloads."
          },
          "response": [
            {
              "name": "200 Single product category.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/categories/{{category_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "categories",
                    "{{category_id}}"
                  ]
                },
                "description": "Load one product category by id with `product_count`.\n\nReturns partner-safe fields; `extended[]` is reserved for future category integration payloads."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Category fetched successfully\",\n  \"response\": {\n    \"id\": 10,\n    \"name\": \"Coffee\",\n    \"description\": \"Espresso-based drinks\",\n    \"external_id\": \"cat-coffee\",\n    \"product_count\": 12,\n    \"created_at\": \"2025-11-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n    \"extended\": []\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id or category_id.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/categories/{{category_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "categories",
                    "{{category_id}}"
                  ]
                },
                "description": "Load one product category by id with `product_count`.\n\nReturns partner-safe fields; `extended[]` is reserved for future category integration payloads."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or category_id\"\n}"
            },
            {
              "name": "404 Category not found.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/categories/{{category_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "categories",
                    "{{category_id}}"
                  ]
                },
                "description": "Load one product category by id with `product_count`.\n\nReturns partner-safe fields; `extended[]` is reserved for future category integration payloads."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Category not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/categories/{{category_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "categories",
                    "{{category_id}}"
                  ]
                },
                "description": "Load one product category by id with `product_count`.\n\nReturns partner-safe fields; `extended[]` is reserved for future category integration payloads."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Promotions",
      "item": [
        {
          "name": "Browse promotions for a brand",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "promotions"
              ],
              "query": [
                {
                  "key": "location_id",
                  "value": "{{location_id}}",
                  "description": "Restrict to promotions for one location (includes brand-wide promotions).",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "15",
                  "description": "",
                  "disabled": false
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "valid_only",
                  "value": "true",
                  "description": "When true, only promotions within their valid date range are returned.",
                  "disabled": true
                },
                {
                  "key": "surface",
                  "value": "",
                  "description": "Restrict to promotions visible on one channel.",
                  "disabled": true
                },
                {
                  "key": "integration_type",
                  "value": "",
                  "description": "List filter only — not a basket field. Use to return which promotions can show from customer Show vs available groups, in a coarse context (ordering, listing, or ecommerce), without knowing which app will evaluate the sale. Do not send on evaluate, apply, or complete. Those open-order calls always n",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "List promotions configured for a brand.\n\nFilter by `location_id`, paginate with `page` and `limit`, or narrow with search.\n\nPass `surface` (`connection_pages`, `meandu`, `lso`, or `lsk`) when you already know the app. Optional `integration_type` is only for listing what can show without an open order and without knowing which app will later evaluate the sale: `ordering` → meandu, `listing` / `ecommerce` → connection_pages. Eligibility still follows customer **Show vs available** groups. Do not send `integration_type` on evaluate, apply, or complete — those basket calls always need `surface` (`lsk` or `lso`). `pos` is not a substitute for a register or ordering app.\n\nEach promotion includes `image_url` (the offer image, or null), `availability[]` (the surfaces it may appear on) and enriched requirements/targets with product and category names plus `extended[]`.\n\nSee glossary: Promotion, Surface, Location."
          },
          "response": [
            {
              "name": "200 Paginated promotion catalog for the brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions"
                  ],
                  "query": [
                    {
                      "key": "location_id",
                      "value": "{{location_id}}",
                      "description": "Restrict to promotions for one location (includes brand-wide promotions).",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "search",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "valid_only",
                      "value": "true",
                      "description": "When true, only promotions within their valid date range are returned.",
                      "disabled": true
                    },
                    {
                      "key": "surface",
                      "value": "",
                      "description": "Restrict to promotions visible on one channel.",
                      "disabled": true
                    },
                    {
                      "key": "integration_type",
                      "value": "",
                      "description": "List filter only — not a basket field. Use to return which promotions can show from customer Show vs available groups, in a coarse context (ordering, listing, or ecommerce), without knowing which app will evaluate the sale. Do not send on evaluate, apply, or complete. Those open-order calls always n",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "List promotions configured for a brand.\n\nFilter by `location_id`, paginate with `page` and `limit`, or narrow with search.\n\nPass `surface` (`connection_pages`, `meandu`, `lso`, or `lsk`) when you already know the app. Optional `integration_type` is only for listing what can show without an open order and without knowing which app will later evaluate the sale: `ordering` → meandu, `listing` / `ecommerce` → connection_pages. Eligibility still follows customer **Show vs available** groups. Do not send `integration_type` on evaluate, apply, or complete — those basket calls always need `surface` (`lsk` or `lso`). `pos` is not a substitute for a register or ordering app.\n\nEach promotion includes `image_url` (the offer image, or null), `availability[]` (the surfaces it may appear on) and enriched requirements/targets with product and category names plus `extended[]`.\n\nSee glossary: Promotion, Surface, Location."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions fetched successfully\",\n  \"response\": {\n    \"promotions\": [\n      {\n        \"id\": 901,\n        \"brand_id\": 42,\n        \"name\": \"Free regular coffee\",\n        \"description\": \"One regular coffee on us\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"discount_type\": \"free_item\",\n        \"applies_to\": \"in_store\",\n        \"channel\": \"pos\",\n        \"location_id\": null,\n        \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n        \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n        \"created_at\": \"2025-12-01T00:00:00.000Z\",\n        \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n        \"availability\": [\n          \"connection_pages\",\n          \"lsk\",\n          \"lso\",\n          \"meandu\",\n          \"meandu_connect\",\n          \"shopify\"\n        ],\n        \"requirements\": {\n          \"products\": [],\n          \"categories\": [\n            {\n              \"id\": 10,\n              \"name\": \"Coffee\",\n              \"extended\": []\n            }\n          ]\n        },\n        \"targets\": {\n          \"products\": [\n            {\n              \"id\": 501,\n              \"name\": \"Flat white\",\n              \"extended\": [\n                {\n                  \"source\": \"lightspeed-k\",\n                  \"external_id\": \"456\",\n                  \"json_data\": {},\n                  \"updated_at\": null\n                }\n              ]\n            }\n          ],\n          \"categories\": []\n        }\n      }\n    ],\n    \"total\": 1,\n    \"limit\": 20,\n    \"offset\": 0,\n    \"page\": 1\n  }\n}"
            }
          ]
        },
        {
          "name": "List promotions for a customer with eligibility",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/promotions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "promotions"
              ],
              "query": [
                {
                  "key": "valid_only",
                  "value": "true",
                  "description": "When true (default), omit expired or not-yet-valid promotions.",
                  "disabled": true
                },
                {
                  "key": "surface",
                  "value": "",
                  "description": "Restrict to promotions visible on one channel.",
                  "disabled": true
                },
                {
                  "key": "integration_type",
                  "value": "",
                  "description": "List filter only — not a basket field. Use to return which promotions can show from customer Show vs available groups, in a coarse context (ordering, listing, or ecommerce), without knowing which app will evaluate the sale. Do not send on evaluate, apply, or complete. Those open-order calls always n",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Return promotions visible to a customer and whether each is redeemable now.\n\nEach row includes `eligibility.visibility_state` (`unlocked` or `locked`) using myne **Show vs available** customer group rules, plus `image_url`, `availability[]` and enriched catalog references.\n\nThe response also includes the customer's current `points_balance` and `cashback_balance_in_cents` so you do not need a separate points or credit call. Pay with Cashback promotions also include `cashback_balance_in_cents` on the row (wallet balance, not a cart discount).\n\nPass `surface` when you know the app (same meaning as list promotions). Hidden promotions are omitted. Optional `integration_type` lists what this customer can see from **Show vs available** groups without an open order and without choosing the evaluate app (`ordering`, `listing`, or `ecommerce`). Do not send it on evaluate, apply, or complete.\n\nSee glossary: Promotion, Surface, Show vs available, Customer."
          },
          "response": [
            {
              "name": "200 Promotions visible to the customer with eligibility, plus current points and cashback balances.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/promotions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "promotions"
                  ],
                  "query": [
                    {
                      "key": "valid_only",
                      "value": "true",
                      "description": "When true (default), omit expired or not-yet-valid promotions.",
                      "disabled": true
                    },
                    {
                      "key": "surface",
                      "value": "",
                      "description": "Restrict to promotions visible on one channel.",
                      "disabled": true
                    },
                    {
                      "key": "integration_type",
                      "value": "",
                      "description": "List filter only — not a basket field. Use to return which promotions can show from customer Show vs available groups, in a coarse context (ordering, listing, or ecommerce), without knowing which app will evaluate the sale. Do not send on evaluate, apply, or complete. Those open-order calls always n",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Return promotions visible to a customer and whether each is redeemable now.\n\nEach row includes `eligibility.visibility_state` (`unlocked` or `locked`) using myne **Show vs available** customer group rules, plus `image_url`, `availability[]` and enriched catalog references.\n\nThe response also includes the customer's current `points_balance` and `cashback_balance_in_cents` so you do not need a separate points or credit call. Pay with Cashback promotions also include `cashback_balance_in_cents` on the row (wallet balance, not a cart discount).\n\nPass `surface` when you know the app (same meaning as list promotions). Hidden promotions are omitted. Optional `integration_type` lists what this customer can see from **Show vs available** groups without an open order and without choosing the evaluate app (`ordering`, `listing`, or `ecommerce`). Do not send it on evaluate, apply, or complete.\n\nSee glossary: Promotion, Surface, Show vs available, Customer."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer promotions fetched successfully\",\n  \"response\": {\n    \"promotions\": [\n      {\n        \"id\": 901,\n        \"brand_id\": 42,\n        \"name\": \"Free regular coffee\",\n        \"description\": \"One regular coffee on us\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"discount_type\": \"free_item\",\n        \"applies_to\": \"in_store\",\n        \"channel\": \"pos\",\n        \"location_id\": null,\n        \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n        \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n        \"created_at\": \"2025-12-01T00:00:00.000Z\",\n        \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n        \"availability\": [\n          \"connection_pages\",\n          \"lsk\",\n          \"lso\",\n          \"meandu\",\n          \"meandu_connect\",\n          \"shopify\"\n        ],\n        \"requirements\": {\n          \"products\": [],\n          \"categories\": [\n            {\n              \"id\": 10,\n              \"name\": \"Coffee\",\n              \"extended\": []\n            }\n          ]\n        },\n        \"targets\": {\n          \"products\": [\n            {\n              \"id\": 501,\n              \"name\": \"Flat white\",\n              \"extended\": [\n                {\n                  \"source\": \"lightspeed-k\",\n                  \"external_id\": \"456\",\n                  \"json_data\": {},\n                  \"updated_at\": null\n                }\n              ]\n            }\n          ],\n          \"categories\": []\n        },\n        \"eligibility\": {\n          \"visibility_state\": \"locked\",\n          \"unlock_display_text\": \"Visit 2 more times this month\"\n        }\n      },\n      {\n        \"id\": 880,\n        \"brand_id\": 42,\n        \"name\": \"Pay with Cashback\",\n        \"description\": \"Spend store credit on this order.\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"discount_type\": \"free_item\",\n        \"applies_to\": \"in_store\",\n        \"channel\": \"pos\",\n        \"location_id\": null,\n        \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n        \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n        \"created_at\": \"2025-12-01T00:00:00.000Z\",\n        \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n        \"availability\": [\n          \"connection_pages\",\n          \"lsk\",\n          \"lso\",\n          \"meandu\",\n          \"meandu_connect\",\n          \"shopify\"\n        ],\n        \"requirements\": {\n          \"products\": [],\n          \"categories\": [\n            {\n              \"id\": 10,\n              \"name\": \"Coffee\",\n              \"extended\": []\n            }\n          ]\n        },\n        \"targets\": {\n          \"products\": [\n            {\n              \"id\": 501,\n              \"name\": \"Flat white\",\n              \"extended\": [\n                {\n                  \"source\": \"lightspeed-k\",\n                  \"external_id\": \"456\",\n                  \"json_data\": {},\n                  \"updated_at\": null\n                }\n              ]\n            }\n          ],\n          \"categories\": []\n        },\n        \"eligibility\": {\n          \"visibility_state\": \"unlocked\",\n          \"unlock_display_text\": null\n        },\n        \"rules\": {\n          \"promotion_type\": \"pay_with_cashback\"\n        },\n        \"cashback_balance_in_cents\": 2500\n      }\n    ],\n    \"total\": 2,\n    \"points_balance\": 120,\n    \"cashback_balance_in_cents\": 2500\n  }\n}"
            },
            {
              "name": "404 Customer not found for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/promotions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "promotions"
                  ],
                  "query": [
                    {
                      "key": "valid_only",
                      "value": "true",
                      "description": "When true (default), omit expired or not-yet-valid promotions.",
                      "disabled": true
                    },
                    {
                      "key": "surface",
                      "value": "",
                      "description": "Restrict to promotions visible on one channel.",
                      "disabled": true
                    },
                    {
                      "key": "integration_type",
                      "value": "",
                      "description": "List filter only — not a basket field. Use to return which promotions can show from customer Show vs available groups, in a coarse context (ordering, listing, or ecommerce), without knowing which app will evaluate the sale. Do not send on evaluate, apply, or complete. Those open-order calls always n",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Return promotions visible to a customer and whether each is redeemable now.\n\nEach row includes `eligibility.visibility_state` (`unlocked` or `locked`) using myne **Show vs available** customer group rules, plus `image_url`, `availability[]` and enriched catalog references.\n\nThe response also includes the customer's current `points_balance` and `cashback_balance_in_cents` so you do not need a separate points or credit call. Pay with Cashback promotions also include `cashback_balance_in_cents` on the row (wallet balance, not a cart discount).\n\nPass `surface` when you know the app (same meaning as list promotions). Hidden promotions are omitted. Optional `integration_type` lists what this customer can see from **Show vs available** groups without an open order and without choosing the evaluate app (`ordering`, `listing`, or `ecommerce`). Do not send it on evaluate, apply, or complete.\n\nSee glossary: Promotion, Surface, Show vs available, Customer."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            }
          ]
        },
        {
          "name": "Get a promotion by id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/{{promotion_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "promotions",
                "{{promotion_id}}"
              ]
            },
            "description": "Load one promotion with enrichment.\n\n`availability` lists the canonical **Surfaces** it may appear on. `image_url` is the offer image (null when none is set). Requirements and targets include product and category names plus `extended[]` resolved from the promotion's rules.\n\nSee glossary: Promotion, Surface."
          },
          "response": [
            {
              "name": "200 Enriched promotion with availability and catalog references.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/{{promotion_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "{{promotion_id}}"
                  ]
                },
                "description": "Load one promotion with enrichment.\n\n`availability` lists the canonical **Surfaces** it may appear on. `image_url` is the offer image (null when none is set). Requirements and targets include product and category names plus `extended[]` resolved from the promotion's rules.\n\nSee glossary: Promotion, Surface."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotion fetched successfully\",\n  \"response\": {\n    \"id\": 901,\n    \"brand_id\": 42,\n    \"name\": \"Free regular coffee\",\n    \"description\": \"One regular coffee on us\",\n    \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n    \"discount_type\": \"free_item\",\n    \"applies_to\": \"in_store\",\n    \"channel\": \"pos\",\n    \"location_id\": null,\n    \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n    \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n    \"created_at\": \"2025-12-01T00:00:00.000Z\",\n    \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n    \"availability\": [\n      \"connection_pages\",\n      \"lsk\",\n      \"lso\",\n      \"meandu\",\n      \"meandu_connect\",\n      \"shopify\"\n    ],\n    \"requirements\": {\n      \"products\": [],\n      \"categories\": [\n        {\n          \"id\": 10,\n          \"name\": \"Coffee\",\n          \"extended\": []\n        }\n      ]\n    },\n    \"targets\": {\n      \"products\": [\n        {\n          \"id\": 501,\n          \"name\": \"Flat white\",\n          \"extended\": [\n            {\n              \"source\": \"lightspeed-k\",\n              \"external_id\": \"456\",\n              \"json_data\": {},\n              \"updated_at\": null\n            }\n          ]\n        }\n      ],\n      \"categories\": []\n    }\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id or promotion_id.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/{{promotion_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "{{promotion_id}}"
                  ]
                },
                "description": "Load one promotion with enrichment.\n\n`availability` lists the canonical **Surfaces** it may appear on. `image_url` is the offer image (null when none is set). Requirements and targets include product and category names plus `extended[]` resolved from the promotion's rules.\n\nSee glossary: Promotion, Surface."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or promotion_id\"\n}"
            },
            {
              "name": "404 Promotion not found.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/{{promotion_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "{{promotion_id}}"
                  ]
                },
                "description": "Load one promotion with enrichment.\n\n`availability` lists the canonical **Surfaces** it may appear on. `image_url` is the offer image (null when none is set). Requirements and targets include product and category names plus `extended[]` resolved from the promotion's rules.\n\nSee glossary: Promotion, Surface."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotion not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/{{promotion_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "{{promotion_id}}"
                  ]
                },
                "description": "Load one promotion with enrichment.\n\n`availability` lists the canonical **Surfaces** it may appear on. `image_url` is the offer image (null when none is set). Requirements and targets include product and category names plus `extended[]` resolved from the promotion's rules.\n\nSee glossary: Promotion, Surface."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Search promotions with advanced filters",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "promotions",
                "search"
              ]
            },
            "description": "Search promotions with filters not available on the browse endpoint: `category_ids` and `product_ids` (any match against a promotion's rules), `surface`, optional `integration_type` (coarse list filter only — not for basket calls), `valid_only`, and free-text search.\n\nEach result is enriched the same way as `GET …/promotions/{promotion_id}`.\n\nSee glossary: Promotion, Surface.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"limit\": 15,\n  \"search\": \"happy hour\",\n  \"valid_only\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Paginated enriched promotion search results.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "search"
                  ]
                },
                "description": "Search promotions with filters not available on the browse endpoint: `category_ids` and `product_ids` (any match against a promotion's rules), `surface`, optional `integration_type` (coarse list filter only — not for basket calls), `valid_only`, and free-text search.\n\nEach result is enriched the same way as `GET …/promotions/{promotion_id}`.\n\nSee glossary: Promotion, Surface.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"search\": \"happy hour\",\n  \"valid_only\": true\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions fetched successfully\",\n  \"response\": {\n    \"promotions\": [\n      {\n        \"id\": 901,\n        \"brand_id\": 42,\n        \"name\": \"Free regular coffee\",\n        \"description\": \"One regular coffee on us\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"discount_type\": \"free_item\",\n        \"applies_to\": \"in_store\",\n        \"channel\": \"pos\",\n        \"location_id\": null,\n        \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n        \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n        \"created_at\": \"2025-12-01T00:00:00.000Z\",\n        \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n        \"availability\": [\n          \"connection_pages\",\n          \"lsk\",\n          \"lso\",\n          \"meandu\",\n          \"meandu_connect\",\n          \"shopify\"\n        ],\n        \"requirements\": {\n          \"products\": [],\n          \"categories\": [\n            {\n              \"id\": 10,\n              \"name\": \"Coffee\",\n              \"extended\": []\n            }\n          ]\n        },\n        \"targets\": {\n          \"products\": [\n            {\n              \"id\": 501,\n              \"name\": \"Flat white\",\n              \"extended\": [\n                {\n                  \"source\": \"lightspeed-k\",\n                  \"external_id\": \"456\",\n                  \"json_data\": {},\n                  \"updated_at\": null\n                }\n              ]\n            }\n          ],\n          \"categories\": []\n        }\n      }\n    ],\n    \"total\": 1,\n    \"limit\": 20,\n    \"offset\": 0,\n    \"page\": 1\n  }\n}"
            },
            {
              "name": "400 Invalid JSON body or filter. Invalid updated_since.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "search"
                  ]
                },
                "description": "Search promotions with filters not available on the browse endpoint: `category_ids` and `product_ids` (any match against a promotion's rules), `surface`, optional `integration_type` (coarse list filter only — not for basket calls), `valid_only`, and free-text search.\n\nEach result is enriched the same way as `GET …/promotions/{promotion_id}`.\n\nSee glossary: Promotion, Surface.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"search\": \"happy hour\",\n  \"valid_only\": true\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"integration_type=pos requires an explicit POS register surface\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "search"
                  ]
                },
                "description": "Search promotions with filters not available on the browse endpoint: `category_ids` and `product_ids` (any match against a promotion's rules), `surface`, optional `integration_type` (coarse list filter only — not for basket calls), `valid_only`, and free-text search.\n\nEach result is enriched the same way as `GET …/promotions/{promotion_id}`.\n\nSee glossary: Promotion, Surface.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"search\": \"happy hour\",\n  \"valid_only\": true\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Check which rewards fit this open order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "promotions",
                "evaluate"
              ]
            },
            "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "LSK — available free item (negative promotion product)",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"basket_id\": \"pos-order-7f3a2c\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"901\",\n          \"type\": \"Offer\",\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"AVAILABLE_TO_REDEEM\",\n          \"visibility_state\": \"unlocked\",\n          \"application_scope\": \"line\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"discount_amount_in_cents\": 550,\n          \"discount_application\": {\n            \"method\": \"negative_product_line\",\n            \"instructions\": \"To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id.\"\n          },\n          \"promotion\": {\n            \"id\": 901,\n            \"brand_id\": 42,\n            \"name\": \"Free regular coffee\",\n            \"description\": \"One regular coffee on us\",\n            \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n            \"discount_type\": \"free_item\",\n            \"applies_to\": \"in_store\",\n            \"channel\": \"pos\",\n            \"location_id\": null,\n            \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n            \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n            \"created_at\": \"2025-12-01T00:00:00.000Z\",\n            \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n            \"availability\": [\n              \"connection_pages\",\n              \"lsk\",\n              \"lso\",\n              \"meandu\",\n              \"meandu_connect\",\n              \"shopify\"\n            ],\n            \"requirements\": {\n              \"products\": [],\n              \"categories\": [\n                {\n                  \"id\": 10,\n                  \"name\": \"Coffee\",\n                  \"extended\": []\n                }\n              ]\n            },\n            \"targets\": {\n              \"products\": [\n                {\n                  \"id\": 501,\n                  \"name\": \"Flat white\",\n                  \"extended\": [\n                    {\n                      \"source\": \"lightspeed-k\",\n                      \"external_id\": \"456\",\n                      \"json_data\": {},\n                      \"updated_at\": null\n                    }\n                  ]\n                }\n              ],\n              \"categories\": []\n            }\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"901\",\n        \"type\": \"Offer\",\n        \"name\": \"Free regular coffee\",\n        \"description\": \"One regular coffee on us\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"AVAILABLE_TO_REDEEM\",\n        \"visibility_state\": \"unlocked\",\n        \"application_scope\": \"line\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"discount_amount_in_cents\": 550,\n        \"discount_application\": {\n          \"method\": \"negative_product_line\",\n          \"instructions\": \"To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id.\"\n        },\n        \"promotion\": {\n          \"id\": 901,\n          \"brand_id\": 42,\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"discount_type\": \"free_item\",\n          \"applies_to\": \"in_store\",\n          \"channel\": \"pos\",\n          \"location_id\": null,\n          \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n          \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n          \"created_at\": \"2025-12-01T00:00:00.000Z\",\n          \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n          \"availability\": [\n            \"connection_pages\",\n            \"lsk\",\n            \"lso\",\n            \"meandu\",\n            \"meandu_connect\",\n            \"shopify\"\n          ],\n          \"requirements\": {\n            \"products\": [],\n            \"categories\": [\n              {\n                \"id\": 10,\n                \"name\": \"Coffee\",\n                \"extended\": []\n              }\n            ]\n          },\n          \"targets\": {\n            \"products\": [\n              {\n                \"id\": 501,\n                \"name\": \"Flat white\",\n                \"extended\": [\n                  {\n                    \"source\": \"lightspeed-k\",\n                    \"external_id\": \"456\",\n                    \"json_data\": {},\n                    \"updated_at\": null\n                  }\n                ]\n              }\n            ],\n            \"categories\": []\n          }\n        }\n      }\n    ],\n    \"surface\": \"lsk\"\n  }\n}"
            },
            {
              "name": "LSK — eligible reward, wrong cart",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"basket_id\": \"pos-order-7f3a2c\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"901\",\n          \"type\": \"Offer\",\n          \"name\": \"Free regular coffee\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"UNAVAILABLE_TO_REDEEM\",\n          \"visibility_state\": \"unlocked\",\n          \"application_scope\": \"line\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"non_redeemable_cause\": {\n            \"code\": \"NO_MATCHING_PRODUCTS\",\n            \"message\": \"No matching products\"\n          },\n          \"discount_application\": {\n            \"method\": \"negative_product_line\",\n            \"instructions\": \"To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id.\"\n          },\n          \"promotion\": {\n            \"id\": 901,\n            \"brand_id\": 42,\n            \"name\": \"Free regular coffee\",\n            \"description\": \"One regular coffee on us\",\n            \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n            \"discount_type\": \"free_item\",\n            \"applies_to\": \"in_store\",\n            \"channel\": \"pos\",\n            \"location_id\": null,\n            \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n            \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n            \"created_at\": \"2025-12-01T00:00:00.000Z\",\n            \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n            \"availability\": [\n              \"connection_pages\",\n              \"lsk\",\n              \"lso\",\n              \"meandu\",\n              \"meandu_connect\",\n              \"shopify\"\n            ],\n            \"requirements\": {\n              \"products\": [],\n              \"categories\": [\n                {\n                  \"id\": 10,\n                  \"name\": \"Coffee\",\n                  \"extended\": []\n                }\n              ]\n            },\n            \"targets\": {\n              \"products\": [\n                {\n                  \"id\": 501,\n                  \"name\": \"Flat white\",\n                  \"extended\": [\n                    {\n                      \"source\": \"lightspeed-k\",\n                      \"external_id\": \"456\",\n                      \"json_data\": {},\n                      \"updated_at\": null\n                    }\n                  ]\n                }\n              ],\n              \"categories\": []\n            }\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"901\",\n        \"type\": \"Offer\",\n        \"name\": \"Free regular coffee\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"UNAVAILABLE_TO_REDEEM\",\n        \"visibility_state\": \"unlocked\",\n        \"application_scope\": \"line\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"non_redeemable_cause\": {\n          \"code\": \"NO_MATCHING_PRODUCTS\",\n          \"message\": \"No matching products\"\n        },\n        \"discount_application\": {\n          \"method\": \"negative_product_line\",\n          \"instructions\": \"To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id.\"\n        },\n        \"promotion\": {\n          \"id\": 901,\n          \"brand_id\": 42,\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"discount_type\": \"free_item\",\n          \"applies_to\": \"in_store\",\n          \"channel\": \"pos\",\n          \"location_id\": null,\n          \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n          \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n          \"created_at\": \"2025-12-01T00:00:00.000Z\",\n          \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n          \"availability\": [\n            \"connection_pages\",\n            \"lsk\",\n            \"lso\",\n            \"meandu\",\n            \"meandu_connect\",\n            \"shopify\"\n          ],\n          \"requirements\": {\n            \"products\": [],\n            \"categories\": [\n              {\n                \"id\": 10,\n                \"name\": \"Coffee\",\n                \"extended\": []\n              }\n            ]\n          },\n          \"targets\": {\n            \"products\": [\n              {\n                \"id\": 501,\n                \"name\": \"Flat white\",\n                \"extended\": [\n                  {\n                    \"source\": \"lightspeed-k\",\n                    \"external_id\": \"456\",\n                    \"json_data\": {},\n                    \"updated_at\": null\n                  }\n                ]\n              }\n            ],\n            \"categories\": []\n          }\n        }\n      }\n    ],\n    \"surface\": \"lsk\"\n  }\n}"
            },
            {
              "name": "LSK — Collect Across Venues stamps incomplete",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"basket_id\": \"pos-order-7f3a2c\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"901\",\n          \"type\": \"Offer\",\n          \"name\": \"Visit three venues\",\n          \"description\": \"Collect Across Venues passport\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"UNAVAILABLE_TO_REDEEM\",\n          \"visibility_state\": \"unlocked\",\n          \"application_scope\": \"order\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"venue_progress\": {\n            \"eligible\": false,\n            \"unlocked\": false,\n            \"collected_location_ids\": [\n              10\n            ],\n            \"collected_brand_ids\": [],\n            \"required_count\": 3,\n            \"stamp_set_size\": 3,\n            \"summary\": \"1 of 3 venues · 2 to go.\",\n            \"stamps\": [\n              {\n                \"id\": 10,\n                \"label\": \"Bondi\",\n                \"earned\": true,\n                \"kind\": \"location\"\n              },\n              {\n                \"id\": 11,\n                \"label\": \"Surry Hills\",\n                \"earned\": false,\n                \"kind\": \"location\"\n              },\n              {\n                \"id\": 12,\n                \"label\": \"Newtown\",\n                \"earned\": false,\n                \"kind\": \"location\"\n              }\n            ]\n          },\n          \"non_redeemable_cause\": {\n            \"code\": \"VENUE_UNLOCK_NOT_ELIGIBLE\",\n            \"message\": \"1 of 3 venues · 2 to go.\"\n          },\n          \"discount_application\": {\n            \"method\": \"negative_product_line\",\n            \"instructions\": \"To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id.\"\n          },\n          \"promotion\": {\n            \"id\": 901,\n            \"brand_id\": 42,\n            \"name\": \"Free regular coffee\",\n            \"description\": \"One regular coffee on us\",\n            \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n            \"discount_type\": \"free_item\",\n            \"applies_to\": \"in_store\",\n            \"channel\": \"pos\",\n            \"location_id\": null,\n            \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n            \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n            \"created_at\": \"2025-12-01T00:00:00.000Z\",\n            \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n            \"availability\": [\n              \"connection_pages\",\n              \"lsk\",\n              \"lso\",\n              \"meandu\",\n              \"meandu_connect\",\n              \"shopify\"\n            ],\n            \"requirements\": {\n              \"products\": [],\n              \"categories\": [\n                {\n                  \"id\": 10,\n                  \"name\": \"Coffee\",\n                  \"extended\": []\n                }\n              ]\n            },\n            \"targets\": {\n              \"products\": [\n                {\n                  \"id\": 501,\n                  \"name\": \"Flat white\",\n                  \"extended\": [\n                    {\n                      \"source\": \"lightspeed-k\",\n                      \"external_id\": \"456\",\n                      \"json_data\": {},\n                      \"updated_at\": null\n                    }\n                  ]\n                }\n              ],\n              \"categories\": []\n            }\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"901\",\n        \"type\": \"Offer\",\n        \"name\": \"Visit three venues\",\n        \"description\": \"Collect Across Venues passport\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"UNAVAILABLE_TO_REDEEM\",\n        \"visibility_state\": \"unlocked\",\n        \"application_scope\": \"order\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"venue_progress\": {\n          \"eligible\": false,\n          \"unlocked\": false,\n          \"collected_location_ids\": [\n            10\n          ],\n          \"collected_brand_ids\": [],\n          \"required_count\": 3,\n          \"stamp_set_size\": 3,\n          \"summary\": \"1 of 3 venues · 2 to go.\",\n          \"stamps\": [\n            {\n              \"id\": 10,\n              \"label\": \"Bondi\",\n              \"earned\": true,\n              \"kind\": \"location\"\n            },\n            {\n              \"id\": 11,\n              \"label\": \"Surry Hills\",\n              \"earned\": false,\n              \"kind\": \"location\"\n            },\n            {\n              \"id\": 12,\n              \"label\": \"Newtown\",\n              \"earned\": false,\n              \"kind\": \"location\"\n            }\n          ]\n        },\n        \"non_redeemable_cause\": {\n          \"code\": \"VENUE_UNLOCK_NOT_ELIGIBLE\",\n          \"message\": \"1 of 3 venues · 2 to go.\"\n        },\n        \"discount_application\": {\n          \"method\": \"negative_product_line\",\n          \"instructions\": \"To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id.\"\n        },\n        \"promotion\": {\n          \"id\": 901,\n          \"brand_id\": 42,\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"discount_type\": \"free_item\",\n          \"applies_to\": \"in_store\",\n          \"channel\": \"pos\",\n          \"location_id\": null,\n          \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n          \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n          \"created_at\": \"2025-12-01T00:00:00.000Z\",\n          \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n          \"availability\": [\n            \"connection_pages\",\n            \"lsk\",\n            \"lso\",\n            \"meandu\",\n            \"meandu_connect\",\n            \"shopify\"\n          ],\n          \"requirements\": {\n            \"products\": [],\n            \"categories\": [\n              {\n                \"id\": 10,\n                \"name\": \"Coffee\",\n                \"extended\": []\n              }\n            ]\n          },\n          \"targets\": {\n            \"products\": [\n              {\n                \"id\": 501,\n                \"name\": \"Flat white\",\n                \"extended\": [\n                  {\n                    \"source\": \"lightspeed-k\",\n                    \"external_id\": \"456\",\n                    \"json_data\": {},\n                    \"updated_at\": null\n                  }\n                ]\n              }\n            ],\n            \"categories\": []\n          }\n        }\n      }\n    ],\n    \"surface\": \"lsk\"\n  }\n}"
            },
            {
              "name": "LSK — must claim on the join page",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"basket_id\": \"pos-order-7f3a2c\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"901\",\n          \"type\": \"Offer\",\n          \"name\": \"Free regular coffee\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"UNAVAILABLE_TO_REDEEM\",\n          \"visibility_state\": \"unlocked\",\n          \"application_scope\": \"line\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"non_redeemable_cause\": {\n            \"code\": \"CLAIM_REQUIRED\",\n            \"message\": \"This offer must be claimed on the join page before it can be redeemed.\"\n          },\n          \"discount_application\": {\n            \"method\": \"negative_product_line\",\n            \"instructions\": \"To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id.\"\n          },\n          \"promotion\": {\n            \"id\": 901,\n            \"brand_id\": 42,\n            \"name\": \"Free regular coffee\",\n            \"description\": \"One regular coffee on us\",\n            \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n            \"discount_type\": \"free_item\",\n            \"applies_to\": \"in_store\",\n            \"channel\": \"pos\",\n            \"location_id\": null,\n            \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n            \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n            \"created_at\": \"2025-12-01T00:00:00.000Z\",\n            \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n            \"availability\": [\n              \"connection_pages\",\n              \"lsk\",\n              \"lso\",\n              \"meandu\",\n              \"meandu_connect\",\n              \"shopify\"\n            ],\n            \"requirements\": {\n              \"products\": [],\n              \"categories\": [\n                {\n                  \"id\": 10,\n                  \"name\": \"Coffee\",\n                  \"extended\": []\n                }\n              ]\n            },\n            \"targets\": {\n              \"products\": [\n                {\n                  \"id\": 501,\n                  \"name\": \"Flat white\",\n                  \"extended\": [\n                    {\n                      \"source\": \"lightspeed-k\",\n                      \"external_id\": \"456\",\n                      \"json_data\": {},\n                      \"updated_at\": null\n                    }\n                  ]\n                }\n              ],\n              \"categories\": []\n            }\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"901\",\n        \"type\": \"Offer\",\n        \"name\": \"Free regular coffee\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"UNAVAILABLE_TO_REDEEM\",\n        \"visibility_state\": \"unlocked\",\n        \"application_scope\": \"line\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"non_redeemable_cause\": {\n          \"code\": \"CLAIM_REQUIRED\",\n          \"message\": \"This offer must be claimed on the join page before it can be redeemed.\"\n        },\n        \"discount_application\": {\n          \"method\": \"negative_product_line\",\n          \"instructions\": \"To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id.\"\n        },\n        \"promotion\": {\n          \"id\": 901,\n          \"brand_id\": 42,\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"discount_type\": \"free_item\",\n          \"applies_to\": \"in_store\",\n          \"channel\": \"pos\",\n          \"location_id\": null,\n          \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n          \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n          \"created_at\": \"2025-12-01T00:00:00.000Z\",\n          \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n          \"availability\": [\n            \"connection_pages\",\n            \"lsk\",\n            \"lso\",\n            \"meandu\",\n            \"meandu_connect\",\n            \"shopify\"\n          ],\n          \"requirements\": {\n            \"products\": [],\n            \"categories\": [\n              {\n                \"id\": 10,\n                \"name\": \"Coffee\",\n                \"extended\": []\n              }\n            ]\n          },\n          \"targets\": {\n            \"products\": [\n              {\n                \"id\": 501,\n                \"name\": \"Flat white\",\n                \"extended\": [\n                  {\n                    \"source\": \"lightspeed-k\",\n                    \"external_id\": \"456\",\n                    \"json_data\": {},\n                    \"updated_at\": null\n                  }\n                ]\n              }\n            ],\n            \"categories\": []\n          }\n        }\n      }\n    ],\n    \"surface\": \"lsk\"\n  }\n}"
            },
            {
              "name": "LSK — first-X redemption cap exhausted",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"basket_id\": \"pos-order-7f3a2c\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"901\",\n          \"type\": \"Offer\",\n          \"name\": \"Free regular coffee\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"UNAVAILABLE_TO_REDEEM\",\n          \"visibility_state\": \"unlocked\",\n          \"application_scope\": \"line\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"non_redeemable_cause\": {\n            \"code\": \"TOTAL_REDEMPTION_LIMIT_REACHED\",\n            \"message\": \"Sorry, this offer is no longer available.\"\n          },\n          \"discount_application\": {\n            \"method\": \"negative_product_line\",\n            \"instructions\": \"To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id.\"\n          },\n          \"promotion\": {\n            \"id\": 901,\n            \"brand_id\": 42,\n            \"name\": \"Free regular coffee\",\n            \"description\": \"One regular coffee on us\",\n            \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n            \"discount_type\": \"free_item\",\n            \"applies_to\": \"in_store\",\n            \"channel\": \"pos\",\n            \"location_id\": null,\n            \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n            \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n            \"created_at\": \"2025-12-01T00:00:00.000Z\",\n            \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n            \"availability\": [\n              \"connection_pages\",\n              \"lsk\",\n              \"lso\",\n              \"meandu\",\n              \"meandu_connect\",\n              \"shopify\"\n            ],\n            \"requirements\": {\n              \"products\": [],\n              \"categories\": [\n                {\n                  \"id\": 10,\n                  \"name\": \"Coffee\",\n                  \"extended\": []\n                }\n              ]\n            },\n            \"targets\": {\n              \"products\": [\n                {\n                  \"id\": 501,\n                  \"name\": \"Flat white\",\n                  \"extended\": [\n                    {\n                      \"source\": \"lightspeed-k\",\n                      \"external_id\": \"456\",\n                      \"json_data\": {},\n                      \"updated_at\": null\n                    }\n                  ]\n                }\n              ],\n              \"categories\": []\n            }\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"901\",\n        \"type\": \"Offer\",\n        \"name\": \"Free regular coffee\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"UNAVAILABLE_TO_REDEEM\",\n        \"visibility_state\": \"unlocked\",\n        \"application_scope\": \"line\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"non_redeemable_cause\": {\n          \"code\": \"TOTAL_REDEMPTION_LIMIT_REACHED\",\n          \"message\": \"Sorry, this offer is no longer available.\"\n        },\n        \"discount_application\": {\n          \"method\": \"negative_product_line\",\n          \"instructions\": \"To take this discount off at the register: add a promotion product line priced at minus discount_amount_in_cents. Name the line after the reward. To lock the reward, send Apply a promotion with this promotion_id. Do not use the promotion id as a point of sale product id. After the customer has paid: if paid sales already go to myne, write MYNE and the order id on the order; otherwise send Complete promotions with that order id.\"\n        },\n        \"promotion\": {\n          \"id\": 901,\n          \"brand_id\": 42,\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"discount_type\": \"free_item\",\n          \"applies_to\": \"in_store\",\n          \"channel\": \"pos\",\n          \"location_id\": null,\n          \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n          \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n          \"created_at\": \"2025-12-01T00:00:00.000Z\",\n          \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n          \"availability\": [\n            \"connection_pages\",\n            \"lsk\",\n            \"lso\",\n            \"meandu\",\n            \"meandu_connect\",\n            \"shopify\"\n          ],\n          \"requirements\": {\n            \"products\": [],\n            \"categories\": [\n              {\n                \"id\": 10,\n                \"name\": \"Coffee\",\n                \"extended\": []\n              }\n            ]\n          },\n          \"targets\": {\n            \"products\": [\n              {\n                \"id\": 501,\n                \"name\": \"Flat white\",\n                \"extended\": [\n                  {\n                    \"source\": \"lightspeed-k\",\n                    \"external_id\": \"456\",\n                    \"json_data\": {},\n                    \"updated_at\": null\n                  }\n                ]\n              }\n            ],\n            \"categories\": []\n          }\n        }\n      }\n    ],\n    \"surface\": \"lsk\"\n  }\n}"
            },
            {
              "name": "LSO — order-level price variation",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"basket_id\": \"lso-order-991\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"940\",\n          \"type\": \"Offer\",\n          \"name\": \"$10 off order\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"AVAILABLE_TO_REDEEM\",\n          \"visibility_state\": \"unlocked\",\n          \"application_scope\": \"order\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"discount_amount_in_cents\": 1000,\n          \"discount_application\": {\n            \"method\": \"order_price_variation\",\n            \"instructions\": \"To take this discount off on an ordering ticket: apply an order-level price change of minus discount_amount_in_cents. Do not add a negative product line. To lock the reward, send Apply a promotion with this promotion_id and the same basket_id (order id). After the customer has paid, send Complete promotions with that same basket_id.\"\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"940\",\n        \"type\": \"Offer\",\n        \"name\": \"$10 off order\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"AVAILABLE_TO_REDEEM\",\n        \"visibility_state\": \"unlocked\",\n        \"application_scope\": \"order\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"discount_amount_in_cents\": 1000,\n        \"discount_application\": {\n          \"method\": \"order_price_variation\",\n          \"instructions\": \"To take this discount off on an ordering ticket: apply an order-level price change of minus discount_amount_in_cents. Do not add a negative product line. To lock the reward, send Apply a promotion with this promotion_id and the same basket_id (order id). After the customer has paid, send Complete promotions with that same basket_id.\"\n        }\n      }\n    ],\n    \"surface\": \"lso\"\n  }\n}"
            },
            {
              "name": "LSO — line-level price variation",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"basket_id\": \"lso-order-991\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"901\",\n          \"type\": \"Offer\",\n          \"name\": \"Free regular coffee\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"AVAILABLE_TO_REDEEM\",\n          \"visibility_state\": \"unlocked\",\n          \"application_scope\": \"line\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"discount_amount_in_cents\": 550,\n          \"order_discount\": {\n            \"amount_in_cents\": 550\n          },\n          \"line_discounts\": [\n            {\n              \"pos_id\": \"456\",\n              \"amount_in_cents\": 550,\n              \"quantity\": 1\n            }\n          ],\n          \"discount_application\": {\n            \"method\": \"line_price_variation\",\n            \"instructions\": \"To take this discount off on matching lines: when line_discounts is present, apply each entry to cart.items with the same pos_id. Use amount_in_cents on the line when quantity is omitted; when quantity is set, discount only that many units (leave any extra units at full price). Use discount_in_percent for a whole-line percentage when quantity covers the full line. Otherwise apply a line-level discount totaling discount_amount_in_cents. Do not use a negative promotion product. To lock the reward, send Apply a promotion with this promotion_id and the same basket_id. After the customer has paid, send Complete promotions with that same basket_id.\"\n          },\n          \"promotion\": {\n            \"id\": 901,\n            \"brand_id\": 42,\n            \"name\": \"Free regular coffee\",\n            \"description\": \"One regular coffee on us\",\n            \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n            \"discount_type\": \"free_item\",\n            \"applies_to\": \"in_store\",\n            \"channel\": \"pos\",\n            \"location_id\": null,\n            \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n            \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n            \"created_at\": \"2025-12-01T00:00:00.000Z\",\n            \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n            \"availability\": [\n              \"connection_pages\",\n              \"lsk\",\n              \"lso\",\n              \"meandu\",\n              \"meandu_connect\",\n              \"shopify\"\n            ],\n            \"requirements\": {\n              \"products\": [],\n              \"categories\": [\n                {\n                  \"id\": 10,\n                  \"name\": \"Coffee\",\n                  \"extended\": []\n                }\n              ]\n            },\n            \"targets\": {\n              \"products\": [\n                {\n                  \"id\": 501,\n                  \"name\": \"Flat white\",\n                  \"extended\": [\n                    {\n                      \"source\": \"lightspeed-k\",\n                      \"external_id\": \"456\",\n                      \"json_data\": {},\n                      \"updated_at\": null\n                    }\n                  ]\n                }\n              ],\n              \"categories\": []\n            }\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"901\",\n        \"type\": \"Offer\",\n        \"name\": \"Free regular coffee\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"AVAILABLE_TO_REDEEM\",\n        \"visibility_state\": \"unlocked\",\n        \"application_scope\": \"line\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"discount_amount_in_cents\": 550,\n        \"order_discount\": {\n          \"amount_in_cents\": 550\n        },\n        \"line_discounts\": [\n          {\n            \"pos_id\": \"456\",\n            \"amount_in_cents\": 550,\n            \"quantity\": 1\n          }\n        ],\n        \"discount_application\": {\n          \"method\": \"line_price_variation\",\n          \"instructions\": \"To take this discount off on matching lines: when line_discounts is present, apply each entry to cart.items with the same pos_id. Use amount_in_cents on the line when quantity is omitted; when quantity is set, discount only that many units (leave any extra units at full price). Use discount_in_percent for a whole-line percentage when quantity covers the full line. Otherwise apply a line-level discount totaling discount_amount_in_cents. Do not use a negative promotion product. To lock the reward, send Apply a promotion with this promotion_id and the same basket_id. After the customer has paid, send Complete promotions with that same basket_id.\"\n        },\n        \"promotion\": {\n          \"id\": 901,\n          \"brand_id\": 42,\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"discount_type\": \"free_item\",\n          \"applies_to\": \"in_store\",\n          \"channel\": \"pos\",\n          \"location_id\": null,\n          \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n          \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n          \"created_at\": \"2025-12-01T00:00:00.000Z\",\n          \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n          \"availability\": [\n            \"connection_pages\",\n            \"lsk\",\n            \"lso\",\n            \"meandu\",\n            \"meandu_connect\",\n            \"shopify\"\n          ],\n          \"requirements\": {\n            \"products\": [],\n            \"categories\": [\n              {\n                \"id\": 10,\n                \"name\": \"Coffee\",\n                \"extended\": []\n              }\n            ]\n          },\n          \"targets\": {\n            \"products\": [\n              {\n                \"id\": 501,\n                \"name\": \"Flat white\",\n                \"extended\": [\n                  {\n                    \"source\": \"lightspeed-k\",\n                    \"external_id\": \"456\",\n                    \"json_data\": {},\n                    \"updated_at\": null\n                  }\n                ]\n              }\n            ],\n            \"categories\": []\n          }\n        }\n      }\n    ],\n    \"surface\": \"lso\"\n  }\n}"
            },
            {
              "name": "meandu — available without discount amount",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"901\",\n          \"type\": \"Offer\",\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"AVAILABLE_TO_REDEEM\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"discount_application\": {\n            \"method\": \"ordering_platform\",\n            \"instructions\": \"Connect Apply a promotion is not available for surface=meandu. Use the ordering platform redemption path. discount_amount_in_cents appears on SELECTED rewards only for this surface.\"\n          },\n          \"promotion\": {\n            \"id\": 901,\n            \"brand_id\": 42,\n            \"name\": \"Free regular coffee\",\n            \"description\": \"One regular coffee on us\",\n            \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n            \"discount_type\": \"free_item\",\n            \"applies_to\": \"in_store\",\n            \"channel\": \"pos\",\n            \"location_id\": null,\n            \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n            \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n            \"created_at\": \"2025-12-01T00:00:00.000Z\",\n            \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n            \"availability\": [\n              \"connection_pages\",\n              \"lsk\",\n              \"lso\",\n              \"meandu\",\n              \"meandu_connect\",\n              \"shopify\"\n            ],\n            \"requirements\": {\n              \"products\": [],\n              \"categories\": [\n                {\n                  \"id\": 10,\n                  \"name\": \"Coffee\",\n                  \"extended\": []\n                }\n              ]\n            },\n            \"targets\": {\n              \"products\": [\n                {\n                  \"id\": 501,\n                  \"name\": \"Flat white\",\n                  \"extended\": [\n                    {\n                      \"source\": \"lightspeed-k\",\n                      \"external_id\": \"456\",\n                      \"json_data\": {},\n                      \"updated_at\": null\n                    }\n                  ]\n                }\n              ],\n              \"categories\": []\n            }\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"901\",\n        \"type\": \"Offer\",\n        \"name\": \"Free regular coffee\",\n        \"description\": \"One regular coffee on us\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"AVAILABLE_TO_REDEEM\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"discount_application\": {\n          \"method\": \"ordering_platform\",\n          \"instructions\": \"Connect Apply a promotion is not available for surface=meandu. Use the ordering platform redemption path. discount_amount_in_cents appears on SELECTED rewards only for this surface.\"\n        },\n        \"promotion\": {\n          \"id\": 901,\n          \"brand_id\": 42,\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"discount_type\": \"free_item\",\n          \"applies_to\": \"in_store\",\n          \"channel\": \"pos\",\n          \"location_id\": null,\n          \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n          \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n          \"created_at\": \"2025-12-01T00:00:00.000Z\",\n          \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n          \"availability\": [\n            \"connection_pages\",\n            \"lsk\",\n            \"lso\",\n            \"meandu\",\n            \"meandu_connect\",\n            \"shopify\"\n          ],\n          \"requirements\": {\n            \"products\": [],\n            \"categories\": [\n              {\n                \"id\": 10,\n                \"name\": \"Coffee\",\n                \"extended\": []\n              }\n            ]\n          },\n          \"targets\": {\n            \"products\": [\n              {\n                \"id\": 501,\n                \"name\": \"Flat white\",\n                \"extended\": [\n                  {\n                    \"source\": \"lightspeed-k\",\n                    \"external_id\": \"456\",\n                    \"json_data\": {},\n                    \"updated_at\": null\n                  }\n                ]\n              }\n            ],\n            \"categories\": []\n          }\n        }\n      }\n    ],\n    \"surface\": \"meandu\"\n  }\n}"
            },
            {
              "name": "400 Invalid body (missing customer/order lines, invalid order lines in cart.items, source passed instead of surface, or invalid surface).",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Do not pass source. Use surface for the rewards channel.\"\n}"
            },
            {
              "name": "404 Customer not found for this brand.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            },
            {
              "name": "502 Promotions engine unavailable or rejected the request.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 1250,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"business_location_id\": \"{{location_external_id}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 502,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions engine unavailable. Please try again shortly.\"\n}"
            }
          ]
        },
        {
          "name": "Evaluate promotions (LSO — ordering)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "promotions",
                "evaluate"
              ]
            },
            "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lso\",\n  \"basket_id\": \"lso-order-991\",\n  \"venue_id\": \"venue-42\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 4500,\n        \"quantity\": 1\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "LSO — order-level price variation",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lso\",\n  \"basket_id\": \"lso-order-991\",\n  \"venue_id\": \"venue-42\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 4500,\n        \"quantity\": 1\n      }\n    ]\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"basket_id\": \"lso-order-991\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"940\",\n          \"type\": \"Offer\",\n          \"name\": \"$10 off order\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"AVAILABLE_TO_REDEEM\",\n          \"visibility_state\": \"unlocked\",\n          \"application_scope\": \"order\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"discount_amount_in_cents\": 1000,\n          \"discount_application\": {\n            \"method\": \"order_price_variation\",\n            \"instructions\": \"To take this discount off on an ordering ticket: apply an order-level price change of minus discount_amount_in_cents. Do not add a negative product line. To lock the reward, send Apply a promotion with this promotion_id and the same basket_id (order id). After the customer has paid, send Complete promotions with that same basket_id.\"\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"940\",\n        \"type\": \"Offer\",\n        \"name\": \"$10 off order\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"AVAILABLE_TO_REDEEM\",\n        \"visibility_state\": \"unlocked\",\n        \"application_scope\": \"order\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"discount_amount_in_cents\": 1000,\n        \"discount_application\": {\n          \"method\": \"order_price_variation\",\n          \"instructions\": \"To take this discount off on an ordering ticket: apply an order-level price change of minus discount_amount_in_cents. Do not add a negative product line. To lock the reward, send Apply a promotion with this promotion_id and the same basket_id (order id). After the customer has paid, send Complete promotions with that same basket_id.\"\n        }\n      }\n    ],\n    \"surface\": \"lso\"\n  }\n}"
            },
            {
              "name": "LSO — line-level price variation",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"lso\",\n  \"basket_id\": \"lso-order-991\",\n  \"venue_id\": \"venue-42\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 4500,\n        \"quantity\": 1\n      }\n    ]\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"basket_id\": \"lso-order-991\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"901\",\n          \"type\": \"Offer\",\n          \"name\": \"Free regular coffee\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"AVAILABLE_TO_REDEEM\",\n          \"visibility_state\": \"unlocked\",\n          \"application_scope\": \"line\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"discount_amount_in_cents\": 550,\n          \"order_discount\": {\n            \"amount_in_cents\": 550\n          },\n          \"line_discounts\": [\n            {\n              \"pos_id\": \"456\",\n              \"amount_in_cents\": 550,\n              \"quantity\": 1\n            }\n          ],\n          \"discount_application\": {\n            \"method\": \"line_price_variation\",\n            \"instructions\": \"To take this discount off on matching lines: when line_discounts is present, apply each entry to cart.items with the same pos_id. Use amount_in_cents on the line when quantity is omitted; when quantity is set, discount only that many units (leave any extra units at full price). Use discount_in_percent for a whole-line percentage when quantity covers the full line. Otherwise apply a line-level discount totaling discount_amount_in_cents. Do not use a negative promotion product. To lock the reward, send Apply a promotion with this promotion_id and the same basket_id. After the customer has paid, send Complete promotions with that same basket_id.\"\n          },\n          \"promotion\": {\n            \"id\": 901,\n            \"brand_id\": 42,\n            \"name\": \"Free regular coffee\",\n            \"description\": \"One regular coffee on us\",\n            \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n            \"discount_type\": \"free_item\",\n            \"applies_to\": \"in_store\",\n            \"channel\": \"pos\",\n            \"location_id\": null,\n            \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n            \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n            \"created_at\": \"2025-12-01T00:00:00.000Z\",\n            \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n            \"availability\": [\n              \"connection_pages\",\n              \"lsk\",\n              \"lso\",\n              \"meandu\",\n              \"meandu_connect\",\n              \"shopify\"\n            ],\n            \"requirements\": {\n              \"products\": [],\n              \"categories\": [\n                {\n                  \"id\": 10,\n                  \"name\": \"Coffee\",\n                  \"extended\": []\n                }\n              ]\n            },\n            \"targets\": {\n              \"products\": [\n                {\n                  \"id\": 501,\n                  \"name\": \"Flat white\",\n                  \"extended\": [\n                    {\n                      \"source\": \"lightspeed-k\",\n                      \"external_id\": \"456\",\n                      \"json_data\": {},\n                      \"updated_at\": null\n                    }\n                  ]\n                }\n              ],\n              \"categories\": []\n            }\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"901\",\n        \"type\": \"Offer\",\n        \"name\": \"Free regular coffee\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"AVAILABLE_TO_REDEEM\",\n        \"visibility_state\": \"unlocked\",\n        \"application_scope\": \"line\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"discount_amount_in_cents\": 550,\n        \"order_discount\": {\n          \"amount_in_cents\": 550\n        },\n        \"line_discounts\": [\n          {\n            \"pos_id\": \"456\",\n            \"amount_in_cents\": 550,\n            \"quantity\": 1\n          }\n        ],\n        \"discount_application\": {\n          \"method\": \"line_price_variation\",\n          \"instructions\": \"To take this discount off on matching lines: when line_discounts is present, apply each entry to cart.items with the same pos_id. Use amount_in_cents on the line when quantity is omitted; when quantity is set, discount only that many units (leave any extra units at full price). Use discount_in_percent for a whole-line percentage when quantity covers the full line. Otherwise apply a line-level discount totaling discount_amount_in_cents. Do not use a negative promotion product. To lock the reward, send Apply a promotion with this promotion_id and the same basket_id. After the customer has paid, send Complete promotions with that same basket_id.\"\n        },\n        \"promotion\": {\n          \"id\": 901,\n          \"brand_id\": 42,\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"discount_type\": \"free_item\",\n          \"applies_to\": \"in_store\",\n          \"channel\": \"pos\",\n          \"location_id\": null,\n          \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n          \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n          \"created_at\": \"2025-12-01T00:00:00.000Z\",\n          \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n          \"availability\": [\n            \"connection_pages\",\n            \"lsk\",\n            \"lso\",\n            \"meandu\",\n            \"meandu_connect\",\n            \"shopify\"\n          ],\n          \"requirements\": {\n            \"products\": [],\n            \"categories\": [\n              {\n                \"id\": 10,\n                \"name\": \"Coffee\",\n                \"extended\": []\n              }\n            ]\n          },\n          \"targets\": {\n            \"products\": [\n              {\n                \"id\": 501,\n                \"name\": \"Flat white\",\n                \"extended\": [\n                  {\n                    \"source\": \"lightspeed-k\",\n                    \"external_id\": \"456\",\n                    \"json_data\": {},\n                    \"updated_at\": null\n                  }\n                ]\n              }\n            ],\n            \"categories\": []\n          }\n        }\n      }\n    ],\n    \"surface\": \"lso\"\n  }\n}"
            }
          ]
        },
        {
          "name": "Evaluate promotions (meandu — eligibility)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "promotions",
                "evaluate"
              ]
            },
            "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"meandu\",\n  \"venue_id\": \"meandu-venue-1\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "meandu — available without discount amount",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/evaluate",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "evaluate"
                  ]
                },
                "description": "Step **1** in the Promotions section (**Check what fits**). This call only returns which rewards fit. It does not lock a reward. Read that life cycle first; this page lists the fields for this call.\n\nWe need: who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), the open-order lines (each with the **Order line pos_id** for this **Surface**), and where you are selling (`lsk` for register, `lso` for ordering). This is an open-order call: send `surface` for the app. Do not send `integration_type` (that field is only for listing promotions without a basket). Send your order id (**basket_id and MYNE reference**) so later calls use the same order. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns which rewards fit and how much to take off. Each reward includes `image_url` (the offer image, or null) and a nested `promotion`. Unavailable rewards include `non_redeemable_cause`. Join-page claim uses `CLAIM_REQUIRED`. An exhausted first-X redemption cap uses `TOTAL_REDEMPTION_LIMIT_REACHED` (POS surfaces keep `MAXIMUM_USES_REACHED` for the same limit). Next: take the money off in your POS (step 2), **Apply a promotion** (step 3), then record the redemption when the customer has paid (steps 4–5).\n\nSee glossary: Surface, Order line pos_id, basket_id and MYNE reference, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"surface\": \"meandu\",\n  \"venue_id\": \"meandu-venue-1\",\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  }\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions evaluated successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"membership\": {\n      \"id\": \"104823\",\n      \"points_balance\": 120,\n      \"rewards\": [\n        {\n          \"id\": \"901\",\n          \"type\": \"Offer\",\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"status\": \"AVAILABLE_TO_REDEEM\",\n          \"cart_application_method\": \"MANUAL_APPLY\",\n          \"discount_application\": {\n            \"method\": \"ordering_platform\",\n            \"instructions\": \"Connect Apply a promotion is not available for surface=meandu. Use the ordering platform redemption path. discount_amount_in_cents appears on SELECTED rewards only for this surface.\"\n          },\n          \"promotion\": {\n            \"id\": 901,\n            \"brand_id\": 42,\n            \"name\": \"Free regular coffee\",\n            \"description\": \"One regular coffee on us\",\n            \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n            \"discount_type\": \"free_item\",\n            \"applies_to\": \"in_store\",\n            \"channel\": \"pos\",\n            \"location_id\": null,\n            \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n            \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n            \"created_at\": \"2025-12-01T00:00:00.000Z\",\n            \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n            \"availability\": [\n              \"connection_pages\",\n              \"lsk\",\n              \"lso\",\n              \"meandu\",\n              \"meandu_connect\",\n              \"shopify\"\n            ],\n            \"requirements\": {\n              \"products\": [],\n              \"categories\": [\n                {\n                  \"id\": 10,\n                  \"name\": \"Coffee\",\n                  \"extended\": []\n                }\n              ]\n            },\n            \"targets\": {\n              \"products\": [\n                {\n                  \"id\": 501,\n                  \"name\": \"Flat white\",\n                  \"extended\": [\n                    {\n                      \"source\": \"lightspeed-k\",\n                      \"external_id\": \"456\",\n                      \"json_data\": {},\n                      \"updated_at\": null\n                    }\n                  ]\n                }\n              ],\n              \"categories\": []\n            }\n          }\n        }\n      ]\n    },\n    \"rewards\": [\n      {\n        \"id\": \"901\",\n        \"type\": \"Offer\",\n        \"name\": \"Free regular coffee\",\n        \"description\": \"One regular coffee on us\",\n        \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n        \"status\": \"AVAILABLE_TO_REDEEM\",\n        \"cart_application_method\": \"MANUAL_APPLY\",\n        \"discount_application\": {\n          \"method\": \"ordering_platform\",\n          \"instructions\": \"Connect Apply a promotion is not available for surface=meandu. Use the ordering platform redemption path. discount_amount_in_cents appears on SELECTED rewards only for this surface.\"\n        },\n        \"promotion\": {\n          \"id\": 901,\n          \"brand_id\": 42,\n          \"name\": \"Free regular coffee\",\n          \"description\": \"One regular coffee on us\",\n          \"image_url\": \"https://assets.myne.network/images/demo/free-coffee.png\",\n          \"discount_type\": \"free_item\",\n          \"applies_to\": \"in_store\",\n          \"channel\": \"pos\",\n          \"location_id\": null,\n          \"valid_from\": \"2026-01-01T00:00:00.000Z\",\n          \"valid_until\": \"2026-12-31T23:59:59.000Z\",\n          \"created_at\": \"2025-12-01T00:00:00.000Z\",\n          \"updated_at\": \"2026-06-01T00:00:00.000Z\",\n          \"availability\": [\n            \"connection_pages\",\n            \"lsk\",\n            \"lso\",\n            \"meandu\",\n            \"meandu_connect\",\n            \"shopify\"\n          ],\n          \"requirements\": {\n            \"products\": [],\n            \"categories\": [\n              {\n                \"id\": 10,\n                \"name\": \"Coffee\",\n                \"extended\": []\n              }\n            ]\n          },\n          \"targets\": {\n            \"products\": [\n              {\n                \"id\": 501,\n                \"name\": \"Flat white\",\n                \"extended\": [\n                  {\n                    \"source\": \"lightspeed-k\",\n                    \"external_id\": \"456\",\n                    \"json_data\": {},\n                    \"updated_at\": null\n                  }\n                ]\n              }\n            ],\n            \"categories\": []\n          }\n        }\n      }\n    ],\n    \"surface\": \"meandu\"\n  }\n}"
            }
          ]
        },
        {
          "name": "Apply a promotion (lock the reward on this open order)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/apply",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "promotions",
                "apply"
              ]
            },
            "description": "This is **Apply a promotion** — step **3** in the Promotions section (**Lock the reward**). This call applies the chosen promotion to the open order (locks it). It does not take money off the ticket (step 2) and it does not record the redemption (step 5). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the **Surface** (`lsk` for register, `lso` for ordering), who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), and which **Promotion** they are using (`promotion_id`). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Send the same order id (**basket_id and MYNE reference**) from step 1 when you have one. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns the order id to keep using (`basket_id`; if you omitted it, this is myne’s `selection_id`). The promotion is applied (locked), not redeemed. If the offer must be claimed on the join page first, apply rejects with `code` `CLAIM_REQUIRED`. If the first-X redemption cap is gone, apply rejects with `TOTAL_REDEMPTION_LIMIT_REACHED`. Then either mark the order so myne records the redemption when the paid sale arrives (steps 4–5), or send **Complete promotions** when the customer has paid (skip step 4).\n\n**Promotion stacking** is covered in the Promotions life cycle (step 3).\n\nSee glossary: basket_id and MYNE reference, Promotion stacking, Surface, Promotion, External source.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"promotion_id\": {{promotion_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Promotion applied (locked) on the open order. basket_id is your order id when you sent one, otherwise myne’s selection id. selection_id is myne’s internal id for this apply. This call does not record the redemption. Then record the redemption in one way: mark the order (prefix MYNE) if paid sales already go to myne, or send Complete promotions when the customer has paid. Multiple applies on one open order follow promotion stacking rules.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/apply",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "apply"
                  ]
                },
                "description": "This is **Apply a promotion** — step **3** in the Promotions section (**Lock the reward**). This call applies the chosen promotion to the open order (locks it). It does not take money off the ticket (step 2) and it does not record the redemption (step 5). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the **Surface** (`lsk` for register, `lso` for ordering), who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), and which **Promotion** they are using (`promotion_id`). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Send the same order id (**basket_id and MYNE reference**) from step 1 when you have one. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns the order id to keep using (`basket_id`; if you omitted it, this is myne’s `selection_id`). The promotion is applied (locked), not redeemed. If the offer must be claimed on the join page first, apply rejects with `code` `CLAIM_REQUIRED`. If the first-X redemption cap is gone, apply rejects with `TOTAL_REDEMPTION_LIMIT_REACHED`. Then either mark the order so myne records the redemption when the paid sale arrives (steps 4–5), or send **Complete promotions** when the customer has paid (skip step 4).\n\n**Promotion stacking** is covered in the Promotions life cycle (step 3).\n\nSee glossary: basket_id and MYNE reference, Promotion stacking, Surface, Promotion, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"promotion_id\": {{promotion_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotion applied to open order successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"basket_id\": 9001,\n    \"selection_id\": 9001,\n    \"promotion_id\": 901,\n    \"surface\": \"lsk\",\n    \"external_reference_prefix\": \"MYNE\"\n  }\n}"
            },
            {
              "name": "400 Invalid body, missing surface, promotion not on this surface or location, or apply rejected (e.g. points inactive).",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/apply",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "apply"
                  ]
                },
                "description": "This is **Apply a promotion** — step **3** in the Promotions section (**Lock the reward**). This call applies the chosen promotion to the open order (locks it). It does not take money off the ticket (step 2) and it does not record the redemption (step 5). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the **Surface** (`lsk` for register, `lso` for ordering), who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), and which **Promotion** they are using (`promotion_id`). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Send the same order id (**basket_id and MYNE reference**) from step 1 when you have one. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns the order id to keep using (`basket_id`; if you omitted it, this is myne’s `selection_id`). The promotion is applied (locked), not redeemed. If the offer must be claimed on the join page first, apply rejects with `code` `CLAIM_REQUIRED`. If the first-X redemption cap is gone, apply rejects with `TOTAL_REDEMPTION_LIMIT_REACHED`. Then either mark the order so myne records the redemption when the paid sale arrives (steps 4–5), or send **Complete promotions** when the customer has paid (skip step 4).\n\n**Promotion stacking** is covered in the Promotions life cycle (step 3).\n\nSee glossary: basket_id and MYNE reference, Promotion stacking, Surface, Promotion, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"promotion_id\": {{promotion_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"surface must be lsk or lso to apply a promotion (open order + basket_id ref)\"\n}"
            },
            {
              "name": "Must claim on the join page",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/apply",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "apply"
                  ]
                },
                "description": "This is **Apply a promotion** — step **3** in the Promotions section (**Lock the reward**). This call applies the chosen promotion to the open order (locks it). It does not take money off the ticket (step 2) and it does not record the redemption (step 5). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the **Surface** (`lsk` for register, `lso` for ordering), who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), and which **Promotion** they are using (`promotion_id`). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Send the same order id (**basket_id and MYNE reference**) from step 1 when you have one. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns the order id to keep using (`basket_id`; if you omitted it, this is myne’s `selection_id`). The promotion is applied (locked), not redeemed. If the offer must be claimed on the join page first, apply rejects with `code` `CLAIM_REQUIRED`. If the first-X redemption cap is gone, apply rejects with `TOTAL_REDEMPTION_LIMIT_REACHED`. Then either mark the order so myne records the redemption when the paid sale arrives (steps 4–5), or send **Complete promotions** when the customer has paid (skip step 4).\n\n**Promotion stacking** is covered in the Promotions life cycle (step 3).\n\nSee glossary: basket_id and MYNE reference, Promotion stacking, Surface, Promotion, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"promotion_id\": {{promotion_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"This offer must be claimed on the join page before it can be redeemed.\",\n  \"code\": \"CLAIM_REQUIRED\"\n}"
            },
            {
              "name": "First-X redemption cap exhausted",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/apply",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "apply"
                  ]
                },
                "description": "This is **Apply a promotion** — step **3** in the Promotions section (**Lock the reward**). This call applies the chosen promotion to the open order (locks it). It does not take money off the ticket (step 2) and it does not record the redemption (step 5). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the **Surface** (`lsk` for register, `lso` for ordering), who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), and which **Promotion** they are using (`promotion_id`). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Send the same order id (**basket_id and MYNE reference**) from step 1 when you have one. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns the order id to keep using (`basket_id`; if you omitted it, this is myne’s `selection_id`). The promotion is applied (locked), not redeemed. If the offer must be claimed on the join page first, apply rejects with `code` `CLAIM_REQUIRED`. If the first-X redemption cap is gone, apply rejects with `TOTAL_REDEMPTION_LIMIT_REACHED`. Then either mark the order so myne records the redemption when the paid sale arrives (steps 4–5), or send **Complete promotions** when the customer has paid (skip step 4).\n\n**Promotion stacking** is covered in the Promotions life cycle (step 3).\n\nSee glossary: basket_id and MYNE reference, Promotion stacking, Surface, Promotion, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"promotion_id\": {{promotion_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Sorry, this offer is no longer available.\",\n  \"code\": \"TOTAL_REDEMPTION_LIMIT_REACHED\"\n}"
            },
            {
              "name": "404 Customer or promotion not found for this brand.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/apply",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "apply"
                  ]
                },
                "description": "This is **Apply a promotion** — step **3** in the Promotions section (**Lock the reward**). This call applies the chosen promotion to the open order (locks it). It does not take money off the ticket (step 2) and it does not record the redemption (step 5). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the **Surface** (`lsk` for register, `lso` for ordering), who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), and which **Promotion** they are using (`promotion_id`). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Send the same order id (**basket_id and MYNE reference**) from step 1 when you have one. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns the order id to keep using (`basket_id`; if you omitted it, this is myne’s `selection_id`). The promotion is applied (locked), not redeemed. If the offer must be claimed on the join page first, apply rejects with `code` `CLAIM_REQUIRED`. If the first-X redemption cap is gone, apply rejects with `TOTAL_REDEMPTION_LIMIT_REACHED`. Then either mark the order so myne records the redemption when the paid sale arrives (steps 4–5), or send **Complete promotions** when the customer has paid (skip step 4).\n\n**Promotion stacking** is covered in the Promotions life cycle (step 3).\n\nSee glossary: basket_id and MYNE reference, Promotion stacking, Surface, Promotion, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"promotion_id\": {{promotion_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer or promotion not found\"\n}"
            },
            {
              "name": "409 Insufficient points for this promotion.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/apply",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "apply"
                  ]
                },
                "description": "This is **Apply a promotion** — step **3** in the Promotions section (**Lock the reward**). This call applies the chosen promotion to the open order (locks it). It does not take money off the ticket (step 2) and it does not record the redemption (step 5). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the **Surface** (`lsk` for register, `lso` for ordering), who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), and which **Promotion** they are using (`promotion_id`). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Send the same order id (**basket_id and MYNE reference**) from step 1 when you have one. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns the order id to keep using (`basket_id`; if you omitted it, this is myne’s `selection_id`). The promotion is applied (locked), not redeemed. If the offer must be claimed on the join page first, apply rejects with `code` `CLAIM_REQUIRED`. If the first-X redemption cap is gone, apply rejects with `TOTAL_REDEMPTION_LIMIT_REACHED`. Then either mark the order so myne records the redemption when the paid sale arrives (steps 4–5), or send **Complete promotions** when the customer has paid (skip step 4).\n\n**Promotion stacking** is covered in the Promotions life cycle (step 3).\n\nSee glossary: basket_id and MYNE reference, Promotion stacking, Surface, Promotion, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"promotion_id\": {{promotion_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 409,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Insufficient points to redeem this promotion\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/apply",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "apply"
                  ]
                },
                "description": "This is **Apply a promotion** — step **3** in the Promotions section (**Lock the reward**). This call applies the chosen promotion to the open order (locks it). It does not take money off the ticket (step 2) and it does not record the redemption (step 5). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the **Surface** (`lsk` for register, `lso` for ordering), who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), and which **Promotion** they are using (`promotion_id`). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Send the same order id (**basket_id and MYNE reference**) from step 1 when you have one. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns the order id to keep using (`basket_id`; if you omitted it, this is myne’s `selection_id`). The promotion is applied (locked), not redeemed. If the offer must be claimed on the join page first, apply rejects with `code` `CLAIM_REQUIRED`. If the first-X redemption cap is gone, apply rejects with `TOTAL_REDEMPTION_LIMIT_REACHED`. Then either mark the order so myne records the redemption when the paid sale arrives (steps 4–5), or send **Complete promotions** when the customer has paid (skip step 4).\n\n**Promotion stacking** is covered in the Promotions life cycle (step 3).\n\nSee glossary: basket_id and MYNE reference, Promotion stacking, Surface, Promotion, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"promotion_id\": {{promotion_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            },
            {
              "name": "502 Promotions engine unavailable or rejected the request.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/apply",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "apply"
                  ]
                },
                "description": "This is **Apply a promotion** — step **3** in the Promotions section (**Lock the reward**). This call applies the chosen promotion to the open order (locks it). It does not take money off the ticket (step 2) and it does not record the redemption (step 5). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the **Surface** (`lsk` for register, `lso` for ordering), who the customer is (`customer_id`, or an `external_id` you already synced under your **External source**), and which **Promotion** they are using (`promotion_id`). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Send the same order id (**basket_id and MYNE reference**) from step 1 when you have one. Send a venue or location id when the offer depends on place.\n\nWhat happens: myne returns the order id to keep using (`basket_id`; if you omitted it, this is myne’s `selection_id`). The promotion is applied (locked), not redeemed. If the offer must be claimed on the join page first, apply rejects with `code` `CLAIM_REQUIRED`. If the first-X redemption cap is gone, apply rejects with `TOTAL_REDEMPTION_LIMIT_REACHED`. Then either mark the order so myne records the redemption when the paid sale arrives (steps 4–5), or send **Complete promotions** when the customer has paid (skip step 4).\n\n**Promotion stacking** is covered in the Promotions life cycle (step 3).\n\nSee glossary: basket_id and MYNE reference, Promotion stacking, Surface, Promotion, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"promotion_id\": {{promotion_id}},\n  \"surface\": \"lsk\",\n  \"basket_id\": \"pos-order-7f3a2c\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 502,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions engine unavailable. Please try again shortly.\"\n}"
            }
          ]
        },
        {
          "name": "Record the redemption after the customer has paid",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/complete",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "promotions",
                "complete"
              ]
            },
            "description": "Step **5** in the Promotions section (**Record the redemption when the customer has paid**), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the same **basket_id** from apply, who the customer is, the **Surface** (`lsk` or `lso`), and the paid product lines (each with the **Order line pos_id** — not the discount line). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Optional `discounts[]` amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.\n\nWhat happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, `redeemed_count` is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with `CLAIM_REQUIRED` instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.\n\nSee glossary: basket_id and MYNE reference, Surface, Transaction.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"surface\": \"lsk\",\n  \"location_id\": {{location_id}},\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"discounts\": [\n    {\n      \"promotion_id\": {{promotion_id}},\n      \"amount_in_cents\": 550\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "Customer paid — redemption recorded",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/complete",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "complete"
                  ]
                },
                "description": "Step **5** in the Promotions section (**Record the redemption when the customer has paid**), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the same **basket_id** from apply, who the customer is, the **Surface** (`lsk` or `lso`), and the paid product lines (each with the **Order line pos_id** — not the discount line). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Optional `discounts[]` amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.\n\nWhat happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, `redeemed_count` is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with `CLAIM_REQUIRED` instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.\n\nSee glossary: basket_id and MYNE reference, Surface, Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"surface\": \"lsk\",\n  \"location_id\": {{location_id}},\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"discounts\": [\n    {\n      \"promotion_id\": {{promotion_id}},\n      \"amount_in_cents\": 550\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions completed for this order successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"redeemed_count\": 1,\n    \"promotion_ids\": [\n      901\n    ],\n    \"basket_id\": \"pos-order-7f3a2c\",\n    \"surface\": \"lsk\"\n  }\n}"
            },
            {
              "name": "Already recorded — nothing locked",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/complete",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "complete"
                  ]
                },
                "description": "Step **5** in the Promotions section (**Record the redemption when the customer has paid**), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the same **basket_id** from apply, who the customer is, the **Surface** (`lsk` or `lso`), and the paid product lines (each with the **Order line pos_id** — not the discount line). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Optional `discounts[]` amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.\n\nWhat happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, `redeemed_count` is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with `CLAIM_REQUIRED` instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.\n\nSee glossary: basket_id and MYNE reference, Surface, Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"surface\": \"lsk\",\n  \"location_id\": {{location_id}},\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"discounts\": [\n    {\n      \"promotion_id\": {{promotion_id}},\n      \"amount_in_cents\": 550\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions completed for this order successfully\",\n  \"response\": {\n    \"status\": \"ok\",\n    \"redeemed_count\": 0,\n    \"promotion_ids\": [],\n    \"basket_id\": \"pos-order-7f3a2c\",\n    \"surface\": \"lsk\"\n  }\n}"
            },
            {
              "name": "400 Invalid body (missing basket_id or cart, source passed instead of surface, or surface not lsk/lso).",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/complete",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "complete"
                  ]
                },
                "description": "Step **5** in the Promotions section (**Record the redemption when the customer has paid**), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the same **basket_id** from apply, who the customer is, the **Surface** (`lsk` or `lso`), and the paid product lines (each with the **Order line pos_id** — not the discount line). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Optional `discounts[]` amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.\n\nWhat happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, `redeemed_count` is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with `CLAIM_REQUIRED` instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.\n\nSee glossary: basket_id and MYNE reference, Surface, Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"surface\": \"lsk\",\n  \"location_id\": {{location_id}},\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"discounts\": [\n    {\n      \"promotion_id\": {{promotion_id}},\n      \"amount_in_cents\": 550\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Do not pass source. Use surface for the rewards channel.\"\n}"
            },
            {
              "name": "Must claim on the join page",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/complete",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "complete"
                  ]
                },
                "description": "Step **5** in the Promotions section (**Record the redemption when the customer has paid**), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the same **basket_id** from apply, who the customer is, the **Surface** (`lsk` or `lso`), and the paid product lines (each with the **Order line pos_id** — not the discount line). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Optional `discounts[]` amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.\n\nWhat happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, `redeemed_count` is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with `CLAIM_REQUIRED` instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.\n\nSee glossary: basket_id and MYNE reference, Surface, Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"surface\": \"lsk\",\n  \"location_id\": {{location_id}},\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"discounts\": [\n    {\n      \"promotion_id\": {{promotion_id}},\n      \"amount_in_cents\": 550\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"This offer must be claimed on the join page before it can be redeemed.\",\n  \"code\": \"CLAIM_REQUIRED\"\n}"
            },
            {
              "name": "First-X redemption cap exhausted",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/complete",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "complete"
                  ]
                },
                "description": "Step **5** in the Promotions section (**Record the redemption when the customer has paid**), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the same **basket_id** from apply, who the customer is, the **Surface** (`lsk` or `lso`), and the paid product lines (each with the **Order line pos_id** — not the discount line). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Optional `discounts[]` amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.\n\nWhat happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, `redeemed_count` is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with `CLAIM_REQUIRED` instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.\n\nSee glossary: basket_id and MYNE reference, Surface, Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"surface\": \"lsk\",\n  \"location_id\": {{location_id}},\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"discounts\": [\n    {\n      \"promotion_id\": {{promotion_id}},\n      \"amount_in_cents\": 550\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Sorry, this offer is no longer available.\",\n  \"code\": \"TOTAL_REDEMPTION_LIMIT_REACHED\"\n}"
            },
            {
              "name": "404 Customer not found for this brand.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/complete",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "complete"
                  ]
                },
                "description": "Step **5** in the Promotions section (**Record the redemption when the customer has paid**), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the same **basket_id** from apply, who the customer is, the **Surface** (`lsk` or `lso`), and the paid product lines (each with the **Order line pos_id** — not the discount line). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Optional `discounts[]` amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.\n\nWhat happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, `redeemed_count` is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with `CLAIM_REQUIRED` instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.\n\nSee glossary: basket_id and MYNE reference, Surface, Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"surface\": \"lsk\",\n  \"location_id\": {{location_id}},\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"discounts\": [\n    {\n      \"promotion_id\": {{promotion_id}},\n      \"amount_in_cents\": 550\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "409 Insufficient points for a promotion on this basket.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/complete",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "complete"
                  ]
                },
                "description": "Step **5** in the Promotions section (**Record the redemption when the customer has paid**), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the same **basket_id** from apply, who the customer is, the **Surface** (`lsk` or `lso`), and the paid product lines (each with the **Order line pos_id** — not the discount line). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Optional `discounts[]` amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.\n\nWhat happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, `redeemed_count` is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with `CLAIM_REQUIRED` instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.\n\nSee glossary: basket_id and MYNE reference, Surface, Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"surface\": \"lsk\",\n  \"location_id\": {{location_id}},\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"discounts\": [\n    {\n      \"promotion_id\": {{promotion_id}},\n      \"amount_in_cents\": 550\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 409,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Insufficient points to redeem this promotion\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/complete",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "complete"
                  ]
                },
                "description": "Step **5** in the Promotions section (**Record the redemption when the customer has paid**), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the same **basket_id** from apply, who the customer is, the **Surface** (`lsk` or `lso`), and the paid product lines (each with the **Order line pos_id** — not the discount line). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Optional `discounts[]` amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.\n\nWhat happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, `redeemed_count` is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with `CLAIM_REQUIRED` instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.\n\nSee glossary: basket_id and MYNE reference, Surface, Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"surface\": \"lsk\",\n  \"location_id\": {{location_id}},\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"discounts\": [\n    {\n      \"promotion_id\": {{promotion_id}},\n      \"amount_in_cents\": 550\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            },
            {
              "name": "502 Promotions engine unavailable or rejected the request.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/promotions/complete",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "promotions",
                    "complete"
                  ]
                },
                "description": "Step **5** in the Promotions section (**Record the redemption when the customer has paid**), when paid sales from this POS do not already arrive in myne. Skip the order mark (step 4). Read that life cycle first; this page lists the fields for this call.\n\nWe need: the same **basket_id** from apply, who the customer is, the **Surface** (`lsk` or `lso`), and the paid product lines (each with the **Order line pos_id** — not the discount line). This is an open-order call: send `surface` for the app. Do not send `integration_type`. Optional `discounts[]` amounts are for reporting only. They do not cause the redemption. One call records every locked reward on that order.\n\nWhat happens: myne records the redemptions and clears the lock. A later POS or ordering sale for the same cart does not redeem again. If nothing is still locked, `redeemed_count` is 0. Complete does not create Claimed-group membership. If a locked offer still requires join-page claim, complete rejects with `CLAIM_REQUIRED` instead of recording. Recording the redemption does not create the paid purchase in myne. That purchase is created when the POS or ordering system sends the sale.\n\nSee glossary: basket_id and MYNE reference, Surface, Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"customer_id\": {{customer_id}},\n  \"basket_id\": \"pos-order-7f3a2c\",\n  \"surface\": \"lsk\",\n  \"location_id\": {{location_id}},\n  \"cart\": {\n    \"items\": [\n      {\n        \"metadata\": {\n          \"pos_id\": \"{{product_external_id}}\"\n        },\n        \"amount_in_cents\": 550,\n        \"quantity\": 1\n      }\n    ]\n  },\n  \"discounts\": [\n    {\n      \"promotion_id\": {{promotion_id}},\n      \"amount_in_cents\": 550\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 502,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Promotions engine unavailable. Please try again shortly.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Transactions",
      "item": [
        {
          "name": "List transactions for a customer",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/transactions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "transactions"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "15",
                  "description": "",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "payments",
                  "value": "",
                  "description": "When true, include payment detail on each transaction.",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Page through paid purchase history for one customer. An open sale is an **order**; once paid it becomes a **Transaction**. Returns POS and payment-linked transactions with line items.\n\nUse `limit` and `offset` (or `page`) for pagination. Set `payments=true` to include payment detail on each row.\n\nCustomer events and CRM activities are separate endpoints—see Activities and `POST …/events`. Returns 404 when the customer is not in this brand.\n\nSee glossary: Customer, Transaction, Activity."
          },
          "response": [
            {
              "name": "200 Paginated customer transaction history.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/transactions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "transactions"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "payments",
                      "value": "",
                      "description": "When true, include payment detail on each transaction.",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through paid purchase history for one customer. An open sale is an **order**; once paid it becomes a **Transaction**. Returns POS and payment-linked transactions with line items.\n\nUse `limit` and `offset` (or `page`) for pagination. Set `payments=true` to include payment detail on each row.\n\nCustomer events and CRM activities are separate endpoints—see Activities and `POST …/events`. Returns 404 when the customer is not in this brand.\n\nSee glossary: Customer, Transaction, Activity."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Transactions fetched successfully\",\n  \"response\": {\n    \"transactions\": [\n      {\n        \"id\": 550012,\n        \"transaction_id\": 550012,\n        \"transacted_at\": \"2026-06-21T03:15:00.000Z\",\n        \"location\": \"Sydney CBD\",\n        \"location_id\": 3,\n        \"source\": \"lightspeed\",\n        \"external_transaction_id\": \"POS-88421\",\n        \"notes\": null,\n        \"order_type\": \"dine_in\",\n        \"total_paid\": 24.5,\n        \"line_items\": [\n          {\n            \"id\": 88001,\n            \"product_id\": 1201,\n            \"product_name\": \"Flat White\",\n            \"quantity\": 1,\n            \"price_inc_tax\": 5.5,\n            \"total_price\": 5.5,\n            \"children\": [],\n            \"modifiers\": []\n          }\n        ],\n        \"payments\": [\n          {\n            \"id\": 77001,\n            \"method\": \"Card\",\n            \"paid_amount\": 24.5,\n            \"tip_amount\": 0,\n            \"last_4\": \"4242\"\n          }\n        ]\n      }\n    ],\n    \"total\": 1,\n    \"limit\": 25,\n    \"offset\": 0,\n    \"page\": 1\n  }\n}"
            },
            {
              "name": "404 Customer not found.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/transactions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "transactions"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "payments",
                      "value": "",
                      "description": "When true, include payment detail on each transaction.",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through paid purchase history for one customer. An open sale is an **order**; once paid it becomes a **Transaction**. Returns POS and payment-linked transactions with line items.\n\nUse `limit` and `offset` (or `page`) for pagination. Set `payments=true` to include payment detail on each row.\n\nCustomer events and CRM activities are separate endpoints—see Activities and `POST …/events`. Returns 404 when the customer is not in this brand.\n\nSee glossary: Customer, Transaction, Activity."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/transactions",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "transactions"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "payments",
                      "value": "",
                      "description": "When true, include payment detail on each transaction.",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through paid purchase history for one customer. An open sale is an **order**; once paid it becomes a **Transaction**. Returns POS and payment-linked transactions with line items.\n\nUse `limit` and `offset` (or `page`) for pagination. Set `payments=true` to include payment detail on each row.\n\nCustomer events and CRM activities are separate endpoints—see Activities and `POST …/events`. Returns 404 when the customer is not in this brand.\n\nSee glossary: Customer, Transaction, Activity."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get a transaction by id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/transactions/{{transaction_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "transactions",
                "{{transaction_id}}"
              ],
              "query": [
                {
                  "key": "payments",
                  "value": "",
                  "description": "When false, omit payment rows from the response.",
                  "disabled": true
                }
              ]
            },
            "description": "Load one paid **Transaction** for the brand, including line items and payment rows.\n\nUse after listing customer transactions or searching brand-wide. Returns 404 when the transaction is not in this brand.\n\nSee glossary: Transaction."
          },
          "response": [
            {
              "name": "200 Single transaction with line items.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/transactions/{{transaction_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "transactions",
                    "{{transaction_id}}"
                  ],
                  "query": [
                    {
                      "key": "payments",
                      "value": "",
                      "description": "When false, omit payment rows from the response.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Load one paid **Transaction** for the brand, including line items and payment rows.\n\nUse after listing customer transactions or searching brand-wide. Returns 404 when the transaction is not in this brand.\n\nSee glossary: Transaction."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Transaction fetched successfully\",\n  \"response\": {\n    \"transaction\": {\n      \"id\": 550012,\n      \"transaction_id\": 550012,\n      \"transacted_at\": \"2026-06-21T03:15:00.000Z\",\n      \"location\": \"Sydney CBD\",\n      \"location_id\": 3,\n      \"source\": \"lightspeed\",\n      \"external_transaction_id\": \"POS-88421\",\n      \"notes\": null,\n      \"order_type\": \"dine_in\",\n      \"total_paid\": 24.5,\n      \"line_items\": [\n        {\n          \"id\": 88001,\n          \"product_id\": 1201,\n          \"product_name\": \"Flat White\",\n          \"quantity\": 1,\n          \"price_inc_tax\": 5.5,\n          \"total_price\": 5.5,\n          \"children\": [],\n          \"modifiers\": []\n        }\n      ],\n      \"payments\": [\n        {\n          \"id\": 77001,\n          \"method\": \"Card\",\n          \"paid_amount\": 24.5,\n          \"tip_amount\": 0,\n          \"last_4\": \"4242\"\n        }\n      ]\n    }\n  }\n}"
            },
            {
              "name": "404 Transaction not found.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/transactions/{{transaction_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "transactions",
                    "{{transaction_id}}"
                  ],
                  "query": [
                    {
                      "key": "payments",
                      "value": "",
                      "description": "When false, omit payment rows from the response.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Load one paid **Transaction** for the brand, including line items and payment rows.\n\nUse after listing customer transactions or searching brand-wide. Returns 404 when the transaction is not in this brand.\n\nSee glossary: Transaction."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Transaction not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/transactions/{{transaction_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "transactions",
                    "{{transaction_id}}"
                  ],
                  "query": [
                    {
                      "key": "payments",
                      "value": "",
                      "description": "When false, omit payment rows from the response.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Load one paid **Transaction** for the brand, including line items and payment rows.\n\nUse after listing customer transactions or searching brand-wide. Returns 404 when the transaction is not in this brand.\n\nSee glossary: Transaction."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Search transactions with filters",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/transactions/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "transactions",
                "search"
              ]
            },
            "description": "Search brand **transactions** (paid orders) with a JSON body: optional `customer_id`, `location_id`, date range (`transacted_from` / `transacted_to`), `min_amount` and `max_amount` (total paid), plus `page`, `limit`, and `payments=true` for payment detail.\n\nPrefer the customer-scoped list when you already have `customer_id`.\n\nSee glossary: Transaction.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"limit\": 15,\n  \"customer_id\": {{customer_id}}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Paginated transaction search results.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/transactions/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "transactions",
                    "search"
                  ]
                },
                "description": "Search brand **transactions** (paid orders) with a JSON body: optional `customer_id`, `location_id`, date range (`transacted_from` / `transacted_to`), `min_amount` and `max_amount` (total paid), plus `page`, `limit`, and `payments=true` for payment detail.\n\nPrefer the customer-scoped list when you already have `customer_id`.\n\nSee glossary: Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"customer_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Transactions fetched successfully\",\n  \"response\": {\n    \"transactions\": [\n      {\n        \"id\": 550012,\n        \"transaction_id\": 550012,\n        \"transacted_at\": \"2026-06-21T03:15:00.000Z\",\n        \"location\": \"Sydney CBD\",\n        \"location_id\": 3,\n        \"source\": \"lightspeed\",\n        \"external_transaction_id\": \"POS-88421\",\n        \"notes\": null,\n        \"order_type\": \"dine_in\",\n        \"total_paid\": 24.5,\n        \"line_items\": [\n          {\n            \"id\": 88001,\n            \"product_id\": 1201,\n            \"product_name\": \"Flat White\",\n            \"quantity\": 1,\n            \"price_inc_tax\": 5.5,\n            \"total_price\": 5.5,\n            \"children\": [],\n            \"modifiers\": []\n          }\n        ],\n        \"payments\": [\n          {\n            \"id\": 77001,\n            \"method\": \"Card\",\n            \"paid_amount\": 24.5,\n            \"tip_amount\": 0,\n            \"last_4\": \"4242\"\n          }\n        ],\n        \"customer_id\": 104823\n      }\n    ],\n    \"total\": 1,\n    \"limit\": 25,\n    \"offset\": 0,\n    \"page\": 1\n  }\n}"
            },
            {
              "name": "400 Invalid JSON body. Invalid updated_since.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/transactions/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "transactions",
                    "search"
                  ]
                },
                "description": "Search brand **transactions** (paid orders) with a JSON body: optional `customer_id`, `location_id`, date range (`transacted_from` / `transacted_to`), `min_amount` and `max_amount` (total paid), plus `page`, `limit`, and `payments=true` for payment detail.\n\nPrefer the customer-scoped list when you already have `customer_id`.\n\nSee glossary: Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"customer_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid JSON body\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/transactions/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "transactions",
                    "search"
                  ]
                },
                "description": "Search brand **transactions** (paid orders) with a JSON body: optional `customer_id`, `location_id`, date range (`transacted_from` / `transacted_to`), `min_amount` and `max_amount` (total paid), plus `page`, `limit`, and `payments=true` for payment detail.\n\nPrefer the customer-scoped list when you already have `customer_id`.\n\nSee glossary: Transaction.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"customer_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Activities",
      "item": [
        {
          "name": "List manual activity type labels",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/activities/manual-types",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "activities",
                "manual-types"
              ]
            },
            "description": "Return activity type labels for CRM dropdowns: the standard library (Call, Email, Meeting, Note, Site visit, Demo, Proposal, Contract, Follow-up, Support) merged with any custom labels already used on this brand (staff or Connect).\n\nUse before `POST …/activities/manual`.\n\nCustomer events (`POST …/events`) are integration timeline writes. Activities are the CRM feed, including manual notes.\n\nSee glossary: Activity."
          },
          "response": [
            {
              "name": "200 Distinct manual activity type labels for the brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/activities/manual-types",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "activities",
                    "manual-types"
                  ]
                },
                "description": "Return activity type labels for CRM dropdowns: the standard library (Call, Email, Meeting, Note, Site visit, Demo, Proposal, Contract, Follow-up, Support) merged with any custom labels already used on this brand (staff or Connect).\n\nUse before `POST …/activities/manual`.\n\nCustomer events (`POST …/events`) are integration timeline writes. Activities are the CRM feed, including manual notes.\n\nSee glossary: Activity."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Manual activity types fetched successfully\",\n  \"response\": {\n    \"types\": [\n      \"Call\",\n      \"Contract\",\n      \"Demo\",\n      \"Email\",\n      \"Follow-up\",\n      \"Meeting\",\n      \"Note\",\n      \"Proposal\",\n      \"Site visit\",\n      \"Support\"\n    ]\n  }\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/activities/manual-types",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "activities",
                    "manual-types"
                  ]
                },
                "description": "Return activity type labels for CRM dropdowns: the standard library (Call, Email, Meeting, Note, Site visit, Demo, Proposal, Contract, Follow-up, Support) merged with any custom labels already used on this brand (staff or Connect).\n\nUse before `POST …/activities/manual`.\n\nCustomer events (`POST …/events`) are integration timeline writes. Activities are the CRM feed, including manual notes.\n\nSee glossary: Activity."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "List activities for a customer",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/activities",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "activities"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "15",
                  "description": "",
                  "disabled": false
                },
                {
                  "key": "offset",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "activity_type",
                  "value": "",
                  "description": "Match event_name or manual activity_type label (e.g. Call).",
                  "disabled": true
                },
                {
                  "key": "type",
                  "value": "",
                  "description": "Alias for activity_type.",
                  "disabled": true
                },
                {
                  "key": "source",
                  "value": "",
                  "description": "Filter by event source (e.g. business_manual or your integration source).",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Page through the CRM activity feed for one customer—manual notes, reservations, task-linked entries, and integration-sourced rows.\n\nOptional query filters: `activity_type` (or `type`) and `source`. For date ranges or brand-wide search, use `POST …/activities/search`.\n\nNot the same as `POST …/events`, which appends integration timeline events. Paginate with `limit` and `offset` (or `page`). Returns 404 when the customer is not in this brand.\n\nSee glossary: Activity."
          },
          "response": [
            {
              "name": "200 Paginated CRM activity feed for the customer.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/activities",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "activities"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "activity_type",
                      "value": "",
                      "description": "Match event_name or manual activity_type label (e.g. Call).",
                      "disabled": true
                    },
                    {
                      "key": "type",
                      "value": "",
                      "description": "Alias for activity_type.",
                      "disabled": true
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Filter by event source (e.g. business_manual or your integration source).",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through the CRM activity feed for one customer—manual notes, reservations, task-linked entries, and integration-sourced rows.\n\nOptional query filters: `activity_type` (or `type`) and `source`. For date ranges or brand-wide search, use `POST …/activities/search`.\n\nNot the same as `POST …/events`, which appends integration timeline events. Paginate with `limit` and `offset` (or `page`). Returns 404 when the customer is not in this brand.\n\nSee glossary: Activity."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Activities fetched successfully\",\n  \"response\": {\n    \"activities\": [\n      {\n        \"id\": 99001,\n        \"event_time\": \"2026-06-20T11:00:00.000Z\",\n        \"event_name\": \"Manual activity\",\n        \"source\": \"integration_api\",\n        \"location\": \"Sydney CBD\",\n        \"location_id\": 3,\n        \"business_id\": 801,\n        \"business_name\": \"Acme Hospitality Group\",\n        \"activity_performer_category\": \"staff\",\n        \"title\": \"Follow-up call\",\n        \"activity_type\": \"Call\",\n        \"notes\": \"Confirmed catering order for Friday.\",\n        \"created_by_display_name\": \"Partner CRM\",\n        \"created_by_integration_source\": \"integration_api\"\n      }\n    ],\n    \"total\": 1,\n    \"limit\": 25,\n    \"offset\": 0,\n    \"page\": 1\n  }\n}"
            },
            {
              "name": "404 Customer not found.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/activities",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "activities"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "activity_type",
                      "value": "",
                      "description": "Match event_name or manual activity_type label (e.g. Call).",
                      "disabled": true
                    },
                    {
                      "key": "type",
                      "value": "",
                      "description": "Alias for activity_type.",
                      "disabled": true
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Filter by event source (e.g. business_manual or your integration source).",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through the CRM activity feed for one customer—manual notes, reservations, task-linked entries, and integration-sourced rows.\n\nOptional query filters: `activity_type` (or `type`) and `source`. For date ranges or brand-wide search, use `POST …/activities/search`.\n\nNot the same as `POST …/events`, which appends integration timeline events. Paginate with `limit` and `offset` (or `page`). Returns 404 when the customer is not in this brand.\n\nSee glossary: Activity."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/activities",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "activities"
                  ],
                  "query": [
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "offset",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "activity_type",
                      "value": "",
                      "description": "Match event_name or manual activity_type label (e.g. Call).",
                      "disabled": true
                    },
                    {
                      "key": "type",
                      "value": "",
                      "description": "Alias for activity_type.",
                      "disabled": true
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Filter by event source (e.g. business_manual or your integration source).",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through the CRM activity feed for one customer—manual notes, reservations, task-linked entries, and integration-sourced rows.\n\nOptional query filters: `activity_type` (or `type`) and `source`. For date ranges or brand-wide search, use `POST …/activities/search`.\n\nNot the same as `POST …/events`, which appends integration timeline events. Paginate with `limit` and `offset` (or `page`). Returns 404 when the customer is not in this brand.\n\nSee glossary: Activity."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Record a manual CRM activity",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/activities/manual",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "activities",
                "manual"
              ]
            },
            "description": "Add a manual note or call log to the customer CRM feed without staff login.\n\nRequires `title`, or both `activity_type` and `notes`. Prefer `activity_type` values from `GET …/activities/manual-types`.\n\nOptional `business_id` when the contact is linked to a business account; `event_time` defaults to now. The activity is attributed to your API credential source and label. Do not send `source` in the body.\n\nSee glossary: Activity, Business, External source.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"title\": \"Follow-up call\",\n  \"activity_type\": \"call\",\n  \"notes\": \"Discussed upcoming visit\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Manual activity recorded on the CRM feed.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/activities/manual",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "activities",
                    "manual"
                  ]
                },
                "description": "Add a manual note or call log to the customer CRM feed without staff login.\n\nRequires `title`, or both `activity_type` and `notes`. Prefer `activity_type` values from `GET …/activities/manual-types`.\n\nOptional `business_id` when the contact is linked to a business account; `event_time` defaults to now. The activity is attributed to your API credential source and label. Do not send `source` in the body.\n\nSee glossary: Activity, Business, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"title\": \"Follow-up call\",\n  \"activity_type\": \"call\",\n  \"notes\": \"Discussed upcoming visit\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Manual activity recorded successfully\",\n  \"response\": {\n    \"id\": 99001,\n    \"event_time\": \"2026-06-20T11:00:00.000Z\",\n    \"event_name\": \"Manual activity\",\n    \"source\": \"integration_api\",\n    \"title\": \"Follow-up call\",\n    \"activity_type\": \"Call\",\n    \"notes\": \"Confirmed catering order for Friday.\"\n  }\n}"
            },
            {
              "name": "400 Invalid body or business link.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/activities/manual",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "activities",
                    "manual"
                  ]
                },
                "description": "Add a manual note or call log to the customer CRM feed without staff login.\n\nRequires `title`, or both `activity_type` and `notes`. Prefer `activity_type` values from `GET …/activities/manual-types`.\n\nOptional `business_id` when the contact is linked to a business account; `event_time` defaults to now. The activity is attributed to your API credential source and label. Do not send `source` in the body.\n\nSee glossary: Activity, Business, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"title\": \"Follow-up call\",\n  \"activity_type\": \"call\",\n  \"notes\": \"Discussed upcoming visit\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"title is required, or provide both activity_type and notes\"\n}"
            },
            {
              "name": "403 source cannot be set on write requests.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/activities/manual",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "activities",
                    "manual"
                  ]
                },
                "description": "Add a manual note or call log to the customer CRM feed without staff login.\n\nRequires `title`, or both `activity_type` and `notes`. Prefer `activity_type` values from `GET …/activities/manual-types`.\n\nOptional `business_id` when the contact is linked to a business account; `event_time` defaults to now. The activity is attributed to your API credential source and label. Do not send `source` in the body.\n\nSee glossary: Activity, Business, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"title\": \"Follow-up call\",\n  \"activity_type\": \"call\",\n  \"notes\": \"Discussed upcoming visit\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"source is managed by your API credentials and cannot be set on write requests\"\n}"
            },
            {
              "name": "404 Customer not found.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/activities/manual",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "activities",
                    "manual"
                  ]
                },
                "description": "Add a manual note or call log to the customer CRM feed without staff login.\n\nRequires `title`, or both `activity_type` and `notes`. Prefer `activity_type` values from `GET …/activities/manual-types`.\n\nOptional `business_id` when the contact is linked to a business account; `event_time` defaults to now. The activity is attributed to your API credential source and label. Do not send `source` in the body.\n\nSee glossary: Activity, Business, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"title\": \"Follow-up call\",\n  \"activity_type\": \"call\",\n  \"notes\": \"Discussed upcoming visit\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/activities/manual",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "activities",
                    "manual"
                  ]
                },
                "description": "Add a manual note or call log to the customer CRM feed without staff login.\n\nRequires `title`, or both `activity_type` and `notes`. Prefer `activity_type` values from `GET …/activities/manual-types`.\n\nOptional `business_id` when the contact is linked to a business account; `event_time` defaults to now. The activity is attributed to your API credential source and label. Do not send `source` in the body.\n\nSee glossary: Activity, Business, External source.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"title\": \"Follow-up call\",\n  \"activity_type\": \"call\",\n  \"notes\": \"Discussed upcoming visit\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Search activities with filters",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/activities/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "activities",
                "search"
              ]
            },
            "description": "Search the CRM activity feed with a JSON body: optional `customer_id`, `business_id`, `activity_type` (or `type`), `source`, date range (`event_from` / `event_to`), plus `page` and `limit`.\n\nCustomer events (`POST …/events`) are integration timeline writes; this endpoint reads the broader CRM feed.\n\nSee glossary: Activity.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"limit\": 15,\n  \"customer_id\": {{customer_id}}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Paginated CRM activity search results.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/activities/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "activities",
                    "search"
                  ]
                },
                "description": "Search the CRM activity feed with a JSON body: optional `customer_id`, `business_id`, `activity_type` (or `type`), `source`, date range (`event_from` / `event_to`), plus `page` and `limit`.\n\nCustomer events (`POST …/events`) are integration timeline writes; this endpoint reads the broader CRM feed.\n\nSee glossary: Activity.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"customer_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Activities fetched successfully\",\n  \"response\": {\n    \"activities\": [\n      {\n        \"id\": 99001,\n        \"event_time\": \"2026-06-20T11:00:00.000Z\",\n        \"event_name\": \"Manual activity\",\n        \"source\": \"integration_api\",\n        \"location\": \"Sydney CBD\",\n        \"location_id\": 3,\n        \"business_id\": 801,\n        \"business_name\": \"Acme Hospitality Group\",\n        \"activity_performer_category\": \"staff\",\n        \"title\": \"Follow-up call\",\n        \"activity_type\": \"Call\",\n        \"notes\": \"Confirmed catering order for Friday.\",\n        \"created_by_display_name\": \"Partner CRM\",\n        \"created_by_integration_source\": \"integration_api\",\n        \"customer_id\": 104823\n      }\n    ],\n    \"total\": 1,\n    \"limit\": 25,\n    \"offset\": 0,\n    \"page\": 1\n  }\n}"
            },
            {
              "name": "400 Invalid JSON body. Invalid updated_since.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/activities/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "activities",
                    "search"
                  ]
                },
                "description": "Search the CRM activity feed with a JSON body: optional `customer_id`, `business_id`, `activity_type` (or `type`), `source`, date range (`event_from` / `event_to`), plus `page` and `limit`.\n\nCustomer events (`POST …/events`) are integration timeline writes; this endpoint reads the broader CRM feed.\n\nSee glossary: Activity.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"customer_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid JSON body\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/activities/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "activities",
                    "search"
                  ]
                },
                "description": "Search the CRM activity feed with a JSON body: optional `customer_id`, `business_id`, `activity_type` (or `type`), `source`, date range (`event_from` / `event_to`), plus `page` and `limit`.\n\nCustomer events (`POST …/events`) are integration timeline writes; this endpoint reads the broader CRM feed.\n\nSee glossary: Activity.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"customer_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Tasks",
      "item": [
        {
          "name": "Browse tasks for a brand",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "tasks"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "15",
                  "description": "",
                  "disabled": false
                },
                {
                  "key": "query",
                  "value": "",
                  "description": "Free-text search on title and linked record names.",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "statuses",
                  "value": "",
                  "description": "Comma-separated status values.",
                  "disabled": true
                },
                {
                  "key": "exclude_statuses",
                  "value": "",
                  "description": "Comma-separated statuses to omit.",
                  "disabled": true
                },
                {
                  "key": "assignee_user_ids",
                  "value": "",
                  "description": "Comma-separated myne user IDs.",
                  "disabled": true
                },
                {
                  "key": "assignee_filter",
                  "value": "",
                  "description": "Special assignee filter. Use unassigned for tasks with no assignee.",
                  "disabled": true
                },
                {
                  "key": "due_date_filter",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "business_id",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "customer_id",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "sort_by",
                  "value": "total_spent",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "sort_dir",
                  "value": "",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Page through staff tasks for the brand. Defaults to open work (`todo` and `in_progress`) sorted by most recently updated.\n\nFilter with query parameters: `status` or `statuses`, `exclude_statuses`, `assignee_user_ids`, `assignee_filter` (e.g. unassigned), `due_date_filter`, `business_id`, `customer_id`, free-text `query`, `sort_by`, and `sort_dir`.\n\nPartner-safe fields only—assignee and customer emails are omitted.\n\nSee glossary: Task."
          },
          "response": [
            {
              "name": "200 Paginated open/recent tasks for the brand (partner-safe fields).",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "tasks"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "query",
                      "value": "",
                      "description": "Free-text search on title and linked record names.",
                      "disabled": true
                    },
                    {
                      "key": "status",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "statuses",
                      "value": "",
                      "description": "Comma-separated status values.",
                      "disabled": true
                    },
                    {
                      "key": "exclude_statuses",
                      "value": "",
                      "description": "Comma-separated statuses to omit.",
                      "disabled": true
                    },
                    {
                      "key": "assignee_user_ids",
                      "value": "",
                      "description": "Comma-separated myne user IDs.",
                      "disabled": true
                    },
                    {
                      "key": "assignee_filter",
                      "value": "",
                      "description": "Special assignee filter. Use unassigned for tasks with no assignee.",
                      "disabled": true
                    },
                    {
                      "key": "due_date_filter",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "business_id",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "customer_id",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "sort_by",
                      "value": "total_spent",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "sort_dir",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through staff tasks for the brand. Defaults to open work (`todo` and `in_progress`) sorted by most recently updated.\n\nFilter with query parameters: `status` or `statuses`, `exclude_statuses`, `assignee_user_ids`, `assignee_filter` (e.g. unassigned), `due_date_filter`, `business_id`, `customer_id`, free-text `query`, `sort_by`, and `sort_dir`.\n\nPartner-safe fields only—assignee and customer emails are omitted.\n\nSee glossary: Task."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Tasks fetched successfully\",\n  \"response\": {\n    \"tasks\": [\n      {\n        \"id\": 501,\n        \"brand_id\": 42,\n        \"title\": \"Call venue about catering enquiry\",\n        \"description\": \"Follow up on Monday intake form.\",\n        \"due_date\": \"2026-07-15\",\n        \"due_time\": \"09:00:00\",\n        \"status\": \"todo\",\n        \"created_at\": \"2026-07-01T02:30:00.000Z\",\n        \"updated_at\": \"2026-07-10T04:15:00.000Z\",\n        \"status_changed_at\": null,\n        \"comment_count\": 1,\n        \"latest_comment\": {\n          \"created_at\": \"2026-07-10T04:15:00.000Z\",\n          \"preview\": \"Left voicemail\",\n          \"author_first_name\": \"Alex\",\n          \"author_last_name\": \"Nguyen\"\n        },\n        \"assignees\": [\n          {\n            \"user_id\": 12,\n            \"first_name\": \"Alex\",\n            \"last_name\": \"Nguyen\"\n          }\n        ],\n        \"businesses\": [\n          {\n            \"business_id\": 801,\n            \"name\": \"Harbour Catering Co\"\n          }\n        ],\n        \"customers\": [\n          {\n            \"customer_id\": 104823,\n            \"first_name\": \"Jordan\",\n            \"last_name\": \"Lee\"\n          }\n        ],\n        \"locations\": [\n          {\n            \"location_id\": 3,\n            \"name\": \"Main\"\n          }\n        ],\n        \"attachments\": []\n      }\n    ],\n    \"pagination\": {\n      \"page\": 1,\n      \"limit\": 20,\n      \"total_tasks\": 1,\n      \"total_pages\": 1\n    }\n  }\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "tasks"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "query",
                      "value": "",
                      "description": "Free-text search on title and linked record names.",
                      "disabled": true
                    },
                    {
                      "key": "status",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "statuses",
                      "value": "",
                      "description": "Comma-separated status values.",
                      "disabled": true
                    },
                    {
                      "key": "exclude_statuses",
                      "value": "",
                      "description": "Comma-separated statuses to omit.",
                      "disabled": true
                    },
                    {
                      "key": "assignee_user_ids",
                      "value": "",
                      "description": "Comma-separated myne user IDs.",
                      "disabled": true
                    },
                    {
                      "key": "assignee_filter",
                      "value": "",
                      "description": "Special assignee filter. Use unassigned for tasks with no assignee.",
                      "disabled": true
                    },
                    {
                      "key": "due_date_filter",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "business_id",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "customer_id",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "sort_by",
                      "value": "total_spent",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "sort_dir",
                      "value": "",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through staff tasks for the brand. Defaults to open work (`todo` and `in_progress`) sorted by most recently updated.\n\nFilter with query parameters: `status` or `statuses`, `exclude_statuses`, `assignee_user_ids`, `assignee_filter` (e.g. unassigned), `due_date_filter`, `business_id`, `customer_id`, free-text `query`, `sort_by`, and `sort_dir`.\n\nPartner-safe fields only—assignee and customer emails are omitted.\n\nSee glossary: Task."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get a task by id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks/{{task_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "tasks",
                "{{task_id}}"
              ],
              "query": [
                {
                  "key": "include_updates",
                  "value": "",
                  "description": "When false, omit the updates array from the response.",
                  "disabled": true
                }
              ]
            },
            "description": "Load one task with linked customers, businesses, locations, assignees, and optional status/comment updates.\n\nSet `include_updates=false` to omit the updates array. Returns 404 when the task is not in this brand.\n\nSee glossary: Task."
          },
          "response": [
            {
              "name": "200 Single task with linked records (partner-safe fields).",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks/{{task_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "tasks",
                    "{{task_id}}"
                  ],
                  "query": [
                    {
                      "key": "include_updates",
                      "value": "",
                      "description": "When false, omit the updates array from the response.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Load one task with linked customers, businesses, locations, assignees, and optional status/comment updates.\n\nSet `include_updates=false` to omit the updates array. Returns 404 when the task is not in this brand.\n\nSee glossary: Task."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Task fetched successfully\",\n  \"response\": {\n    \"id\": 501,\n    \"brand_id\": 42,\n    \"title\": \"Call venue about catering enquiry\",\n    \"description\": \"Follow up on Monday intake form.\",\n    \"due_date\": \"2026-07-15\",\n    \"due_time\": \"09:00:00\",\n    \"status\": \"todo\",\n    \"created_at\": \"2026-07-01T02:30:00.000Z\",\n    \"updated_at\": \"2026-07-10T04:15:00.000Z\",\n    \"status_changed_at\": null,\n    \"comment_count\": 1,\n    \"latest_comment\": {\n      \"created_at\": \"2026-07-10T04:15:00.000Z\",\n      \"preview\": \"Left voicemail\",\n      \"author_first_name\": \"Alex\",\n      \"author_last_name\": \"Nguyen\"\n    },\n    \"assignees\": [\n      {\n        \"user_id\": 12,\n        \"first_name\": \"Alex\",\n        \"last_name\": \"Nguyen\"\n      }\n    ],\n    \"businesses\": [\n      {\n        \"business_id\": 801,\n        \"name\": \"Harbour Catering Co\"\n      }\n    ],\n    \"customers\": [\n      {\n        \"customer_id\": 104823,\n        \"first_name\": \"Jordan\",\n        \"last_name\": \"Lee\"\n      }\n    ],\n    \"locations\": [\n      {\n        \"location_id\": 3,\n        \"name\": \"Main\"\n      }\n    ],\n    \"attachments\": [],\n    \"updates\": [\n      {\n        \"id\": 9001,\n        \"task_id\": 501,\n        \"brand_id\": 42,\n        \"update_kind\": \"comment\",\n        \"status_from\": null,\n        \"status_to\": null,\n        \"comment_text\": \"Left voicemail\",\n        \"created_at\": \"2026-07-10T04:15:00.000Z\",\n        \"created_by_first_name\": \"Alex\",\n        \"created_by_last_name\": \"Nguyen\"\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 Invalid brand_id or task_id.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks/{{task_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "tasks",
                    "{{task_id}}"
                  ],
                  "query": [
                    {
                      "key": "include_updates",
                      "value": "",
                      "description": "When false, omit the updates array from the response.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Load one task with linked customers, businesses, locations, assignees, and optional status/comment updates.\n\nSet `include_updates=false` to omit the updates array. Returns 404 when the task is not in this brand.\n\nSee glossary: Task."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or task_id\"\n}"
            },
            {
              "name": "404 Task not found.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks/{{task_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "tasks",
                    "{{task_id}}"
                  ],
                  "query": [
                    {
                      "key": "include_updates",
                      "value": "",
                      "description": "When false, omit the updates array from the response.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Load one task with linked customers, businesses, locations, assignees, and optional status/comment updates.\n\nSet `include_updates=false` to omit the updates array. Returns 404 when the task is not in this brand.\n\nSee glossary: Task."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Task not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks/{{task_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "tasks",
                    "{{task_id}}"
                  ],
                  "query": [
                    {
                      "key": "include_updates",
                      "value": "",
                      "description": "When false, omit the updates array from the response.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Load one task with linked customers, businesses, locations, assignees, and optional status/comment updates.\n\nSet `include_updates=false` to omit the updates array. Returns 404 when the task is not in this brand.\n\nSee glossary: Task."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "List tasks for a customer",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/tasks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "customers",
                "{{customer_id}}",
                "tasks"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "15",
                  "description": "",
                  "disabled": false
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                  "disabled": true
                }
              ]
            },
            "description": "Page through tasks linked to one customer (contact). Uses the same pagination fields as brand task browse.\n\nUseful when syncing follow-ups from your CRM back to myne or displaying open work on a contact profile.\n\nSee glossary: Task, Customer."
          },
          "response": [
            {
              "name": "200 Paginated tasks linked to the customer.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/tasks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "tasks"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through tasks linked to one customer (contact). Uses the same pagination fields as brand task browse.\n\nUseful when syncing follow-ups from your CRM back to myne or displaying open work on a contact profile.\n\nSee glossary: Task, Customer."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer tasks fetched successfully\",\n  \"response\": {\n    \"tasks\": [\n      {\n        \"id\": 501,\n        \"brand_id\": 42,\n        \"title\": \"Call venue about catering enquiry\",\n        \"description\": \"Follow up on Monday intake form.\",\n        \"due_date\": \"2026-07-15\",\n        \"due_time\": \"09:00:00\",\n        \"status\": \"todo\",\n        \"created_at\": \"2026-07-01T02:30:00.000Z\",\n        \"updated_at\": \"2026-07-10T04:15:00.000Z\",\n        \"status_changed_at\": null,\n        \"comment_count\": 1,\n        \"latest_comment\": {\n          \"created_at\": \"2026-07-10T04:15:00.000Z\",\n          \"preview\": \"Left voicemail\",\n          \"author_first_name\": \"Alex\",\n          \"author_last_name\": \"Nguyen\"\n        },\n        \"assignees\": [\n          {\n            \"user_id\": 12,\n            \"first_name\": \"Alex\",\n            \"last_name\": \"Nguyen\"\n          }\n        ],\n        \"businesses\": [\n          {\n            \"business_id\": 801,\n            \"name\": \"Harbour Catering Co\"\n          }\n        ],\n        \"customers\": [\n          {\n            \"customer_id\": 104823,\n            \"first_name\": \"Jordan\",\n            \"last_name\": \"Lee\"\n          }\n        ],\n        \"locations\": [\n          {\n            \"location_id\": 3,\n            \"name\": \"Main\"\n          }\n        ],\n        \"attachments\": []\n      }\n    ],\n    \"pagination\": {\n      \"page\": 1,\n      \"limit\": 20,\n      \"total_tasks\": 1,\n      \"total_pages\": 1\n    }\n  }\n}"
            },
            {
              "name": "400 Invalid customer_id. Invalid updated_since.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/tasks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "tasks"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through tasks linked to one customer (contact). Uses the same pagination fields as brand task browse.\n\nUseful when syncing follow-ups from your CRM back to myne or displaying open work on a contact profile.\n\nSee glossary: Task, Customer."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid customer_id\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/customers/{{customer_id}}/tasks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "customers",
                    "{{customer_id}}",
                    "tasks"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (compared in UTC, inclusive). Omit for the full list. Invalid values return 400.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through tasks linked to one customer (contact). Uses the same pagination fields as brand task browse.\n\nUseful when syncing follow-ups from your CRM back to myne or displaying open work on a contact profile.\n\nSee glossary: Task, Customer."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Search tasks with advanced filters",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks/search",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "tasks",
                "search"
              ]
            },
            "description": "Search tasks with a JSON body: `assignee_user_ids`, `assignee_filter`, `status` or `statuses`, `exclude_statuses`, `due_date_filter`, `business_id`, `customer_id`, free-text `query`, `sort_by`, `sort_dir`, `page`, and `limit`.\n\nPrefer `GET …/tasks` for the default open/recent browse; use this when you need richer filters in one request.\n\nCreating tasks via Connect is not available yet.\n\nSee glossary: Task.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"limit\": 15,\n  \"query\": \"follow up\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Paginated task search results (partner-safe fields).",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "tasks",
                    "search"
                  ]
                },
                "description": "Search tasks with a JSON body: `assignee_user_ids`, `assignee_filter`, `status` or `statuses`, `exclude_statuses`, `due_date_filter`, `business_id`, `customer_id`, free-text `query`, `sort_by`, `sort_dir`, `page`, and `limit`.\n\nPrefer `GET …/tasks` for the default open/recent browse; use this when you need richer filters in one request.\n\nCreating tasks via Connect is not available yet.\n\nSee glossary: Task.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"query\": \"follow up\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Tasks fetched successfully\",\n  \"response\": {\n    \"tasks\": [\n      {\n        \"id\": 501,\n        \"brand_id\": 42,\n        \"title\": \"Call venue about catering enquiry\",\n        \"description\": \"Follow up on Monday intake form.\",\n        \"due_date\": \"2026-07-15\",\n        \"due_time\": \"09:00:00\",\n        \"status\": \"todo\",\n        \"created_at\": \"2026-07-01T02:30:00.000Z\",\n        \"updated_at\": \"2026-07-10T04:15:00.000Z\",\n        \"status_changed_at\": null,\n        \"comment_count\": 1,\n        \"latest_comment\": {\n          \"created_at\": \"2026-07-10T04:15:00.000Z\",\n          \"preview\": \"Left voicemail\",\n          \"author_first_name\": \"Alex\",\n          \"author_last_name\": \"Nguyen\"\n        },\n        \"assignees\": [\n          {\n            \"user_id\": 12,\n            \"first_name\": \"Alex\",\n            \"last_name\": \"Nguyen\"\n          }\n        ],\n        \"businesses\": [\n          {\n            \"business_id\": 801,\n            \"name\": \"Harbour Catering Co\"\n          }\n        ],\n        \"customers\": [\n          {\n            \"customer_id\": 104823,\n            \"first_name\": \"Jordan\",\n            \"last_name\": \"Lee\"\n          }\n        ],\n        \"locations\": [\n          {\n            \"location_id\": 3,\n            \"name\": \"Main\"\n          }\n        ],\n        \"attachments\": []\n      }\n    ],\n    \"pagination\": {\n      \"page\": 1,\n      \"limit\": 20,\n      \"total_tasks\": 1,\n      \"total_pages\": 1\n    }\n  }\n}"
            },
            {
              "name": "400 Invalid JSON body. Invalid updated_since.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "tasks",
                    "search"
                  ]
                },
                "description": "Search tasks with a JSON body: `assignee_user_ids`, `assignee_filter`, `status` or `statuses`, `exclude_statuses`, `due_date_filter`, `business_id`, `customer_id`, free-text `query`, `sort_by`, `sort_dir`, `page`, and `limit`.\n\nPrefer `GET …/tasks` for the default open/recent browse; use this when you need richer filters in one request.\n\nCreating tasks via Connect is not available yet.\n\nSee glossary: Task.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"query\": \"follow up\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid JSON body\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/tasks/search",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "tasks",
                    "search"
                  ]
                },
                "description": "Search tasks with a JSON body: `assignee_user_ids`, `assignee_filter`, `status` or `statuses`, `exclude_statuses`, `due_date_filter`, `business_id`, `customer_id`, free-text `query`, `sort_by`, `sort_dir`, `page`, and `limit`.\n\nPrefer `GET …/tasks` for the default open/recent browse; use this when you need richer filters in one request.\n\nCreating tasks via Connect is not available yet.\n\nSee glossary: Task.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"limit\": 15,\n  \"query\": \"follow up\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Businesses",
      "item": [
        {
          "name": "Get a business by external id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/by-external-id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "businesses",
                "by-external-id"
              ],
              "query": [
                {
                  "key": "external_id",
                  "value": "{{external_id}}",
                  "description": "Your integration's identifier for the business.",
                  "disabled": false
                },
                {
                  "key": "source",
                  "value": "",
                  "description": "Origin system for the external_id. When omitted, defaults to your Custom API app name (Integrations label). Read-only filter for cross-integration lookups.",
                  "disabled": true
                }
              ]
            },
            "description": "Resolve a myne business account from your `external_id` without paging browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (Integrations label). Pass `source` explicitly to read IDs synced under another integration.\n\nReturns 404 when no match exists for this brand.\n\nSee glossary: Business, external_id and external_data, External source."
          },
          "response": [
            {
              "name": "200 Business account for the brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/by-external-id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "by-external-id"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Your integration's identifier for the business.",
                      "disabled": false
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Origin system for the external_id. When omitted, defaults to your Custom API app name (Integrations label). Read-only filter for cross-integration lookups.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Resolve a myne business account from your `external_id` without paging browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (Integrations label). Pass `source` explicitly to read IDs synced under another integration.\n\nReturns 404 when no match exists for this brand.\n\nSee glossary: Business, external_id and external_data, External source."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Business fetched successfully\",\n  \"response\": {\n    \"id\": 801,\n    \"brand_id\": 42,\n    \"location_id\": 3,\n    \"name\": \"Acme Hospitality Group\",\n    \"external_id\": \"biz-001\",\n    \"source\": \"integration_api\",\n    \"address_line_1\": \"100 George St\",\n    \"address_line_2\": null,\n    \"city\": \"Sydney\",\n    \"region\": \"NSW\",\n    \"postal_code\": \"2000\",\n    \"country\": \"AU\",\n    \"phone\": \"+61298765432\",\n    \"email\": \"accounts@acme.example.com\",\n    \"logo_url\": null,\n    \"website\": \"https://acme.example.com\",\n    \"description\": \"Corporate catering partner\",\n    \"created_at\": \"2025-03-01T10:00:00.000Z\",\n    \"updated_at\": \"2026-06-15T08:30:00.000Z\",\n    \"archived_at\": null,\n    \"contact_count\": 5,\n    \"primary_contact_customer_id\": 104823,\n    \"primary_contact_first_name\": \"Jordan\",\n    \"primary_contact_last_name\": \"Lee\",\n    \"last_contacted_at\": \"2026-06-18T09:00:00.000Z\",\n    \"last_active_at\": \"2026-06-20T14:22:00.000Z\"\n  }\n}"
            },
            {
              "name": "400 external_id query parameter is missing.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/by-external-id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "by-external-id"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Your integration's identifier for the business.",
                      "disabled": false
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Origin system for the external_id. When omitted, defaults to your Custom API app name (Integrations label). Read-only filter for cross-integration lookups.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Resolve a myne business account from your `external_id` without paging browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (Integrations label). Pass `source` explicitly to read IDs synced under another integration.\n\nReturns 404 when no match exists for this brand.\n\nSee glossary: Business, external_id and external_data, External source."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"external_id query parameter is required\"\n}"
            },
            {
              "name": "404 No business matches the external id for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/by-external-id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "by-external-id"
                  ],
                  "query": [
                    {
                      "key": "external_id",
                      "value": "{{external_id}}",
                      "description": "Your integration's identifier for the business.",
                      "disabled": false
                    },
                    {
                      "key": "source",
                      "value": "",
                      "description": "Origin system for the external_id. When omitted, defaults to your Custom API app name (Integrations label). Read-only filter for cross-integration lookups.",
                      "disabled": true
                    }
                  ]
                },
                "description": "Resolve a myne business account from your `external_id` without paging browse results.\n\nWhen `source` is omitted, lookup uses your Custom API app name (Integrations label). Pass `source` explicitly to read IDs synced under another integration.\n\nReturns 404 when no match exists for this brand.\n\nSee glossary: Business, external_id and external_data, External source."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Business not found\"\n}"
            }
          ]
        },
        {
          "name": "Browse businesses for a brand",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "businesses"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1",
                  "description": "",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "15",
                  "description": "",
                  "disabled": false
                },
                {
                  "key": "query",
                  "value": "",
                  "description": "Free-text search across business fields.",
                  "disabled": true
                },
                {
                  "key": "updated_since",
                  "value": "",
                  "description": "ISO-8601 timestamp. When set, only businesses with updated_at on or after this instant are returned (compared in UTC).",
                  "disabled": true
                }
              ]
            },
            "description": "Page through business (B2B) accounts for a brand. Use `page` and `limit` for pagination and `query` for free-text search.\n\nPass `updated_since` (ISO-8601, compared in UTC) to poll only businesses changed on or after that timestamp.\n\nBusiness IDs from here or sync batch can be passed to the single-business and extended endpoints.\n\nSee glossary: Business."
          },
          "response": [
            {
              "name": "200 Paginated business accounts for the brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "query",
                      "value": "",
                      "description": "Free-text search across business fields.",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only businesses with updated_at on or after this instant are returned (compared in UTC).",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through business (B2B) accounts for a brand. Use `page` and `limit` for pagination and `query` for free-text search.\n\nPass `updated_since` (ISO-8601, compared in UTC) to poll only businesses changed on or after that timestamp.\n\nBusiness IDs from here or sync batch can be passed to the single-business and extended endpoints.\n\nSee glossary: Business."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Business records fetched successfully\",\n  \"response\": {\n    \"businesses\": [\n      {\n        \"id\": 801,\n        \"brand_id\": 42,\n        \"location_id\": 3,\n        \"name\": \"Acme Hospitality Group\",\n        \"external_id\": \"biz-001\",\n        \"source\": \"integration_api\",\n        \"address_line_1\": \"100 George St\",\n        \"address_line_2\": null,\n        \"city\": \"Sydney\",\n        \"region\": \"NSW\",\n        \"postal_code\": \"2000\",\n        \"country\": \"AU\",\n        \"phone\": \"+61298765432\",\n        \"email\": \"accounts@acme.example.com\",\n        \"logo_url\": null,\n        \"website\": \"https://acme.example.com\",\n        \"description\": \"Corporate catering partner\",\n        \"created_at\": \"2025-03-01T10:00:00.000Z\",\n        \"updated_at\": \"2026-06-15T08:30:00.000Z\",\n        \"archived_at\": null,\n        \"contact_count\": 5,\n        \"primary_contact_customer_id\": 104823,\n        \"primary_contact_first_name\": \"Jordan\",\n        \"primary_contact_last_name\": \"Lee\",\n        \"last_contacted_at\": \"2026-06-18T09:00:00.000Z\",\n        \"last_active_at\": \"2026-06-20T14:22:00.000Z\"\n      }\n    ],\n    \"total_businesses\": 24,\n    \"total_pages\": 3,\n    \"current_page\": 1\n  }\n}"
            },
            {
              "name": "400 brand_id is missing, not a positive integer, or updated_since is invalid.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "query",
                      "value": "",
                      "description": "Free-text search across business fields.",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only businesses with updated_at on or after this instant are returned (compared in UTC).",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through business (B2B) accounts for a brand. Use `page` and `limit` for pagination and `query` for free-text search.\n\nPass `updated_since` (ISO-8601, compared in UTC) to poll only businesses changed on or after that timestamp.\n\nBusiness IDs from here or sync batch can be passed to the single-business and extended endpoints.\n\nSee glossary: Business."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "description": "",
                      "disabled": true
                    },
                    {
                      "key": "limit",
                      "value": "15",
                      "description": "",
                      "disabled": false
                    },
                    {
                      "key": "query",
                      "value": "",
                      "description": "Free-text search across business fields.",
                      "disabled": true
                    },
                    {
                      "key": "updated_since",
                      "value": "",
                      "description": "ISO-8601 timestamp. When set, only businesses with updated_at on or after this instant are returned (compared in UTC).",
                      "disabled": true
                    }
                  ]
                },
                "description": "Page through business (B2B) accounts for a brand. Use `page` and `limit` for pagination and `query` for free-text search.\n\nPass `updated_since` (ISO-8601, compared in UTC) to poll only businesses changed on or after that timestamp.\n\nBusiness IDs from here or sync batch can be passed to the single-business and extended endpoints.\n\nSee glossary: Business."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get a business by id",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/{{business_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "businesses",
                "{{business_id}}"
              ]
            },
            "description": "Load one business account by ID.\n\nUse after browse or sync when you need core fields such as name, email, and phone.\n\nSee glossary: Business."
          },
          "response": [
            {
              "name": "200 Single business account for the brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/{{business_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "{{business_id}}"
                  ]
                },
                "description": "Load one business account by ID.\n\nUse after browse or sync when you need core fields such as name, email, and phone.\n\nSee glossary: Business."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Business fetched successfully\",\n  \"response\": {\n    \"id\": 801,\n    \"brand_id\": 42,\n    \"location_id\": 3,\n    \"name\": \"Acme Hospitality Group\",\n    \"external_id\": \"biz-001\",\n    \"source\": \"integration_api\",\n    \"address_line_1\": \"100 George St\",\n    \"address_line_2\": null,\n    \"city\": \"Sydney\",\n    \"region\": \"NSW\",\n    \"postal_code\": \"2000\",\n    \"country\": \"AU\",\n    \"phone\": \"+61298765432\",\n    \"email\": \"accounts@acme.example.com\",\n    \"logo_url\": null,\n    \"website\": \"https://acme.example.com\",\n    \"description\": \"Corporate catering partner\",\n    \"created_at\": \"2025-03-01T10:00:00.000Z\",\n    \"updated_at\": \"2026-06-15T08:30:00.000Z\",\n    \"archived_at\": null,\n    \"contact_count\": 5,\n    \"primary_contact_customer_id\": 104823,\n    \"primary_contact_first_name\": \"Jordan\",\n    \"primary_contact_last_name\": \"Lee\",\n    \"last_contacted_at\": \"2026-06-18T09:00:00.000Z\",\n    \"last_active_at\": \"2026-06-20T14:22:00.000Z\"\n  }\n}"
            },
            {
              "name": "400 brand_id or business_id is missing or not a positive integer.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/{{business_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "{{business_id}}"
                  ]
                },
                "description": "Load one business account by ID.\n\nUse after browse or sync when you need core fields such as name, email, and phone.\n\nSee glossary: Business."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or business_id\"\n}"
            },
            {
              "name": "404 No business with the given id exists for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/{{business_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "{{business_id}}"
                  ]
                },
                "description": "Load one business account by ID.\n\nUse after browse or sync when you need core fields such as name, email, and phone.\n\nSee glossary: Business."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Business not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/{{business_id}}",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "{{business_id}}"
                  ]
                },
                "description": "Load one business account by ID.\n\nUse after browse or sync when you need core fields such as name, email, and phone.\n\nSee glossary: Business."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        },
        {
          "name": "Get extended business profile",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/{{business_id}}/extended",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "businesses",
                "{{business_id}}",
                "extended"
              ]
            },
            "description": "Fetch extended business data: custom properties, linked contacts, and other metadata beyond the core business row.\n\nUse when your integration needs the full CRM picture for a company account.\n\nSee glossary: Business, Extended profile."
          },
          "response": [
            {
              "name": "200 Extended metadata records keyed by source and external_id.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/{{business_id}}/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "{{business_id}}",
                    "extended"
                  ]
                },
                "description": "Fetch extended business data: custom properties, linked contacts, and other metadata beyond the core business row.\n\nUse when your integration needs the full CRM picture for a company account.\n\nSee glossary: Business, Extended profile."
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Business extended data fetched successfully\",\n  \"response\": [\n    {\n      \"source\": \"integration_api\",\n      \"external_id\": \"biz-001\",\n      \"json_data\": {\n        \"industry\": \"hospitality\",\n        \"account_tier\": \"gold\",\n        \"contract_renewal\": \"2027-01-01\"\n      },\n      \"updated_at\": \"2026-06-15T08:30:00.000Z\"\n    }\n  ]\n}"
            },
            {
              "name": "400 brand_id or business_id is missing or not a positive integer.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/{{business_id}}/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "{{business_id}}",
                    "extended"
                  ]
                },
                "description": "Fetch extended business data: custom properties, linked contacts, and other metadata beyond the core business row.\n\nUse when your integration needs the full CRM picture for a company account.\n\nSee glossary: Business, Extended profile."
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid brand_id or business_id\"\n}"
            },
            {
              "name": "404 No business with the given id exists for this brand.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/{{business_id}}/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "{{business_id}}",
                    "extended"
                  ]
                },
                "description": "Fetch extended business data: custom properties, linked contacts, and other metadata beyond the core business row.\n\nUse when your integration needs the full CRM picture for a company account.\n\nSee glossary: Business, Extended profile."
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Business not found\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "GET",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/businesses/{{business_id}}/extended",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "businesses",
                    "{{business_id}}",
                    "extended"
                  ]
                },
                "description": "Fetch extended business data: custom properties, linked contacts, and other metadata beyond the core business row.\n\nUse when your integration needs the full CRM picture for a company account.\n\nSee glossary: Business, Extended profile."
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Sync",
      "item": [
        {
          "name": "Batch upsert contacts, businesses, and links",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/sync/batch",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "sync",
                "batch"
              ]
            },
            "description": "Push up to 50 contacts, businesses, business–contact links, extended fields, or health topics in one request.\n\nThis is the primary write path for CRM, PMS, and POS integrations syncing data into myne (for example guests from MEWS).\n\nSend top-level contact fields plus your `external_id`, and optionally `contact_extended` bodies for extra partner keys/metadata used later for lookup and enrichment.\n\nRecords are always stamped with your Custom API app name—do not send `source` (or `contact_source` / `business_source`) in item bodies.\n\nEach item has a `type` and body; see the request schema for supported types.\n\nSee glossary: Sync batch, External source, external_id and external_data.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"items\": [\n    {\n      \"type\": \"contact\",\n      \"body\": {\n        \"external_id\": \"crm-contact-1001\",\n        \"email\": \"alex@example.com\",\n        \"first_name\": \"Alex\",\n        \"last_name\": \"Nguyen\",\n        \"location_id\": {{location_id}}\n      }\n    },\n    {\n      \"type\": \"business\",\n      \"body\": {\n        \"external_id\": \"crm-company-42\",\n        \"name\": \"Acme Hospitality\",\n        \"email\": \"ops@acme.example\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Batch upsert completed. Each result entry matches the item type.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/sync/batch",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "sync",
                    "batch"
                  ]
                },
                "description": "Push up to 50 contacts, businesses, business–contact links, extended fields, or health topics in one request.\n\nThis is the primary write path for CRM, PMS, and POS integrations syncing data into myne (for example guests from MEWS).\n\nSend top-level contact fields plus your `external_id`, and optionally `contact_extended` bodies for extra partner keys/metadata used later for lookup and enrichment.\n\nRecords are always stamped with your Custom API app name—do not send `source` (or `contact_source` / `business_source`) in item bodies.\n\nEach item has a `type` and body; see the request schema for supported types.\n\nSee glossary: Sync batch, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"items\": [\n    {\n      \"type\": \"contact\",\n      \"body\": {\n        \"external_id\": \"crm-contact-1001\",\n        \"email\": \"alex@example.com\",\n        \"first_name\": \"Alex\",\n        \"last_name\": \"Nguyen\",\n        \"location_id\": {{location_id}}\n      }\n    },\n    {\n      \"type\": \"business\",\n      \"body\": {\n        \"external_id\": \"crm-company-42\",\n        \"name\": \"Acme Hospitality\",\n        \"email\": \"ops@acme.example\"\n      }\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Batch upsert complete\",\n  \"response\": {\n    \"count\": 2,\n    \"results\": [\n      {\n        \"type\": \"contact\",\n        \"result\": {\n          \"customer_id\": 104823,\n          \"external_id\": \"crm-001\",\n          \"created\": true\n        }\n      },\n      {\n        \"type\": \"business\",\n        \"result\": {\n          \"business_id\": 801,\n          \"external_id\": \"biz-001\",\n          \"is_new\": false\n        }\n      }\n    ]\n  }\n}"
            },
            {
              "name": "400 Invalid JSON, validation error, unsupported item type, or source fields in item bodies.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/sync/batch",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "sync",
                    "batch"
                  ]
                },
                "description": "Push up to 50 contacts, businesses, business–contact links, extended fields, or health topics in one request.\n\nThis is the primary write path for CRM, PMS, and POS integrations syncing data into myne (for example guests from MEWS).\n\nSend top-level contact fields plus your `external_id`, and optionally `contact_extended` bodies for extra partner keys/metadata used later for lookup and enrichment.\n\nRecords are always stamped with your Custom API app name—do not send `source` (or `contact_source` / `business_source`) in item bodies.\n\nEach item has a `type` and body; see the request schema for supported types.\n\nSee glossary: Sync batch, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"items\": [\n    {\n      \"type\": \"contact\",\n      \"body\": {\n        \"external_id\": \"crm-contact-1001\",\n        \"email\": \"alex@example.com\",\n        \"first_name\": \"Alex\",\n        \"last_name\": \"Nguyen\",\n        \"location_id\": {{location_id}}\n      }\n    },\n    {\n      \"type\": \"business\",\n      \"body\": {\n        \"external_id\": \"crm-company-42\",\n        \"name\": \"Acme Hospitality\",\n        \"email\": \"ops@acme.example\"\n      }\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Invalid JSON body\"\n}"
            },
            {
              "name": "500 Unexpected server error.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/sync/batch",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "sync",
                    "batch"
                  ]
                },
                "description": "Push up to 50 contacts, businesses, business–contact links, extended fields, or health topics in one request.\n\nThis is the primary write path for CRM, PMS, and POS integrations syncing data into myne (for example guests from MEWS).\n\nSend top-level contact fields plus your `external_id`, and optionally `contact_extended` bodies for extra partner keys/metadata used later for lookup and enrichment.\n\nRecords are always stamped with your Custom API app name—do not send `source` (or `contact_source` / `business_source`) in item bodies.\n\nEach item has a `type` and body; see the request schema for supported types.\n\nSee glossary: Sync batch, External source, external_id and external_data.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"items\": [\n    {\n      \"type\": \"contact\",\n      \"body\": {\n        \"external_id\": \"crm-contact-1001\",\n        \"email\": \"alex@example.com\",\n        \"first_name\": \"Alex\",\n        \"last_name\": \"Nguyen\",\n        \"location_id\": {{location_id}}\n      }\n    },\n    {\n      \"type\": \"business\",\n      \"body\": {\n        \"external_id\": \"crm-company-42\",\n        \"name\": \"Acme Hospitality\",\n        \"email\": \"ops@acme.example\"\n      }\n    }\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Something went wrong. Please try again later.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "CRM Embed",
      "item": [
        {
          "name": "Mint a short-lived Bearer token for CRM embed",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/crm-embed/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "crm-embed",
                "token"
              ]
            },
            "description": "Create a short-lived Bearer token (15 minutes) to load myne CRM embed widgets for one customer or business.\n\nCall from your server after Basic auth. Pass `entity_type` (customer or business) and `entity_id` in the body, then use `embed_token` in the widget bootstrap.\n\nSee glossary: CRM embed, Customer, Business.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"entity_type\": \"customer\",\n  \"entity_id\": {{customer_id}}\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [
            {
              "name": "200 Short-lived Bearer token for CRM embed bootstrap.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/crm-embed/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "crm-embed",
                    "token"
                  ]
                },
                "description": "Create a short-lived Bearer token (15 minutes) to load myne CRM embed widgets for one customer or business.\n\nCall from your server after Basic auth. Pass `entity_type` (customer or business) and `entity_id` in the body, then use `embed_token` in the widget bootstrap.\n\nSee glossary: CRM embed, Customer, Business.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"entity_type\": \"customer\",\n  \"entity_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"response\": {\n    \"embed_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJicmFuZF9pZCI6NDJ9.example\",\n    \"expires_in\": 900,\n    \"token_type\": \"Bearer\"\n  }\n}"
            },
            {
              "name": "400 Invalid JSON body or entity_type.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/crm-embed/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "crm-embed",
                    "token"
                  ]
                },
                "description": "Create a short-lived Bearer token (15 minutes) to load myne CRM embed widgets for one customer or business.\n\nCall from your server after Basic auth. Pass `entity_type` (customer or business) and `entity_id` in the body, then use `embed_token` in the widget bootstrap.\n\nSee glossary: CRM embed, Customer, Business.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"entity_type\": \"customer\",\n  \"entity_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"entity_type must be \\\"customer\\\" or \\\"business\\\"\"\n}"
            },
            {
              "name": "403 Path brand_id does not match credential scope.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/crm-embed/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "crm-embed",
                    "token"
                  ]
                },
                "description": "Create a short-lived Bearer token (15 minutes) to load myne CRM embed widgets for one customer or business.\n\nCall from your server after Basic auth. Pass `entity_type` (customer or business) and `entity_id` in the body, then use `embed_token` in the widget bootstrap.\n\nSee glossary: CRM embed, Customer, Business.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"entity_type\": \"customer\",\n  \"entity_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 403,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Unauthorized\"\n}"
            },
            {
              "name": "404 Customer or business not found for this brand.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/crm-embed/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "crm-embed",
                    "token"
                  ]
                },
                "description": "Create a short-lived Bearer token (15 minutes) to load myne CRM embed widgets for one customer or business.\n\nCall from your server after Basic auth. Pass `entity_type` (customer or business) and `entity_id` in the body, then use `embed_token` in the widget bootstrap.\n\nSee glossary: CRM embed, Customer, Business.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"entity_type\": \"customer\",\n  \"entity_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 404,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"Customer not found\"\n}"
            },
            {
              "name": "503 CRM embed signing is not configured in this environment.",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/crm-embed/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "brands",
                    "{{brand_id}}",
                    "crm-embed",
                    "token"
                  ]
                },
                "description": "Create a short-lived Bearer token (15 minutes) to load myne CRM embed widgets for one customer or business.\n\nCall from your server after Basic auth. Pass `entity_type` (customer or business) and `entity_id` in the body, then use `embed_token` in the widget bootstrap.\n\nSee glossary: CRM embed, Customer, Business.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"entity_type\": \"customer\",\n  \"entity_id\": {{customer_id}}\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "status": "Error",
              "code": 503,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"message\": \"CRM embed is not configured.\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Webhooks",
      "item": [
        {
          "name": "List outbound webhook subscriptions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "webhooks"
              ]
            },
            "description": "List HTTPS destinations this Custom API app created for brand events.\n\nSigning secrets are never returned on list or get. Copy the secret when you create or rotate.\n\nStaff can also manage the same rows in Settings → Webhooks → Outgoing.\n\nSee glossary: Outbound webhook, API Credentials."
          },
          "response": []
        },
        {
          "name": "Create an outbound webhook subscription",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "webhooks"
              ]
            },
            "description": "Subscribe an HTTPS URL to one or more brand event topics.\n\nThe signing secret is returned **once**. Store it and verify `X-Myne-Signature` with HMAC-SHA256 over `{unix_ts}.{rawBody}` using `crypto.timingSafeEqual`. Also send `X-Myne-Topic` and `X-Myne-Delivery-Id` (use `delivery_id` for idempotency).\n\nURL must be HTTPS and must not resolve to private, loopback, link-local, or metadata addresses. Default `ignore_own_writes` is true so events this app wrote are not echoed back.\n\nSee glossary: Outbound webhook, Signature.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"label\": \"string\",\n  \"url\": \"string\",\n  \"topics\": [\n    \"string\"\n  ],\n  \"ignore_own_writes\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "List outbound webhook topics",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/webhooks/topics",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "webhooks",
                "topics"
              ]
            },
            "description": "Return the topic catalog you can subscribe to: `customer.created`, `customer.updated`, `transaction.completed`, `customer.added_to_group`, `customer.removed_from_group`, `customer.connected`, `customer.form_submitted`, `promotion.redeemed`, `customer_activity.created`, `customer_activity.updated`.\n\nThe `customer.created` and `customer.updated` payloads include `connection_type` — `connected` when the customer has joined loyalty, `known` when myne has the customer but they have not joined, and `unknown` for an anonymous website visitor. It matches the `connection_type` on the customer list and get-customer endpoints.\n\nUnknown topic names on create or update return 400.\n\nSee glossary: Outbound webhook."
          },
          "response": []
        },
        {
          "name": "Get an outbound webhook subscription",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/webhooks/{{webhook_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "webhooks",
                "{{webhook_id}}"
              ]
            },
            "description": "Load one destination this app owns. The signing secret is not included.\n\nSee glossary: Outbound webhook."
          },
          "response": []
        },
        {
          "name": "Update an outbound webhook subscription",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/webhooks/{{webhook_id}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "webhooks",
                "{{webhook_id}}"
              ]
            },
            "description": "Change label, URL, topics, status (`enabled` or `disabled`), or `ignore_own_writes`.\n\nDoes not return a new signing secret. Use rotate for that.\n\nSee glossary: Outbound webhook."
          },
          "response": []
        },
        {
          "name": "Rotate the outbound webhook signing secret",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/webhooks/{{webhook_id}}/rotate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "webhooks",
                "{{webhook_id}}",
                "rotate"
              ]
            },
            "description": "Replace the HMAC signing secret. The new secret is returned **once** and cannot be shown again.\n\nSee glossary: Outbound webhook, Signature."
          },
          "response": []
        },
        {
          "name": "Remove an outbound webhook subscription",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/webhooks/{{webhook_id}}/remove",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "webhooks",
                "{{webhook_id}}",
                "remove"
              ]
            },
            "description": "Soft-delete this destination. Delivery stops. This app can only remove rows it created.\n\nSee glossary: Outbound webhook."
          },
          "response": []
        },
        {
          "name": "Send a signed ping to the destination",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/webhooks/{{webhook_id}}/ping",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "webhooks",
                "{{webhook_id}}",
                "ping"
              ]
            },
            "description": "POST a sample Connect-shaped envelope to the destination URL with the same signature headers as live events, and write a delivery row.\n\nSee glossary: Outbound webhook, Signature."
          },
          "response": []
        },
        {
          "name": "List recent outbound webhook deliveries",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/brands/{{brand_id}}/webhooks/{{webhook_id}}/deliveries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "brands",
                "{{brand_id}}",
                "webhooks",
                "{{webhook_id}}",
                "deliveries"
              ]
            },
            "description": "Recent delivery attempts for this subscription: status, HTTP code, topic, `delivery_id`, and a truncated error. Payloads and signing secrets are not stored.\n\nSee glossary: Outbound webhook."
          },
          "response": []
        }
      ]
    },
    {
      "name": "OAuth",
      "item": [
        {
          "name": "Get an access token (this brand)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "token"
              ]
            },
            "description": "Exchange API Credentials for a Connect access token. This route is on the Connect host only (`POST /v1/token`). Do not send a gateway JWT.\n\nJSON body is preferred. `client_id` and `client_secret` may also be sent as HTTP Basic on this call, or as `application/x-www-form-urlencoded`.\n\n- `grant_type=client_credentials` — creating brand only. No refresh token.\n- `grant_type=authorization_code` — requires `code` and `redirect_uri` (exact match). Returns `refresh_token`. Code cannot be reused. A second Allow for the same brand adds another refresh token; existing ones stay valid.\n- `grant_type=refresh_token` — requires `refresh_token`; issues a new access token and rotates only the refresh token you sent. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.\n\nAccess tokens last 3600 seconds. Issuing another access token does not invalidate earlier ones. Use `Authorization: Bearer {access_token}` on resource routes. `brand_id` in the token body is the creating brand (client credentials) or the granted brand (authorization code / refresh).\n\nAuthorize (browser, staff admin login): `https://app.myne.network/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&state={state}`. PKCE S256 is accepted when `code_challenge` was sent on authorize.\n\nSee the **OAuth** section for the this-brand and other-brand walkthroughs.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"grant_type\": \"client_credentials\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "auth": {
              "type": "noauth"
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code !== 200) { return; }",
                  "const json = pm.response.json();",
                  "if (json.access_token) { pm.environment.set('access_token', json.access_token); }",
                  "if (json.brand_id != null) { pm.environment.set('brand_id', String(json.brand_id)); }",
                  "if (json.refresh_token) { pm.environment.set('refresh_token', json.refresh_token); }"
                ]
              }
            }
          ],
          "response": [
            {
              "name": "Creating brand (no refresh token)",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "token"
                  ]
                },
                "description": "Exchange API Credentials for a Connect access token. This route is on the Connect host only (`POST /v1/token`). Do not send a gateway JWT.\n\nJSON body is preferred. `client_id` and `client_secret` may also be sent as HTTP Basic on this call, or as `application/x-www-form-urlencoded`.\n\n- `grant_type=client_credentials` — creating brand only. No refresh token.\n- `grant_type=authorization_code` — requires `code` and `redirect_uri` (exact match). Returns `refresh_token`. Code cannot be reused. A second Allow for the same brand adds another refresh token; existing ones stay valid.\n- `grant_type=refresh_token` — requires `refresh_token`; issues a new access token and rotates only the refresh token you sent. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.\n\nAccess tokens last 3600 seconds. Issuing another access token does not invalidate earlier ones. Use `Authorization: Bearer {access_token}` on resource routes. `brand_id` in the token body is the creating brand (client credentials) or the granted brand (authorization code / refresh).\n\nAuthorize (browser, staff admin login): `https://app.myne.network/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&state={state}`. PKCE S256 is accepted when `code_challenge` was sent on authorize.\n\nSee the **OAuth** section for the this-brand and other-brand walkthroughs.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"grant_type\": \"client_credentials\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "auth": {
                  "type": "noauth"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 3600,\n  \"brand_id\": 42\n}"
            },
            {
              "name": "Granted brand (includes refresh token)",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "token"
                  ]
                },
                "description": "Exchange API Credentials for a Connect access token. This route is on the Connect host only (`POST /v1/token`). Do not send a gateway JWT.\n\nJSON body is preferred. `client_id` and `client_secret` may also be sent as HTTP Basic on this call, or as `application/x-www-form-urlencoded`.\n\n- `grant_type=client_credentials` — creating brand only. No refresh token.\n- `grant_type=authorization_code` — requires `code` and `redirect_uri` (exact match). Returns `refresh_token`. Code cannot be reused. A second Allow for the same brand adds another refresh token; existing ones stay valid.\n- `grant_type=refresh_token` — requires `refresh_token`; issues a new access token and rotates only the refresh token you sent. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.\n\nAccess tokens last 3600 seconds. Issuing another access token does not invalidate earlier ones. Use `Authorization: Bearer {access_token}` on resource routes. `brand_id` in the token body is the creating brand (client credentials) or the granted brand (authorization code / refresh).\n\nAuthorize (browser, staff admin login): `https://app.myne.network/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&state={state}`. PKCE S256 is accepted when `code_challenge` was sent on authorize.\n\nSee the **OAuth** section for the this-brand and other-brand walkthroughs.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"grant_type\": \"client_credentials\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "auth": {
                  "type": "noauth"
                }
              },
              "status": "OK",
              "code": 200,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"access_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 3600,\n  \"brand_id\": 99,\n  \"refresh_token\": \"rt_example\"\n}"
            },
            {
              "name": "Missing grant_type",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "token"
                  ]
                },
                "description": "Exchange API Credentials for a Connect access token. This route is on the Connect host only (`POST /v1/token`). Do not send a gateway JWT.\n\nJSON body is preferred. `client_id` and `client_secret` may also be sent as HTTP Basic on this call, or as `application/x-www-form-urlencoded`.\n\n- `grant_type=client_credentials` — creating brand only. No refresh token.\n- `grant_type=authorization_code` — requires `code` and `redirect_uri` (exact match). Returns `refresh_token`. Code cannot be reused. A second Allow for the same brand adds another refresh token; existing ones stay valid.\n- `grant_type=refresh_token` — requires `refresh_token`; issues a new access token and rotates only the refresh token you sent. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.\n\nAccess tokens last 3600 seconds. Issuing another access token does not invalidate earlier ones. Use `Authorization: Bearer {access_token}` on resource routes. `brand_id` in the token body is the creating brand (client credentials) or the granted brand (authorization code / refresh).\n\nAuthorize (browser, staff admin login): `https://app.myne.network/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&state={state}`. PKCE S256 is accepted when `code_challenge` was sent on authorize.\n\nSee the **OAuth** section for the this-brand and other-brand walkthroughs.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"grant_type\": \"client_credentials\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "auth": {
                  "type": "noauth"
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"invalid_request\",\n  \"error_description\": \"grant_type is required\"\n}"
            },
            {
              "name": "Code or refresh token rejected",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "token"
                  ]
                },
                "description": "Exchange API Credentials for a Connect access token. This route is on the Connect host only (`POST /v1/token`). Do not send a gateway JWT.\n\nJSON body is preferred. `client_id` and `client_secret` may also be sent as HTTP Basic on this call, or as `application/x-www-form-urlencoded`.\n\n- `grant_type=client_credentials` — creating brand only. No refresh token.\n- `grant_type=authorization_code` — requires `code` and `redirect_uri` (exact match). Returns `refresh_token`. Code cannot be reused. A second Allow for the same brand adds another refresh token; existing ones stay valid.\n- `grant_type=refresh_token` — requires `refresh_token`; issues a new access token and rotates only the refresh token you sent. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.\n\nAccess tokens last 3600 seconds. Issuing another access token does not invalidate earlier ones. Use `Authorization: Bearer {access_token}` on resource routes. `brand_id` in the token body is the creating brand (client credentials) or the granted brand (authorization code / refresh).\n\nAuthorize (browser, staff admin login): `https://app.myne.network/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&state={state}`. PKCE S256 is accepted when `code_challenge` was sent on authorize.\n\nSee the **OAuth** section for the this-brand and other-brand walkthroughs.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"grant_type\": \"client_credentials\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "auth": {
                  "type": "noauth"
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"invalid_grant\",\n  \"error_description\": \"Authorization code is invalid\"\n}"
            },
            {
              "name": "Unknown grant_type",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "token"
                  ]
                },
                "description": "Exchange API Credentials for a Connect access token. This route is on the Connect host only (`POST /v1/token`). Do not send a gateway JWT.\n\nJSON body is preferred. `client_id` and `client_secret` may also be sent as HTTP Basic on this call, or as `application/x-www-form-urlencoded`.\n\n- `grant_type=client_credentials` — creating brand only. No refresh token.\n- `grant_type=authorization_code` — requires `code` and `redirect_uri` (exact match). Returns `refresh_token`. Code cannot be reused. A second Allow for the same brand adds another refresh token; existing ones stay valid.\n- `grant_type=refresh_token` — requires `refresh_token`; issues a new access token and rotates only the refresh token you sent. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.\n\nAccess tokens last 3600 seconds. Issuing another access token does not invalidate earlier ones. Use `Authorization: Bearer {access_token}` on resource routes. `brand_id` in the token body is the creating brand (client credentials) or the granted brand (authorization code / refresh).\n\nAuthorize (browser, staff admin login): `https://app.myne.network/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&state={state}`. PKCE S256 is accepted when `code_challenge` was sent on authorize.\n\nSee the **OAuth** section for the this-brand and other-brand walkthroughs.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"grant_type\": \"client_credentials\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "auth": {
                  "type": "noauth"
                }
              },
              "status": "Error",
              "code": 400,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"unsupported_grant_type\",\n  \"error_description\": \"grant_type must be client_credentials, authorization_code, or refresh_token\"\n}"
            },
            {
              "name": "401 invalid_client",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "token"
                  ]
                },
                "description": "Exchange API Credentials for a Connect access token. This route is on the Connect host only (`POST /v1/token`). Do not send a gateway JWT.\n\nJSON body is preferred. `client_id` and `client_secret` may also be sent as HTTP Basic on this call, or as `application/x-www-form-urlencoded`.\n\n- `grant_type=client_credentials` — creating brand only. No refresh token.\n- `grant_type=authorization_code` — requires `code` and `redirect_uri` (exact match). Returns `refresh_token`. Code cannot be reused. A second Allow for the same brand adds another refresh token; existing ones stay valid.\n- `grant_type=refresh_token` — requires `refresh_token`; issues a new access token and rotates only the refresh token you sent. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.\n\nAccess tokens last 3600 seconds. Issuing another access token does not invalidate earlier ones. Use `Authorization: Bearer {access_token}` on resource routes. `brand_id` in the token body is the creating brand (client credentials) or the granted brand (authorization code / refresh).\n\nAuthorize (browser, staff admin login): `https://app.myne.network/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&state={state}`. PKCE S256 is accepted when `code_challenge` was sent on authorize.\n\nSee the **OAuth** section for the this-brand and other-brand walkthroughs.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"grant_type\": \"client_credentials\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "auth": {
                  "type": "noauth"
                }
              },
              "status": "Error",
              "code": 401,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"invalid_client\",\n  \"error_description\": \"Client authentication failed\"\n}"
            },
            {
              "name": "500 server_error",
              "originalRequest": {
                "method": "POST",
                "header": [
                  {
                    "key": "Accept",
                    "value": "application/json"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/v1/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "v1",
                    "token"
                  ]
                },
                "description": "Exchange API Credentials for a Connect access token. This route is on the Connect host only (`POST /v1/token`). Do not send a gateway JWT.\n\nJSON body is preferred. `client_id` and `client_secret` may also be sent as HTTP Basic on this call, or as `application/x-www-form-urlencoded`.\n\n- `grant_type=client_credentials` — creating brand only. No refresh token.\n- `grant_type=authorization_code` — requires `code` and `redirect_uri` (exact match). Returns `refresh_token`. Code cannot be reused. A second Allow for the same brand adds another refresh token; existing ones stay valid.\n- `grant_type=refresh_token` — requires `refresh_token`; issues a new access token and rotates only the refresh token you sent. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.\n\nAccess tokens last 3600 seconds. Issuing another access token does not invalidate earlier ones. Use `Authorization: Bearer {access_token}` on resource routes. `brand_id` in the token body is the creating brand (client credentials) or the granted brand (authorization code / refresh).\n\nAuthorize (browser, staff admin login): `https://app.myne.network/oauth/authorize?client_id={client_id}&redirect_uri={redirect_uri}&response_type=code&state={state}`. PKCE S256 is accepted when `code_challenge` was sent on authorize.\n\nSee the **OAuth** section for the this-brand and other-brand walkthroughs.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"grant_type\": \"client_credentials\",\n  \"client_id\": \"{{client_id}}\",\n  \"client_secret\": \"{{client_secret}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "auth": {
                  "type": "noauth"
                }
              },
              "status": "Error",
              "code": 500,
              "_postman_previewlanguage": "json",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"server_error\",\n  \"error_description\": \"Token service is unavailable\"\n}"
            }
          ]
        }
      ]
    }
  ]
}
