{
  "openapi": "3.1.0",
  "info": {
    "title": "myne Connect Brands API",
    "version": "1.0.0",
    "description": "Brand-scoped API for partners building CRM, sync, and loyalty integrations.\n\nUse this reference when you need to keep myne in sync with your own systems—CRM, website, middleware, or POS extensions—without waiting for a packaged connector.\n\nCreate credentials in myne at [API Credentials](https://app.myne.network/settings/integration/selected/brand-basic-auth) (brand admins).\n\nThen pick how you will call Connect: **[HTTP Basic](#tag/HTTP-Basic)** (this brand, no token) or **[OAuth](#tag/OAuth)** (Bearer token for this brand, or another brand that Allows the client).\n\n**Brand hierarchy:** Every resource path is scoped to one **Brand**. Some brands sit in a Head office [Organisation (Head office group)](#glossary-organisation-head-office-group). Discover that on **[Get credential scope and brand identity](#operation/getConnectMe)** (`GET /v1/me`) or **[Get brand bootstrap configuration](#operation/getBrandContext)** (`GET …/brand`): read `response.brand.organisation` (or the brand object in the Brand response). It names `role`, `head_office_brand`, and `sibling_brands`. Solo brands return `organisation: null`. Paths do not become org-wide—use those ids only as context (for example Collect Across Venues stamp labels).\n\nTerms in **bold**, or linked to the glossary, are defined at the bottom of this page (for example **Surface**, **External source**, **API Credentials**).\n\n**Base URL:** `https://connect.myne.network` (production) or `https://connect.dev.myne.network` (development).\n\n**Authentication:** HTTP Basic or Bearer (`POST /v1/token`).\n\nCreate a Client ID and secret in myne at [API Credentials](https://app.myne.network/settings/integration/selected/brand-basic-auth) while signed in as a brand admin. The Client Secret is shown once when you create or rotate.\n\nSend `Authorization: Basic …` **or** `Authorization: Bearer …` on resource calls to `connect.myne.network` `/v1`. The [HTTP Basic](#tag/HTTP-Basic) and [OAuth](#tag/OAuth) sections walk through each method.\n\nDo not rely on HTTP clients following redirects for authenticated calls — many strip `Authorization` on cross-origin redirects. Connect v1 routes respond in place (no auth redirect).\n\n**Response shape:** Successful resource calls return JSON with `message` (status text) and `response` (payload). Resource errors return `{ \"message\": \"…\" }` with an appropriate HTTP status. `POST /v1/token` success is `{ access_token, token_type, expires_in, brand_id }` (plus `refresh_token` for authorization-code); token errors are `{ error, error_description }`.\n**Field naming:** All request and response JSON fields, and query parameters, use `snake_case` (for example `brand_id`, `amount_in_cents`, `has_more`).\n\n## Helpful downloads\n\nReady-made files for tooling and import:\n\n- **OpenAPI:** [openapi.json](./openapi.json) — machine-readable API definition\n- **Postman starter:** [myne-connect-brands-api-basic.postman_collection.json](./postman/myne-connect-brands-api-basic.postman_collection.json) — core routes\n- **Postman full:** [myne-connect-brands-api.postman_collection.json](./postman/myne-connect-brands-api.postman_collection.json) — all documented routes\n- **Postman environment:** [myne-connect-brands.postman_environment.json](./postman/myne-connect-brands.postman_environment.json) — `connect.myne.network`\n\nImport the collection and environment into Postman (or use **Import → Link** with these URLs). Set `client_id` and `client_secret` from API Credentials. Run **Get credential scope** (Basic `GET /v1/me`) or **Get an access token (this brand)** then **Get credential scope with Bearer**. For other brands, authorize in the browser, then **Get an access token (authorization code)** and **Refresh an access token**. Authorize cannot run inside Postman.\n\n## Glossary\n\nExpand a term for its definition. How-to for a call lives on that operation (and its section)—not here.\n\n<details id=\"glossary-brand\">\n<summary><strong>Brand</strong></summary>\n\n<p>The business using myne—your organisation in the product, and the tenant for every Connect route.</p>\n<p>Call <code>GET /v1/me</code> first to learn your <code>brand_id</code>, then pass it in the path on other routes. <strong>API Credentials</strong> for the creating brand are scoped to that brand only. Other brands must Allow the same Client ID at <code>/oauth/authorize</code>.</p>\n<p><code>GET /v1/me</code> and <code>GET …/brand</code> return partner-safe brand fields. When the brand is in a Head office group, see <strong>Organisation (Head office group)</strong> on <code>brand.organisation</code>. Paths stay scoped to the authenticated brand — org fields are context only.</p>\n</details>\n\n<details id=\"glossary-organisation-head-office-group\">\n<summary><strong>Organisation (Head office group)</strong></summary>\n\n<p>A Head office group of brands: one Head office brand and one or more venue brands that share customers and can run cross-venue offers.</p>\n<p><strong>Where to see it:</strong> After auth, call <code>GET /v1/me</code> (read <code>response.brand.organisation</code>) or <code>GET /v1/brands/{brand_id}/brand</code> (same object on the brand in the response array). Solo brands return <code>organisation: null</code>.</p>\n<p><strong>Fields:</strong> - <code>id</code> / <code>name</code> — the organisation - <code>role</code> — <code>venue</code> or <code>head_office</code> for the brand you authenticated as - <code>head_office_brand</code> — parent Head office (<code>id</code> + <code>name</code>; same as this brand when <code>role</code> is <code>head_office</code>) - <code>sibling_brands</code> — peer venue brands (<code>id</code> + <code>name</code>; excludes this brand and the Head office)</p>\n<p>Resource paths do <strong>not</strong> become organisation-scoped. Use these ids to understand relationships (for example Collect Across Venues stamp labels on evaluate <code>venue_progress</code>). Head office Collect Across Venues that target a child brand can appear on that child’s promotion catalog and redeem path for that type only.</p>\n</details>\n\n<details id=\"glossary-location\">\n<summary><strong>Location</strong></summary>\n\n<p>A specific venue or site customers visit—how you split offers, links, and reporting by place.</p>\n<p>Use <code>location_id</code> to scope customer browse, sync payloads, and promotion lists to one venue.</p>\n</details>\n\n<details id=\"glossary-customer\">\n<summary><strong>Customer</strong></summary>\n\n<p>Someone you recognise with a full profile—usually a name and contact details you can message or segment.</p>\n<p>Customer IDs come from browse, sync, create/upsert, or your POS. Most loyalty and promotion calls need a customer id (or an external id you can resolve).</p>\n<p>GET customer (by id or by external id) returns linked system ids in <code>extended[]</code>, not a single top-level source/external_id. The profile includes pass id, Brand Points, and myne cashback. Visit totals sit on <code>stats</code>.</p>\n</details>\n\n<details id=\"glossary-visitor\">\n<summary><strong>Visitor</strong></summary>\n\n<p>Spend or activity you can see before you have a full profile—or alongside one—when someone has not fully signed up.</p>\n<p>Customer browse returns visitors only when you set <code>include_visitors=true</code>. Otherwise results are known customers.</p>\n</details>\n\n<details id=\"glossary-business\">\n<summary><strong>Business</strong></summary>\n\n<p>A B2B account (company or organisation) linked to contacts in myne.</p>\n<p>This is not the same as a location. A business may span multiple venues; a location is one physical site.</p>\n</details>\n\n<details id=\"glossary-connection-state\">\n<summary><strong>Connection state</strong></summary>\n\n<p>How well you know someone on a profile:</p>\n<ul><li><strong>connected</strong> — they have joined your programme through a join experience</li><li><strong>known</strong> — you can identify them, but they may not have fully connected</li><li><strong>unknown</strong> — you see activity without full identity</li></ul>\n<p>Filter customer browse with the <code>connection</code> query parameter. This is about relationship state on the profile—not plan usage metering named “connections”.</p>\n</details>\n\n<details id=\"glossary-customer-group\">\n<summary><strong>Customer group</strong></summary>\n\n<p>A named audience of customers in myne. Paths use <code>filter-groups</code>; the partner-facing name is always <strong>customer group</strong>.</p>\n<p>Membership can be maintained by dynamic refresh, scheduled refresh, import replace, or automation-driven changes.</p>\n<p>Live offers also have a <strong>Claimed - {offer name}</strong> group. Treat it as a read segment: membership is added when a customer claims the offer on the join page. Connect can list the group and its members; it cannot add members or assign a claim.</p>\n<p>Connect lists which customer groups exist, which customers are in one group, and which customer groups a single customer belongs to. Scope browse or search with <code>customer_group_id</code>.</p>\n</details>\n\n<details id=\"glossary-business-group\">\n<summary><strong>Business group</strong></summary>\n\n<p>A named audience of businesses (B2B accounts) in myne—the account-side counterpart to a <strong>Customer group</strong>.</p>\n<p>Membership can use the same patterns as customer groups. Business groups are configured in the product; Connect Brands does not currently expose dedicated business-group list routes.</p>\n</details>\n\n<details id=\"glossary-frequency\">\n<summary><strong>Frequency</strong></summary>\n\n<p>How often someone tends to visit—<strong>New</strong>, <strong>Frequent</strong>, or <strong>Infrequent</strong>.</p>\n<p>Returned on customer rows and available as a browse filter so you can tailor welcome paths vs loyalty depth.</p>\n</details>\n\n<details id=\"glossary-engagement\">\n<summary><strong>Engagement</strong></summary>\n\n<p>A simple read on whether someone is <strong>active with your brand</strong> or <strong>drifting</strong>—useful for quick prioritisation on a profile.</p>\n<p>Returned on customer profiles when myne has enough signal to classify them.</p>\n</details>\n\n<details id=\"glossary-extended-profile\">\n<summary><strong>Extended profile</strong></summary>\n\n<p>Timeline beyond the core customer or business fields: POS records, marketing activities, transactions, custom properties, and similar.</p>\n<p>On GET customer, <code>extended[]</code> is the identity list (source + external_id per connected system). The <code>/customers/{id}/extended</code> route is the activity timeline, not that identity list.</p>\n</details>\n\n<details id=\"glossary-credit\">\n<summary><strong>Credit</strong></summary>\n\n<p>Stored value on a partner loyalty programme (for example Wrapped/Giftit or myne Cashback).</p>\n<p>Credit endpoints read balance, ledger history, and add value. A customer may have no linked credit yet—in that case balance reads as zero and there is nothing to top up until credit is linked.</p>\n<p>GET customer <code>cashback</code> is the internal cashback ledger for the brand, not a live Wrapped or Giftit credit GET. Use the credit endpoints when you need that partner programme's current balance.</p>\n</details>\n\n<details id=\"glossary-points\">\n<summary><strong>Points</strong></summary>\n\n<p>Brand Points balance and earn/redeem history when Brand Points is enabled for the brand.</p>\n<p>Points endpoints mirror credit: balance, ledger, and top-up. Top-ups always need a reason for the staff ledger.</p>\n</details>\n\n<details id=\"glossary-wallet-pass\">\n<summary><strong>Wallet pass</strong></summary>\n\n<p>Apple Wallet or Google Wallet identifiers tied to a customer's credit or rewards pass.</p>\n<p>GET customer returns <code>pass_id</code> (the loyalty pass code) with points and cashback. Wallet serials are also returned alongside balance on the credit endpoint.</p>\n</details>\n\n<details id=\"glossary-sync-batch\">\n<summary><strong>Sync batch</strong></summary>\n\n<p>The main write path for pushing contacts, businesses, links, and extended metadata into myne.</p>\n<p>Send up to 50 typed items per request. Prefer sync for bulk imports; use single customer upsert for one-off registrations. See <strong>external_id and external_data</strong>.</p>\n</details>\n\n<details id=\"glossary-external-id-and-external-data\">\n<summary><strong>external_id and external_data</strong></summary>\n\n<p><code>external_id</code> is <strong>your</strong> identifier for a record (for example a CRM id or website user id). <code>external_data</code> is any extra attributes you choose to store on the core row.</p>\n<p>Lookups and promotion evaluate/apply resolve that same <code>external_id</code> under your <strong>External source</strong>. GET customer lists every known source and id in <code>extended[]</code> rather than a single top-level pair. For extra partner metadata, also write extended rows under your source.</p>\n</details>\n\n<details id=\"glossary-external-source\">\n<summary><strong>External source</strong></summary>\n\n<p>The integration key (<code>source</code>) that owns a customer or business <code>external_id</code> and extended fields.</p>\n<p>Each <strong>API Credentials</strong> client has one source, derived from the app label in Integrations. <code>GET …/me</code> returns it as <code>source</code>.</p>\n<p>Do not invent a source, and do not send <code>source</code> on write requests. Writes always stamp your credential’s source. Lookups default to your source.</p>\n</details>\n\n<details id=\"glossary-crm-embed\">\n<summary><strong>CRM embed</strong></summary>\n\n<p>An embedded myne CRM panel inside your own app.</p>\n<p>Mint a short-lived Bearer token from Connect, then load the widget for a specific customer or business.</p>\n</details>\n\n<details id=\"glossary-api-credentials\">\n<summary><strong>API Credentials</strong></summary>\n\n<p>Client ID and Client Secret for partner server-to-server access (formerly Custom API Basic).</p>\n<p>Create them in myne at <a href=\"https://app.myne.network/settings/integration/selected/brand-basic-auth\" target=\"_blank\" rel=\"noopener noreferrer\">API Credentials</a> while signed in as a <strong>brand admin</strong>. Copy the Client Secret when it is shown—it cannot be retrieved later.</p>\n<p><strong>Two uses, one Client ID:</strong> HTTP Basic or <code>POST /v1/token</code> with <code>grant_type=client_credentials</code> only reaches the <strong>creating</strong> brand. Other myne brands Allow the same Client ID in the browser (<code>https://app.myne.network/oauth/authorize</code>); that returns a code you exchange for tokens for <strong>that</strong> brand.</p>\n<p>Resource calls accept <code>Authorization: Basic …</code> or <code>Authorization: Bearer …</code>. Access tokens last one hour; issuing another token does not cut earlier ones short. Authorization-code installs also return a refresh token. A brand can have more than one live refresh token for the same Client ID (for example two partner entities). Refreshing one does not invalidate the others. Revoke or rotate the secret stops them all.</p>\n</details>\n\n<details id=\"glossary-outbound-webhook\">\n<summary><strong>Outbound webhook</strong></summary>\n\n<p>An HTTPS destination myne POSTs when a subscribed brand event happens.</p>\n<p>Create subscriptions with Connect (<code>/v1/brands/{brand_id}/webhooks</code>) or in Settings → Webhooks → Outgoing. The signing secret is returned only on create and rotate. Partners only see destinations their Custom API app created. Poll collection GETs with <code>updated_since</code> when you prefer pull over push.</p>\n<p>Topics include customer create/update, transactions, group membership, venue check-in, connection-form submit, and promotion redeem. The <code>customer.created</code> and <code>customer.updated</code> payloads include <code>connection_type</code> — <code>connected</code> when the customer has joined loyalty, <code>known</code> when myne has the customer but they have not joined, and <code>unknown</code> for an anonymous website visitor. It matches the <code>connection_type</code> on the customer list and get-customer endpoints.</p>\n</details>\n\n<details id=\"glossary-signature\">\n<summary><strong>Signature</strong></summary>\n\n<p>HMAC-SHA256 of <code>{unix_timestamp}.{rawBody}</code> sent as <code>X-Myne-Signature: t=&lt;unix&gt;,v1=&lt;hex&gt;</code>.</p>\n<p>Also send <code>X-Myne-Topic</code> and <code>X-Myne-Delivery-Id</code>. Verify with <code>crypto.timingSafeEqual</code>. Treat <code>delivery_id</code> as an idempotency key so retries are safe.</p>\n</details>\n\n<details id=\"glossary-updated-since\">\n<summary><strong>updated_since</strong></summary>\n\n<p>Optional ISO-8601 timestamp on collection list and search routes. When set, only rows whose watermark is on or after that instant (UTC, inclusive) are returned. Omit the parameter for today's full list.</p>\n<p>Most collections use <code>updated_at</code>. Credit and points ledgers use <code>created_at</code>. Invalid timestamps return 400.</p>\n</details>\n\n<details id=\"glossary-promotion\">\n<summary><strong>Promotion</strong></summary>\n\n<p>An offer set up in myne. It states what the customer gets, when it can be used, and where it can appear.</p>\n<p>Each promotion can also say whether more than one offer may sit on the same order. Connect list, get, search, and evaluate responses include <code>image_url</code> (the offer image, or null).</p>\n<p><strong>Collect Across Venues</strong> (<code>venue_unlock</code>) is a passport offer: the same action at each selected site or Head office child brand unlocks a reward. On register/ordering evaluate (<code>lsk</code>/<code>lso</code>), progress is in <code>venue_progress</code>; incomplete passports use <code>VENUE_UNLOCK_NOT_ELIGIBLE</code>. Head office offers of this type that target a child brand are visible on that child’s Connect catalog and redeem path (this type only).</p>\n<p>Some offers use <strong>Buy X Get Y</strong> rules. Evaluate promotions applies those on the open order.</p>\n</details>\n\n<details id=\"glossary-buy-x-get-y\">\n<summary><strong>Buy X Get Y</strong></summary>\n\n<p>A promotion mechanic: the customer must buy quantity X of matching items, then quantity Y of matching items is discounted (often free).</p>\n<p>When buy (X) and get (Y) use the same products or categories, the paid X units are reserved first. Y does not cancel X. Example: Buy 1 Get 1 Free needs two matching units — pay for the first, discount the second. Buy 3 Get 1 Free needs four matching units.</p>\n<p>When buy and get are different products or categories, the customer still needs enough X on the order, and the discount applies only to matching Y lines.</p>\n<p>If allow-repeat is on, each complete set of paid X plus discounted Y can apply again on the same order. Rolling Buy X Get Y can bank the buy side from earlier purchases in the period; then the current order only needs matching Y.</p>\n</details>\n\n<details id=\"glossary-surface\">\n<summary><strong>Surface</strong></summary>\n\n<p>Where the sale happens. Examples: a join page, an ordering app, a register, or an online store.</p>\n<p>Send <code>surface</code> to match that place: <code>lsk</code> for register, <code>lso</code> for ordering. Use the same value on evaluate, apply, and complete. Those open-order calls always need <code>surface</code> — do not send <code>integration_type</code> there.</p>\n<p>On browse, customer list, and search you may send <code>integration_type</code> instead as a coarse list filter when you only need which promotions can show (<code>ordering</code>, <code>listing</code>, or <code>ecommerce</code>) from customer <strong>Show vs available</strong> groups, without knowing which app will evaluate the sale. Some availability values are marketing only and are not used on evaluate, apply, or complete.</p>\n</details>\n\n<details id=\"glossary-show-vs-available\">\n<summary><strong>Show vs available</strong></summary>\n\n<p>Two audience rules on a promotion:</p>\n<ul><li><strong>Show</strong> — who may see the offer</li><li><strong>Available</strong> — who may use the offer</li></ul>\n<p>A customer can see an offer and still be unable to use it until they meet the available rules.</p>\n<p>A separate redeem rule is join-page claim. When an offer requires it, evaluate returns <code>non_redeemable_cause.code</code> <code>CLAIM_REQUIRED</code> until the customer has claimed on the join page. That is not assigned by this API.</p>\n</details>\n\n<details id=\"glossary-order-line-pos-id\">\n<summary><strong>Order line pos_id</strong></summary>\n\n<p>The product id for a line on the open order (<code>cart.items[].metadata.pos_id</code>). It comes from the catalogue at the place you are selling. Example: the register item id at a POS.</p>\n<p>We need this on each line so we can match the order to the promotion rules. It is not your Custom API customer or product id.</p>\n</details>\n\n<details id=\"glossary-promotion-stacking\">\n<summary><strong>Promotion stacking</strong></summary>\n\n<p>Whether more than one promotion can sit on the same open order at once.</p>\n<p>Each promotion has its own allow-stacking setting in myne. If stacking is allowed, lock each reward with the same order id. One complete call then records all of them.</p>\n</details>\n\n<details id=\"glossary-basket-id-and-myne-reference\">\n<summary><strong>basket_id and MYNE reference</strong></summary>\n\n<p>Your id for the open order. Use a POS order id, or generate a unique id. In the API this field is named <code>basket_id</code>.</p>\n<p>We need the same id on evaluate, apply, and complete so we treat them as one order. If paid sales already go to myne, write that same id onto the order with the prefix MYNE so myne can match the payment. If you send Complete promotions after the customer pays, you do not write that mark.</p>\n</details>\n\n<details id=\"glossary-transaction\">\n<summary><strong>Transaction</strong></summary>\n\n<p>A paid purchase stored in myne after the customer has paid.</p>\n<p>Before payment, the sale is an open order. Recording a promotion redemption does not create this purchase. The purchase is created when the POS or ordering system sends the sale.</p>\n</details>\n\n<details id=\"glossary-activity\">\n<summary><strong>Activity</strong></summary>\n\n<p>A CRM timeline entry on a contact—manual notes and calls you log via Activities, plus other rows that appear in the staff activity feed.</p>\n<p>This is distinct from <strong>Customer events</strong> (<code>POST …/events</code>), which are integration-sourced timeline posts stamped with your External source.</p>\n</details>\n\n<details id=\"glossary-task\">\n<summary><strong>Task</strong></summary>\n\n<p>A staff follow-up work item in myne—title, status, due date, assignees, and linked contacts or businesses.</p>\n<p>Connect can list and search tasks. Creating tasks via Connect is not available yet.</p>\n</details>",
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://connect.myne.network",
      "description": "Production"
    },
    {
      "url": "https://connect.dev.myne.network",
      "description": "Development"
    }
  ],
  "tags": [
    {
      "name": "Authorisation",
      "description": "Start here. Create API Credentials in myne, then confirm which brand they reach before you call other routes.\n\nYou have one Client ID. Use it two ways:\n\n- **[HTTP Basic](#tag/HTTP-Basic)** — send the ID and secret on each request. Creating brand only. No token step.\n- **[OAuth](#tag/OAuth)** — mint a Bearer token for this brand, or for another myne brand that Allows the client.\n\nCall **Get credential scope and brand identity** first. You do not pass `brand_id` on that path — the credential or token selects the brand. Use the returned `brand_id` on every other `/v1/brands/{brand_id}/…` route.\n\nOn that same response, read `brand.organisation` when present to see Head office / venue relationships (`role`, `head_office_brand`, `sibling_brands`). Details: **[Brand](#tag/Brand)** and [Organisation (Head office group)](#glossary-organisation-head-office-group).\n\nSend `Authorization` on every request to the final `connect.myne.network` `/v1` URL. Do not rely on following redirects for auth.\n\nRotate secret keeps the same Client ID and invalidates outstanding access tokens. Partners do not create Auth0 apps."
    },
    {
      "name": "HTTP Basic",
      "x-traitTag": true,
      "description": "Send your Client ID and secret on each request. There is no token step.\n\nThis only reaches the **creating** brand — the brand where you added the client in myne.\n\n1. Open [API Credentials](https://app.myne.network/settings/integration/selected/brand-basic-auth) as a **brand admin**. Add a client. Copy **Client ID** (`myne_api_…`) and **Client Secret** (shown once).\n2. Call `GET https://connect.myne.network/v1/me` with:\n\n   `Authorization: Basic base64(client_id:client_secret)`\n\n3. Read `response.brand_id` and `response.source`. Use that `brand_id` on every other path. If `response.brand.organisation` is present, note `role`, `head_office_brand`, and `sibling_brands` for multi-venue context.\n\nPath `brand_id` must match the credential. Resource errors look like `{ \"message\": \"…\" }`."
    },
    {
      "name": "OAuth",
      "description": "Mint a Bearer token, then send `Authorization: Bearer {access_token}` on resource calls. Access tokens last **one hour**. Issuing another access token does not invalidate earlier ones; they stay valid until that expiry, or until you rotate the secret or Revoke the grant.\n\nCall `POST https://connect.myne.network/v1/token` (this Connect host only). JSON body is preferred. You can also send `client_id` and `client_secret` as HTTP Basic on that call, or as `application/x-www-form-urlencoded`.\n\nToken errors use `error` / `error_description` (not the resource `{ message }` envelope).\n\n## This brand\n\nUse this when you only need the creating brand. There is no refresh token.\n\n```json\n{ \"grant_type\": \"client_credentials\", \"client_id\": \"myne_api_…\", \"client_secret\": \"…\" }\n```\n\nThen call **Get credential scope and brand identity** with `Authorization: Bearer {access_token}`.\n\n## Other myne brands\n\nUse this when another brand Allows your Client ID. Each Allow and code exchange issues its own refresh token. Refreshing one token rotates only that token; other refresh tokens for the same brand stay valid.\n\n1. On the client in [API Credentials](https://app.myne.network/settings/integration/selected/brand-basic-auth), save https redirect URLs (one per line, exact match). `http` localhost is allowed on development only. An empty redirect list cannot authorize.\n2. Send a brand **admin** to authorize in the browser:\n\n   `https://app.myne.network/oauth/authorize?client_id=myne_api_…&redirect_uri=https://partner.example/oauth/callback&response_type=code&state=YOUR_STATE`\n\n   Development: `https://app.dev.myne.network/oauth/authorize` with the same query. They sign in, pick that brand, and Allow. Deny returns `error=access_denied` to your redirect. Unknown redirects are not sent to an unregistered host. PKCE S256 is accepted (`code_challenge` and `code_challenge_method=S256`).\n3. Exchange the code (same `redirect_uri`, exact match). A code cannot be reused.\n\n   ```json\n   { \"grant_type\": \"authorization_code\", \"client_id\": \"…\", \"client_secret\": \"…\", \"code\": \"…\", \"redirect_uri\": \"…\" }\n   ```\n\n   The response includes `access_token`, `refresh_token`, `expires_in`, and the granted `brand_id`.\n4. Refresh before the hour is up. That refresh token rotates: use the new `refresh_token`; the one you just used fails. Other refresh tokens for the same brand stay valid. Earlier access tokens stay valid until they expire.\n\n   ```json\n   { \"grant_type\": \"refresh_token\", \"client_id\": \"…\", \"client_secret\": \"…\", \"refresh_token\": \"…\" }\n   ```\n\nThen call `GET /v1/me` or `/v1/brands/{brand_id}/…` with Bearer. Path `brand_id` must match the token."
    },
    {
      "name": "Brand",
      "description": "Partner-safe brand display settings—name, logos, colours, and contact fields.\n\n### Where to see relationships\n\n1. Call **[Get credential scope and brand identity](#operation/getConnectMe)** (`GET /v1/me`) after auth — read `response.brand`.\n2. Or call **[Get brand bootstrap configuration](#operation/getBrandContext)** (`GET /v1/brands/{brand_id}/brand`) for the same brand object without credential metadata.\n\nWhen this brand is in a Head office group, `organisation` is present:\n\n- `role` — `venue` or `head_office`\n- `head_office_brand` — parent Head office (`id` + `name`)\n- `sibling_brands` — peer venue brands (`id` + `name`; excludes this brand and the Head office)\n\nSolo brands return `organisation: null`. Resource paths stay scoped to this brand’s `id` — org fields are context only (see [Organisation (Head office group)](#glossary-organisation-head-office-group)). For Collect Across Venues on evaluate, stamp brand ids on `venue_progress` match these ids."
    },
    {
      "name": "Integrations",
      "description": "Connected apps and data sources for the brand, without secrets or tokens."
    },
    {
      "name": "Location",
      "description": "Venues for the brand. Use location IDs when scoping customers, sync, and promotions."
    },
    {
      "name": "Customer",
      "description": "Browse, create or update, and load customer profiles—including extended timeline data."
    },
    {
      "name": "Customer groups",
      "description": "Customer groups and which customers belong to them. Membership may refresh dynamically, on a schedule, via import replace, or through automation-driven changes."
    },
    {
      "name": "Credit",
      "description": "Stored-value / cashback balance, ledger history with reason, and top-up."
    },
    {
      "name": "Points",
      "description": "Brand Points balance, ledger history with reason, and top-up when points are enabled."
    },
    {
      "name": "Products",
      "description": "Product catalog browse, search, and category metadata—including surface-specific external IDs."
    },
    {
      "name": "Promotions",
      "description": "This section covers offers in myne: listing what is set up, checking who can see or use them, and recording a reward on an open sale at a POS or ordering app.\n\nRead the life cycle below first. Then open **Evaluate promotions**, **Apply a promotion**, or **Complete promotions** for the fields of that one call. Linked words open the matching [glossary](#glossary) definition.\n\nA [Promotion](#glossary-promotion) is the offer. A [Surface](#glossary-surface) is where the sale happens (register, ordering, join page, and so on). [Show vs available](#glossary-show-vs-available) is the difference between who can see an offer and who can use it.\n\nJoin-page claim is not an API call. Some offers must be claimed on the join page before they can be redeemed. Evaluate still returns those offers, marked unavailable with `CLAIM_REQUIRED`. Apply and complete reject with the same code. You can list **Claimed** customer groups and their members; you cannot add members or assign a claim through this API.\n\n**Collect Across Venues** (`venue_unlock`) is a passport offer: the same action at each selected site (or each selected child brand under Head office) unlocks a reward. On `lsk` / `lso` evaluate, incomplete passports return `venue_progress` and `non_redeemable_cause.code` `VENUE_UNLOCK_NOT_ELIGIBLE`. Browse and get with a child-brand `brand_id` include Head office Collect Across Venues that target that brand (evaluate-time resolve for this type only — not general Head office promotion sync). Apply and complete use the same promotion id. Stamp `brand_id` values on `venue_progress` match ids from `brand.organisation` on **[Get credential scope and brand identity](#operation/getConnectMe)** or **[Get brand bootstrap configuration](#operation/getBrandContext)** — see [Organisation (Head office group)](#glossary-organisation-head-office-group).\n\nTo list which promotions can show — for the brand catalog or for one customer — without an open order, use **Browse promotions**, **List promotions for a customer**, or **Search promotions**. Optional `integration_type` on those list calls is only a coarse filter when you do not yet know which app will evaluate the sale (`ordering`, `listing`, or `ecommerce`). It does not replace `surface`. Do not send `integration_type` on evaluate, apply, or complete. Those open-order calls always need `surface` (`lsk` or `lso`).\n\n### Redeeming on a POS or ordering app\n\nThe expected life cycle is: check what fits, take the money off, apply (lock) the reward, then record the redemption when the customer has paid. You take the money off in your POS. myne does not change the ticket.\n\nUse **one** way to record the redemption:\n\n- If paid sales from this POS already arrive in myne, mark the order (step 4). When that paid sale arrives, myne records the redemption. Do not send Complete promotions.\n- If paid sales from this POS do not arrive in myne, skip step 4. When the customer has paid, send **Complete promotions** (step 5).\n\nFollow these five steps in order.\n\n<div style=\"overflow-x:auto;margin:1em 0 1.25em\">\n<table role=\"presentation\" style=\"border-collapse:separate;border-spacing:0.5rem;width:100%;min-width:52rem;text-align:center;font-size:0.92em\">\n  <tr>\n    <td style=\"border:1px solid #c5c5c5;border-radius:6px;padding:0.65rem 0.5rem;background:#fafafa;vertical-align:top;width:18%\">\n      <strong>1. Check what fits</strong><br/>\n      <span style=\"color:#666\">Ask which rewards<br/>fit this open order</span>\n    </td>\n    <td style=\"vertical-align:middle;color:#888;font-size:1.25em;width:2%\">→</td>\n    <td style=\"border:1px solid #c5c5c5;border-radius:6px;padding:0.65rem 0.5rem;background:#fafafa;vertical-align:top;width:18%\">\n      <strong>2. Take the money off</strong><br/>\n      <span style=\"color:#666\">You change the ticket<br/>in your POS</span>\n    </td>\n    <td style=\"vertical-align:middle;color:#888;font-size:1.25em;width:2%\">→</td>\n    <td style=\"border:1px solid #c5c5c5;border-radius:6px;padding:0.65rem 0.5rem;background:#fafafa;vertical-align:top;width:18%\">\n      <strong>3. Apply (lock the reward)</strong><br/>\n      <span style=\"color:#666\">Send Apply a promotion<br/>for this open order</span>\n    </td>\n    <td style=\"vertical-align:middle;color:#888;font-size:1.25em;width:2%\">→</td>\n    <td style=\"border:1px solid #c5c5c5;border-radius:6px;padding:0.65rem 0.5rem;background:#fafafa;vertical-align:top;width:18%\">\n      <strong>4. Mark the order</strong><br/>\n      <span style=\"color:#666\">Only if paid sales<br/>already go to myne</span>\n    </td>\n    <td style=\"vertical-align:middle;color:#888;font-size:1.25em;width:2%\">→</td>\n    <td style=\"border:1px solid #c5c5c5;border-radius:6px;padding:0.65rem 0.5rem;background:#fafafa;vertical-align:top;width:18%\">\n      <strong>5. Record the redemption</strong><br/>\n      <span style=\"color:#666\">When paid: myne records it,<br/>or you send Complete</span>\n    </td>\n  </tr>\n</table>\n</div>\n\nBefore the customer pays, the sale is an open **order**. After the customer pays, myne can store a [Transaction](#glossary-transaction). Recording a promotion redemption does not create that purchase. The purchase is created when the POS or ordering system sends the sale.\n\n#### 1. Check what fits\n\nCall **Evaluate promotions** while the order is open. We need the [Customer](#glossary-customer) (or the id you already synced under your [External source](#glossary-external-source)), each line’s [product id on each line](#glossary-order-line-pos-id) for this [Surface](#glossary-surface), and where you are selling (`lsk` for register, `lso` for ordering).\n\nSend your [order id](#glossary-basket-id-and-myne-reference) as well, so later calls use the same order. myne sends that id back unchanged.\n\nIf the offer depends on venue, send a location id that matches how this place of sale names venues.\n\nWhat happens: myne returns which rewards fit this customer and these lines, and how much to take off. This does not lock a reward.\n\nFor [Buy X Get Y](#glossary-buy-x-get-y) offers, paid X units are reserved before Y is discounted — Buy 1 Get 1 Free needs two matching units on the order.\n\n#### 2. Take the money off in your POS\n\nYou need the discount amount and method from step 1. Apply that change on the ticket in your POS or ordering system. myne does not change the ticket for you.\n\n(Optional: expand the detail block below for how that looks on a register versus ordering.)\n\n#### 3. Apply a promotion (lock the reward)\n\nCall **Apply a promotion**. We need the same [order id](#glossary-basket-id-and-myne-reference) as step 1, the customer, the [Surface](#glossary-surface) (`lsk` or `lso`), and which [Promotion](#glossary-promotion) they are using (`promotion_id`).\n\nWhat happens: the promotion is applied (locked) on this open order. It is not redeemed yet. Points, cashback payment, and the redemption on the customer record wait until step 5. This does not take payment and it does not change the ticket.\n\n[Promotion stacking](#glossary-promotion-stacking): if the promotions allow it, apply more than one promotion on the same order. Use the same order id each time. One later complete call records every locked reward on that order.\n\n#### 4. Mark the order (only if paid sales already go to myne)\n\nSkip this step if you will send **Complete promotions** when the customer pays.\n\nIf this POS already sends paid sales into myne, mark the open order so myne can match the payment to the locked reward. Write the [basket_id and MYNE reference](#glossary-basket-id-and-myne-reference) onto the order: prefix MYNE, value = the order id from step 3. Do not write the promotion id.\n\nIf you skip this mark and you also skip Complete promotions, the customer can pay and myne will not record the redemption.\n\n#### 5. Record the redemption when the customer has paid\n\nTo move from locked to redeemed, use **one** of these paths:\n\n- **Paid sales already go to myne** — you marked the order in step 4. When that paid sale arrives, myne records the redemptions. You do not send Complete promotions.\n- **Paid sales do not go to myne** — when the customer has paid, call **Complete promotions**. We need the same order id, the customer, the same surface (`lsk` or `lso`), and the paid product lines (not the discount line). One call records every locked reward on that order. Discount amounts in the request are optional and are for reporting only. They do not cause the redemption. If you send the same complete again, myne records nothing extra. If the venue does not match the offer, myne can return success with nothing redeemed.\n\nRecording the redemption does not create the paid purchase in myne. That [Transaction](#glossary-transaction) is created when the POS or ordering system sends the sale.\n\n<details>\n<summary><strong>Optional detail — how to take money off in the POS</strong></summary>\n\n<p>After step 1, myne tells you how to take money off for that place of sale:</p>\n<ul><li><strong>Register</strong> — add a negative-priced promotion product for the discount amount</li><li><strong>Ordering, whole order</strong> — use the POS order-level discount</li><li><strong>Ordering, line items</strong> — use the POS line-level discount</li><li><strong>Other / not set</strong> — take the money off in your own system</li></ul>\n<p>The promotion id is not a POS product id. Use it only when you Apply a promotion (step 3).</p>\n</details>\n\n### Next\n\nTo list what can show without an open order, open **Browse promotions** or **List promotions for a customer** (optional `integration_type` — customer groups only, no app). For an open order, open **Evaluate promotions** (step 1), **Apply a promotion** (step 3), and **Complete promotions** (step 5, when you record the redemption yourself). Those three always need `surface` (`lsk` or `lso`). Do not send `integration_type` on them."
    },
    {
      "name": "Transactions",
      "description": "Paid purchase history and line-item detail for customers. An open sale is an **order**. After the customer pays, myne can store a transaction. Purchases are created when the POS or ordering system sends the sale. Recording a promotion redemption does not create a purchase."
    },
    {
      "name": "Activities",
      "description": "CRM activity feed and manual notes on contacts—distinct from Customer events writes."
    },
    {
      "name": "Tasks",
      "description": "Staff follow-up tasks: browse, get, and search by assignee, due date, status, business, or customer."
    },
    {
      "name": "Businesses",
      "description": "Browse and retrieve B2B business profiles and extended data."
    },
    {
      "name": "Sync",
      "description": "Batch upsert contacts, businesses, business–contact links, and extended metadata."
    },
    {
      "name": "CRM Embed",
      "description": "Mint short-lived tokens to load CRM embed widgets in your app."
    },
    {
      "name": "Webhooks",
      "description": "Subscribe HTTPS destinations to brand events. Signing secret is returned only on create and rotate. Verify `X-Myne-Signature` with HMAC-SHA256 over `{unix_ts}.{rawBody}` using `crypto.timingSafeEqual`. Use `X-Myne-Delivery-Id` for idempotency. Poll collection GETs with `updated_since` when you prefer pull."
    }
  ],
  "security": [
    {
      "basicAuth": []
    },
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "basicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "HTTP Basic or Bearer from API Credentials at https://app.myne.network/settings/integration/selected/brand-basic-auth (brand admins). Client ID is myne_api_…. Client Secret is shown once when you create or rotate. Basic and client_credentials tokens only reach the creating brand. Other brands Allow the Client ID at https://app.myne.network/oauth/authorize. Send Authorization on every resource request to the final connect.myne.network /v1 URL; do not rely on following redirects for authenticated calls."
      },
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Connect access token from POST /v1/token. typ=connect_access. One hour. brand_id in the token must match the path brand."
      }
    },
    "schemas": {
      "CustomerRow": {
        "type": "object",
        "description": "A customer row as returned by browse and single-customer endpoints.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Customer ID."
          },
          "external_data": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "Opaque integration-supplied attributes (e.g. POS fields)."
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "location_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Business/location the customer is anchored to, if any."
          },
          "brand_id": {
            "type": "integer"
          },
          "uuid": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "predicted_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "type": "string",
            "description": "Record class; always \"customer\" for known customers."
          },
          "transaction_count": {
            "type": "integer",
            "description": "Lifetime transaction count for the brand."
          },
          "total_spent": {
            "type": "number",
            "description": "Lifetime spend for the brand, in the brand's currency."
          },
          "tenure": {
            "type": "number",
            "description": "Days since the customer's first transaction."
          },
          "latest_transaction": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "ISO-8601 timestamp of the most recent transaction."
          },
          "last_seen": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO-8601 timestamp of last observed activity."
          },
          "frequency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "New",
              "Frequent",
              "Infrequent",
              null
            ],
            "description": "Brand frequency bucket derived from transaction history."
          },
          "engagement": {
            "type": [
              "string",
              "null"
            ],
            "description": "Engagement bucket for the current period, if any."
          },
          "connection_type": {
            "type": [
              "string",
              "null"
            ],
            "description": "How the customer is connected (e.g. connected, known, unknown/anonymous website)."
          }
        }
      },
      "Customer": {
        "type": "object",
        "description": "Partner-safe customer profile. Integration identities are listed in `extended[]` on GET, not as a single top-level source and external_id. Includes pass id, points, and cashback when those programmes are linked. Visit totals live on `stats`, not duplicated here.",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Customer ID."
          },
          "brand_id": {
            "type": "integer",
            "description": "Brand/tenant ID."
          },
          "location_id": {
            "type": "integer",
            "description": "Business location the customer is anchored to."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "accepts_marketing": {
            "type": "boolean",
            "description": "Whether the customer has opted into marketing."
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "ISO-8601 timestamp when the customer was soft-archived, if any."
          },
          "frequency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "New",
              "Frequent",
              "Infrequent",
              null
            ],
            "description": "Brand frequency bucket."
          },
          "engagement": {
            "type": [
              "string",
              "null"
            ],
            "description": "Engagement bucket for the current period, if any."
          },
          "connection_type": {
            "type": "string",
            "enum": [
              "connected",
              "known",
              "unknown"
            ],
            "description": "Loyalty join status for this brand. `connected` when the customer has joined loyalty, `known` when myne has the customer but they have not joined, `unknown` for an anonymous website visitor. Matches the `connection_type` on the customer list."
          },
          "pass_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Loyalty pass identifier (gift card / wallet pass code), if linked."
          },
          "points": {
            "type": "number",
            "description": "Brand Points balance. Zero when Brand Points is not enabled."
          },
          "cashback": {
            "type": "number",
            "description": "Internal myne cashback balance in the brand's currency. Zero when cashback is not enabled. Not a live Wrapped or Giftit credit GET — use the credit endpoints for that."
          }
        }
      },
      "IntegrationExtendedRow": {
        "type": "object",
        "required": [
          "source",
          "external_id",
          "json_data"
        ],
        "description": "One connected-system identity for a customer, product, or business.",
        "properties": {
          "source": {
            "type": "string",
            "description": "Integration key (for example lightspeed, monday-crm)."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "That system's identifier for the record."
          },
          "json_data": {
            "type": "object",
            "additionalProperties": true,
            "description": "Opaque payload stored for that source."
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ConnectBrand": {
        "type": "object",
        "description": "Partner-safe brand display and configuration fields. Secrets and internal staff fields are omitted. Paths stay scoped to this brand’s `id`. When the brand is in a Head office group, `organisation` names the parent Head office brand and peer venue brands (useful for Collect Across Venues stamp labels).",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "industry": {
            "type": [
              "string",
              "null"
            ]
          },
          "background_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "light_logo_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "dark_logo_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "square_logo_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "primary_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "contrast_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "text_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "button_color": {
            "type": [
              "string",
              "null"
            ]
          },
          "primary_heading": {
            "type": [
              "string",
              "null"
            ]
          },
          "subheading": {
            "type": [
              "string",
              "null"
            ]
          },
          "call_to_action": {
            "type": [
              "string",
              "null"
            ]
          },
          "redirect_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "legal_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "business_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "governing_state": {
            "type": [
              "string",
              "null"
            ]
          },
          "organisation": {
            "type": [
              "object",
              "null"
            ],
            "description": "Null when this brand is not in an organisation. Otherwise describes Head office vs venue role and peer brands. Does not change path scoping — customers, promotions, and redeem calls still use the authenticated brand.",
            "required": [
              "id",
              "role",
              "sibling_brands"
            ],
            "properties": {
              "id": {
                "type": "integer",
                "description": "Organisation id."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "role": {
                "type": "string",
                "enum": [
                  "head_office",
                  "venue"
                ],
                "description": "`head_office` when this brand is the organisation Head office; otherwise `venue`."
              },
              "head_office_brand": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Parent Head office brand. Same as this brand when `role` is `head_office`.",
                "properties": {
                  "id": {
                    "type": "integer"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "sibling_brands": {
                "type": "array",
                "description": "Other venue brands in the organisation (excludes this brand and the Head office). When this brand is Head office, the venue brands in the group.",
                "items": {
                  "type": "object",
                  "required": [
                    "id"
                  ],
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      },
      "EvaluateReward": {
        "type": "object",
        "additionalProperties": true,
        "description": "Order-aware reward from evaluate. Field presence depends on surface (for example visibility_state / application_scope on lsk and lso; discount_amount_in_cents on AVAILABLE for lsk/lso, and only on SELECTED for some ordering surfaces).",
        "properties": {
          "id": {
            "type": "string",
            "description": "Promotion / reward id as a string."
          },
          "type": {
            "type": "string",
            "enum": [
              "Offer",
              "Deal",
              "PointShopOffer",
              "PromoCode"
            ],
            "description": "Engine reward type."
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Public URL of the promotion image. Null when the promotion has no image. Also present on the nested `promotion`."
          },
          "status": {
            "type": "string",
            "enum": [
              "AVAILABLE_TO_REDEEM",
              "UNAVAILABLE_TO_REDEEM",
              "SELECTED_TO_REDEEM"
            ]
          },
          "visibility_state": {
            "type": "string",
            "enum": [
              "locked",
              "unlocked"
            ],
            "description": "Present for lsk/lso."
          },
          "application_scope": {
            "type": "string",
            "enum": [
              "order",
              "line"
            ],
            "description": "Present for lsk/lso. Guides line vs order price variation on ordering."
          },
          "cart_application_method": {
            "type": "string",
            "enum": [
              "MANUAL_APPLY",
              "AUTO_APPLY"
            ]
          },
          "discount_amount_in_cents": {
            "type": "integer",
            "description": "Monetary discount to put on the sale when present. lsk/lso: AVAILABLE or SELECTED; some ordering surfaces: SELECTED only. For Buy X Get Y, this is only the Y side after paid X units are reserved (same-pool Buy 1 Get 1 Free needs two matching units)."
          },
          "order_discount": {
            "type": "object",
            "description": "Rolled-up order-level discount for this reward (Connect evaluate). Same total as discount_amount_in_cents; prefer this for order-level apply when application_scope is order.",
            "properties": {
              "amount_in_cents": {
                "type": "integer",
                "description": "Total discount in cents for the open order."
              },
              "order_price_variation": {
                "type": "number",
                "description": "Order-level price multiplier for percent order promotions (e.g. 0.85 for 15% off). Present when applicable."
              }
            }
          },
          "line_discounts": {
            "type": "array",
            "description": "Per-line discount breakdown for line-scoped promotions on Connect evaluate. Use with discount_application.method line_price_variation. When quantity is set, discount only that many units on the matching pos_id; omit quantity to discount every unit on the line.",
            "items": {
              "type": "object",
              "required": [
                "pos_id",
                "amount_in_cents"
              ],
              "properties": {
                "pos_id": {
                  "type": "string",
                  "description": "Product id (same as cart.items metadata.pos_id)."
                },
                "amount_in_cents": {
                  "type": "integer",
                  "description": "Total discount in cents for this line entry."
                },
                "quantity": {
                  "type": "number",
                  "exclusiveMinimum": 0,
                  "description": "Units to discount on this product line."
                },
                "discount_in_percent": {
                  "type": "number",
                  "description": "Percentage off (e.g. 25 for 25% off). Use for whole-line percentage when quantity covers the full line."
                }
              }
            }
          },
          "cashback_balance_in_cents": {
            "type": "integer",
            "description": "Current cashback wallet balance in cents for pay-with-cashback rewards. Distinct from discount_amount_in_cents, which is the amount redeemable on this cart."
          },
          "points_price": {
            "type": "integer"
          },
          "rolling_progress": {
            "type": "object",
            "additionalProperties": true,
            "description": "Present for Buy X Get Y rolling promotions on lsk/lso (period progress vs cart). Period history can bank the buy (X) side; the current order still needs matching Y. Same-cart Buy X Get Y reserves paid X before discounting Y.",
            "properties": {
              "total_x": {
                "type": "number"
              },
              "history_x": {
                "type": "number"
              },
              "cart_x": {
                "type": "number"
              },
              "x_quantity": {
                "type": "number"
              },
              "earned_redemptions": {
                "type": "number"
              },
              "redemption_count": {
                "type": "number"
              },
              "rolling_window_eligible": {
                "type": "boolean"
              },
              "summary": {
                "type": "string"
              }
            }
          },
          "venue_progress": {
            "type": "object",
            "additionalProperties": true,
            "description": "Present for Collect Across Venues (venue_unlock) on lsk/lso — passport stamp progress vs required venues (sites on multi-location brands, or child brands under Head office).",
            "properties": {
              "eligible": {
                "type": "boolean"
              },
              "unlocked": {
                "type": "boolean"
              },
              "collected_location_ids": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "collected_brand_ids": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "required_count": {
                "type": "number"
              },
              "stamp_set_size": {
                "type": "number"
              },
              "summary": {
                "type": "string"
              },
              "stamps": {
                "type": "array",
                "description": "Ordered passport stamps. `kind` is `location` (multi-location) or `brand` (Head office child brands).",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "number"
                    },
                    "label": {
                      "type": "string"
                    },
                    "earned": {
                      "type": "boolean"
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "location",
                        "brand"
                      ]
                    }
                  }
                }
              }
            }
          },
          "unlock_display_text": {
            "type": "string",
            "description": "Present for locked rewards on lsk/lso when configured."
          },
          "valid_from": {
            "type": "string",
            "format": "date-time"
          },
          "valid_until": {
            "type": "string",
            "format": "date-time"
          },
          "discount_application": {
            "type": "object",
            "required": [
              "method",
              "instructions"
            ],
            "description": "How to put money on the open order for this surface. Follow Promotions sequence step 2; method values are listed in the Promotions reference expand.",
            "properties": {
              "method": {
                "type": "string",
                "enum": [
                  "negative_product_line",
                  "line_price_variation",
                  "order_price_variation",
                  "ordering_platform",
                  "partner_defined"
                ]
              },
              "instructions": {
                "type": "string"
              }
            }
          },
          "non_redeemable_cause": {
            "type": "object",
            "description": "Present when status is UNAVAILABLE_TO_REDEEM. `CLAIM_REQUIRED` means the offer must be claimed on the join page (not an API call). `TOTAL_REDEMPTION_LIMIT_REACHED` means the first-X redemption cap is exhausted (POS/LSK keep `MAXIMUM_USES_REACHED` for that limit). `VENUE_UNLOCK_NOT_ELIGIBLE` means Collect Across Venues stamps are incomplete (see `venue_progress`). Rolling buy-X-get-Y and streak unlock use their own progress cause codes when not yet earned.",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          },
          "promotion": {
            "type": "object",
            "additionalProperties": true,
            "description": "Hydrated promotion with availability and catalog enrichment."
          }
        }
      }
    }
  },
  "paths": {
    "/v1/me": {
      "get": {
        "operationId": "getConnectMe",
        "summary": "Get credential scope and brand identity",
        "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.",
        "tags": [
          "Authorisation"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "brand_id",
                        "client_id",
                        "credential_kind",
                        "source",
                        "brand"
                      ],
                      "properties": {
                        "brand_id": {
                          "type": "integer",
                          "description": "Tenant brand id for this credential. Use it as `{brand_id}` on every other Connect route."
                        },
                        "client_id": {
                          "type": "string",
                          "description": "API Credentials Client ID (myne_api_…)."
                        },
                        "credential_kind": {
                          "type": "string",
                          "enum": [
                            "brand_basic_auth",
                            "lsk_embed"
                          ],
                          "description": "Credential type. `brand_basic_auth` is an API Credentials client; the POS web extension credential uses the second enum value."
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Staff-configured app name from Integrations (API Credentials). Used to derive source when present."
                        },
                        "source": {
                          "type": "string",
                          "description": "Your External source key. myne stamps this on every write and uses it as the default for external-id lookups. Derived from label (slugified), or from client_id when label is unset."
                        },
                        "brand": {
                          "oneOf": [
                            {
                              "$ref": "#/components/schemas/ConnectBrand"
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Partner-safe brand display and config fields (same shape as GET …/brand). Null only if brand config is missing."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Authenticated successfully",
                  "response": {
                    "brand_id": 42,
                    "client_id": "myne_api_7f3a2b1c",
                    "credential_kind": "brand_basic_auth",
                    "label": "CRM sync",
                    "source": "crm-sync",
                    "brand": {
                      "id": 42,
                      "name": "Harbour Cafe Co",
                      "description": "Neighbourhood cafe group",
                      "website": "https://harbour.example.com",
                      "industry": "hospitality",
                      "background_url": null,
                      "light_logo_url": "https://assets.dev.myne.network/demo/light-logo.png",
                      "dark_logo_url": "https://assets.dev.myne.network/demo/dark-logo.png",
                      "square_logo_url": "https://assets.dev.myne.network/demo/square-logo.png",
                      "primary_color": "#1A1A1A",
                      "contrast_color": "#FFFFFF",
                      "text_color": "#1A1A1A",
                      "button_color": "#C45C26",
                      "primary_heading": "Welcome back",
                      "subheading": "Earn rewards every visit",
                      "call_to_action": "Join now",
                      "redirect_url": "https://harbour.example.com/account",
                      "legal_name": "Harbour Cafe Co Pty Ltd",
                      "contact_email": "hello@harbour.example.com",
                      "business_address": "1 Harbour St, Sydney NSW 2000",
                      "governing_state": "NSW",
                      "organisation": {
                        "id": 42,
                        "name": "Harbour Group",
                        "role": "venue",
                        "head_office_brand": {
                          "id": 100,
                          "name": "Harbour Head Office"
                        },
                        "sibling_brands": [
                          {
                            "id": 101,
                            "name": "Harbour Bondi"
                          },
                          {
                            "id": 102,
                            "name": "Harbour Newtown"
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          }
        }
      }
    },
    "/v1/brands/{brand_id}/brand": {
      "get": {
        "operationId": "getBrandContext",
        "summary": "Get brand bootstrap configuration",
        "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).",
        "tags": [
          "Brand"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Brand bootstrap configuration. response contains exactly one ConnectBrand object.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "array",
                      "minItems": 1,
                      "maxItems": 1,
                      "items": {
                        "$ref": "#/components/schemas/ConnectBrand"
                      }
                    }
                  }
                },
                "example": {
                  "message": "Brands fetched successfully",
                  "response": [
                    {
                      "id": 42,
                      "name": "Harbour Cafe Co",
                      "description": "Neighbourhood cafe group",
                      "website": "https://harbour.example.com",
                      "industry": "hospitality",
                      "background_url": null,
                      "light_logo_url": "https://assets.dev.myne.network/demo/light-logo.png",
                      "dark_logo_url": "https://assets.dev.myne.network/demo/dark-logo.png",
                      "square_logo_url": "https://assets.dev.myne.network/demo/square-logo.png",
                      "primary_color": "#1A1A1A",
                      "contrast_color": "#FFFFFF",
                      "text_color": "#1A1A1A",
                      "button_color": "#C45C26",
                      "primary_heading": "Welcome back",
                      "subheading": "Earn rewards every visit",
                      "call_to_action": "Join now",
                      "redirect_url": "https://harbour.example.com/account",
                      "legal_name": "Harbour Cafe Co Pty Ltd",
                      "contact_email": "hello@harbour.example.com",
                      "business_address": "1 Harbour St, Sydney NSW 2000",
                      "governing_state": "NSW",
                      "organisation": {
                        "id": 42,
                        "name": "Harbour Group",
                        "role": "venue",
                        "head_office_brand": {
                          "id": 100,
                          "name": "Harbour Head Office"
                        },
                        "sibling_brands": [
                          {
                            "id": 101,
                            "name": "Harbour Bondi"
                          },
                          {
                            "id": 102,
                            "name": "Harbour Newtown"
                          }
                        ]
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Brand not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Brand not found"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/integrations": {
      "get": {
        "operationId": "listIntegrations",
        "summary": "List connected integrations for a brand",
        "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.",
        "tags": [
          "Integrations"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Connected integrations for the brand. Secrets, OAuth tokens, and raw integration payloads are never included.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "access_name",
                          "app_name",
                          "label",
                          "source",
                          "state",
                          "category",
                          "is_self"
                        ],
                        "properties": {
                          "id": {
                            "type": "integer",
                            "description": "Connected integration row id."
                          },
                          "access_name": {
                            "type": "string",
                            "description": "Internal access key for the integration."
                          },
                          "app_name": {
                            "type": "string",
                            "description": "Catalog app name when linked from integration_apps."
                          },
                          "label": {
                            "type": "string",
                            "description": "Staff-facing display name in myne Integrations."
                          },
                          "source": {
                            "type": "string",
                            "description": "Slugified integration source key derived from label or access_name."
                          },
                          "state": {
                            "type": "string",
                            "enum": [
                              "connected"
                            ],
                            "description": "Connection state for this row."
                          },
                          "category": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Integration category from the catalog when available."
                          },
                          "is_self": {
                            "type": "boolean",
                            "description": "True when this row matches the source derived from your API credentials."
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Integrations fetched successfully",
                  "response": [
                    {
                      "id": 42,
                      "access_name": "monday",
                      "app_name": "monday",
                      "label": "Monday CRM",
                      "source": "monday-crm",
                      "state": "connected",
                      "category": "crm",
                      "is_self": false
                    },
                    {
                      "id": 99,
                      "access_name": "myne_api_abc123",
                      "app_name": "myne_api_abc123",
                      "label": "Partner sync",
                      "source": "partner-sync",
                      "state": "connected",
                      "category": "custom",
                      "is_self": true
                    }
                  ]
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/locations": {
      "get": {
        "operationId": "listLocations",
        "summary": "List locations for a brand",
        "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.",
        "tags": [
          "Location"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "All locations for the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "brand_id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "phone": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "address": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "uuid": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "valid": {
                            "type": "boolean"
                          },
                          "new_check_in_enabled": {
                            "type": "boolean"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Locations fetched successfully",
                  "response": [
                    {
                      "id": 3,
                      "brand_id": 42,
                      "name": "Harbour Cafe — Circular Quay",
                      "phone": "+61291234567",
                      "address": "1 Alfred St, Sydney NSW 2000",
                      "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                      "valid": true,
                      "new_check_in_enabled": true
                    }
                  ]
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/locations/{location_id}": {
      "get": {
        "operationId": "getLocation",
        "summary": "Get a location by 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.",
        "tags": [
          "Location"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Single venue for the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Location fetched successfully",
                  "response": {
                    "id": 3,
                    "brand_id": 42,
                    "name": "Harbour Cafe — Circular Quay",
                    "phone": "+61291234567",
                    "address": "1 Alfred St, Sydney NSW 2000",
                    "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                    "valid": true,
                    "new_check_in_enabled": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id or location_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or location_id"
                }
              }
            }
          },
          "404": {
            "description": "Location not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Location not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "location_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/by-external-id": {
      "get": {
        "operationId": "getCustomerByExternalId",
        "summary": "Get a customer by external id",
        "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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Customer, connected-system identities, loyalty balances, and lifetime stats.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "customer",
                        "stats",
                        "extended"
                      ],
                      "properties": {
                        "customer": {
                          "$ref": "#/components/schemas/Customer"
                        },
                        "stats": {
                          "type": "object",
                          "description": "Lifetime visit count, spend, and last transaction for this brand.",
                          "required": [
                            "transaction_count",
                            "total_spent",
                            "last_transaction_date"
                          ],
                          "properties": {
                            "transaction_count": {
                              "type": "integer"
                            },
                            "total_spent": {
                              "type": "number"
                            },
                            "last_transaction_date": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date-time"
                            }
                          }
                        },
                        "extended": {
                          "type": "array",
                          "description": "Every known source and external_id for this customer, including the originating POS or CRM row.",
                          "items": {
                            "$ref": "#/components/schemas/IntegrationExtendedRow"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer fetched successfully",
                  "response": {
                    "customer": {
                      "id": 104823,
                      "brand_id": 42,
                      "location_id": 3,
                      "phone": "+61412345678",
                      "email": "jordan.lee@example.com",
                      "created_at": "2025-02-15T01:23:45.000Z",
                      "updated_at": "2026-06-21T03:15:00.000Z",
                      "accepts_marketing": true,
                      "first_name": "Jordan",
                      "last_name": "Lee",
                      "archived_at": null,
                      "frequency": "Frequent",
                      "engagement": "engaged",
                      "connection_type": "connected",
                      "pass_id": "ABC123XYZ",
                      "points": 120,
                      "cashback": 18.5
                    },
                    "stats": {
                      "transaction_count": 17,
                      "total_spent": 542.5,
                      "last_transaction_date": "2026-06-21T03:15:00.000Z"
                    },
                    "extended": [
                      {
                        "source": "lightspeed",
                        "external_id": "ext-abc123",
                        "json_data": {
                          "pos_id": "LS-98765"
                        },
                        "updated_at": "2026-06-21T03:15:00.000Z"
                      },
                      {
                        "source": "monday-crm",
                        "external_id": "crm-001",
                        "json_data": {},
                        "updated_at": "2026-05-01T00:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "external_id query parameter is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "external_id query parameter is required"
                }
              }
            }
          },
          "404": {
            "description": "No customer matches the external id for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your integration's identifier for the customer."
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/by-external-id/extended": {
      "post": {
        "operationId": "postCustomerExtendedByExternalId",
        "summary": "Upsert extended customer data by external id",
        "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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Extended data merged for the customer under your API app source. Creates the customer when missing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "customer_id",
                        "external_id",
                        "source"
                      ],
                      "properties": {
                        "customer_id": {
                          "type": "integer"
                        },
                        "external_id": {
                          "type": "string"
                        },
                        "source": {
                          "type": "string",
                          "description": "Integration source key (your Custom API app name)."
                        },
                        "created_customer": {
                          "type": "boolean",
                          "description": "True when a new customer was created for this request."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer extended data upserted successfully",
                  "response": {
                    "customer_id": 104823,
                    "external_id": "crm-001",
                    "source": "monday-crm",
                    "created_customer": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body or missing external_id/data.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "data must be a JSON object"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "403": {
            "description": "Request included source (or contact_source / business_source)—these are set from your API credentials.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "source is managed by your API credentials and cannot be set on write requests"
                }
              }
            }
          },
          "404": {
            "description": "No customer matches the external id for this brand and source.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "external_id",
                  "data"
                ],
                "properties": {
                  "external_id": {
                    "type": "string",
                    "description": "Your integration's identifier for the customer."
                  },
                  "data": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "JSON fields to merge into customer_extended for your API app source."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Optional. Used when creating the customer if they do not exist yet."
                  },
                  "phone": {
                    "type": "string"
                  },
                  "first_name": {
                    "type": "string"
                  },
                  "last_name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/extended/properties": {
      "get": {
        "operationId": "getCustomerExtendedProperties",
        "summary": "List declared contact-extended properties for your source",
        "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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Partner-declared contact-extended property catalog for your External source.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Human-readable success summary."
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "source",
                        "properties"
                      ],
                      "description": "Catalog payload for the authenticated integration source.",
                      "properties": {
                        "source": {
                          "type": "string",
                          "description": "Integration source key (your Custom API app name)."
                        },
                        "properties": {
                          "type": "array",
                          "description": "Partner-origin declared fields for this brand and source.",
                          "items": {
                            "type": "object",
                            "required": [
                              "key",
                              "type",
                              "label"
                            ],
                            "properties": {
                              "key": {
                                "type": "string",
                                "pattern": "^[a-z][a-z0-9_]*$",
                                "maxLength": 64,
                                "description": "Top-level JSON key written on extended upsert."
                              },
                              "type": {
                                "type": "string",
                                "enum": [
                                  "string",
                                  "number",
                                  "boolean",
                                  "link",
                                  "date"
                                ],
                                "description": "Scalar type used by staff pickers and merge-tag examples."
                              },
                              "label": {
                                "type": "string",
                                "description": "Display label shown in staff UIs."
                              },
                              "description": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "maxLength": 500,
                                "description": "Optional helper text for staff merge-tag pickers."
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer extended properties fetched successfully",
                  "response": {
                    "source": "pocketpass",
                    "properties": [
                      {
                        "key": "pocketpass_link",
                        "type": "link",
                        "label": "Pocketpass link"
                      },
                      {
                        "key": "pocketpass_installed",
                        "type": "boolean",
                        "label": "Pocketpass installed"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "403": {
            "description": "Path brand_id does not match the credential brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "brand_id does not match authenticated brand"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Brand id from your credential scope."
          }
        ]
      },
      "post": {
        "operationId": "postCustomerExtendedProperties",
        "summary": "Replace declared contact-extended properties for your source",
        "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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Partner-declared catalog replaced for your External source.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string",
                      "description": "Human-readable success summary."
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "source",
                        "properties"
                      ],
                      "description": "Replaced catalog for the authenticated integration source.",
                      "properties": {
                        "source": {
                          "type": "string",
                          "description": "Integration source key (your Custom API app name)."
                        },
                        "properties": {
                          "type": "array",
                          "description": "Normalized partner-declared fields after the replace.",
                          "items": {
                            "type": "object",
                            "required": [
                              "key",
                              "type",
                              "label"
                            ],
                            "properties": {
                              "key": {
                                "type": "string",
                                "description": "Top-level JSON key written on extended upsert."
                              },
                              "type": {
                                "type": "string",
                                "enum": [
                                  "string",
                                  "number",
                                  "boolean",
                                  "link",
                                  "date"
                                ],
                                "description": "Scalar type used by staff pickers and merge-tag examples."
                              },
                              "label": {
                                "type": "string",
                                "description": "Display label shown in staff UIs."
                              },
                              "description": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "maxLength": 500,
                                "description": "Optional helper text for staff merge-tag pickers."
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer extended properties replaced successfully",
                  "response": {
                    "source": "pocketpass",
                    "properties": [
                      {
                        "key": "pocketpass_link",
                        "type": "link",
                        "label": "Pocketpass link"
                      },
                      {
                        "key": "pocketpass_installed",
                        "type": "boolean",
                        "label": "Pocketpass installed"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id, invalid JSON body, unknown top-level fields, missing properties, or invalid key/type/count.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Request body may only include properties"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "403": {
            "description": "Path brand_id mismatch, or request included source (managed by credentials).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "source is managed by your API credentials and cannot be set on write requests"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "Brand id from your credential scope."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "properties"
                ],
                "additionalProperties": false,
                "properties": {
                  "properties": {
                    "type": "array",
                    "maxItems": 50,
                    "description": "Full replace list for this brand + your External source. Empty array clears your partner-declared catalog.",
                    "items": {
                      "type": "object",
                      "required": [
                        "key",
                        "type"
                      ],
                      "additionalProperties": false,
                      "properties": {
                        "key": {
                          "type": "string",
                          "pattern": "^[a-z][a-z0-9_]*$",
                          "maxLength": 64,
                          "description": "Top-level JSON key written on extended upsert."
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "string",
                            "number",
                            "boolean",
                            "link",
                            "date"
                          ],
                          "description": "Scalar type used by staff pickers and merge-tag examples."
                        },
                        "label": {
                          "type": "string",
                          "description": "Optional display label; defaults to a title-cased key."
                        },
                        "description": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 500,
                          "description": "Optional helper text for staff merge-tag pickers."
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "properties": [
                  {
                    "key": "pocketpass_link",
                    "type": "link",
                    "label": "Pocketpass link",
                    "description": "URL to the member portal"
                  },
                  {
                    "key": "pocketpass_installed",
                    "type": "boolean",
                    "label": "Pocketpass installed"
                  }
                ]
              }
            }
          }
        }
      }
    },
    "/v1/brands/{brand_id}/customers": {
      "post": {
        "operationId": "postCustomerUpsert",
        "summary": "Upsert a customer by external id",
        "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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Customer created or updated under your External source.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "customer_id",
                        "external_id",
                        "source",
                        "created"
                      ],
                      "properties": {
                        "customer_id": {
                          "type": "integer"
                        },
                        "external_id": {
                          "type": "string"
                        },
                        "source": {
                          "type": "string"
                        },
                        "created": {
                          "type": "boolean"
                        },
                        "skipped": {
                          "type": "boolean"
                        },
                        "reason": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer upserted successfully",
                  "response": {
                    "customer_id": 104823,
                    "external_id": "web-user-001",
                    "source": "monday-crm",
                    "created": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, missing external_id, or connection_type=connected with no resolvable email or phone.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "external_id (or user_id) is required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "403": {
            "description": "Request included a source field (not allowed on writes).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "source is managed by your API credentials and cannot be set on write requests"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "external_id"
                ],
                "properties": {
                  "external_id": {
                    "type": "string",
                    "description": "Your website or CRM identifier for the customer."
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "phone": {
                    "type": "string"
                  },
                  "first_name": {
                    "type": "string"
                  },
                  "last_name": {
                    "type": "string"
                  },
                  "marketing_opt_in_email": {
                    "type": "boolean",
                    "description": "Marketing email opt-in stored on the customer profile."
                  },
                  "connection_type": {
                    "type": "string",
                    "enum": [
                      "connected",
                      "known",
                      "unknown"
                    ],
                    "description": "Optional loyalty connection status. `connected` marks the customer as joined to loyalty (requires an email or phone — sent on this request or already stored for the customer). `known` and `unknown` never change connection status, and unrecognised values are ignored."
                  },
                  "traits": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Optional opaque attributes stored on the customer."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      },
      "get": {
        "operationId": "listCustomers",
        "summary": "Browse customers for a brand",
        "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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "A page of customers. `response.next_cursor` is null when there are no more pages.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "data",
                        "has_more",
                        "next_cursor",
                        "total_count"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CustomerRow"
                          }
                        },
                        "has_more": {
                          "type": "boolean",
                          "description": "Whether another page of results is available."
                        },
                        "next_cursor": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Opaque cursor for the next page. Pass unchanged as the `cursor` query parameter; null when `has_more` is false."
                        },
                        "total_count": {
                          "type": "integer",
                          "description": "Total number of customers matching the filters (independent of paging)."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer records fetched successfully (known only)",
                  "response": {
                    "data": [
                      {
                        "id": 104823,
                        "external_data": null,
                        "first_name": "Jordan",
                        "last_name": "Lee",
                        "email": "jordan.lee@example.com",
                        "phone": "+61412345678",
                        "location_id": null,
                        "brand_id": 42,
                        "uuid": "c0b1f7a4-2e3d-4f5a-9b6c-7d8e9f0a1b2c",
                        "predicted_name": null,
                        "type": "customer",
                        "transaction_count": 17,
                        "total_spent": 542.5,
                        "tenure": 412,
                        "latest_transaction": "2026-06-21T03:15:00.000Z",
                        "last_seen": "2026-06-21T03:15:00.000Z",
                        "frequency": "Frequent",
                        "engagement": "engaged",
                        "connection_type": "connected"
                      }
                    ],
                    "has_more": true,
                    "next_cursor": "1|0|2026-06-21T03:15:00.000Z|104823",
                    "total_count": 1287
                  }
                }
              }
            }
          },
          "400": {
            "description": "brand_id is missing or not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error. Details are logged server-side; the body is a generic message.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          },
          "503": {
            "description": "The underlying browse query timed out. Narrow the search (e.g. add `query`, `locations`, or reduce `limit`) and retry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Service temporarily unavailable. Please narrow your search or try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "description": "ID of the brand (tenant) to browse customers for."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 15
            },
            "description": "Page size. Clamped to 1–100."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque pagination cursor returned as `response.next_cursor` from the previous page. Omit for the first page."
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text search across name, email, phone, and wallet/credit identifiers."
          },
          {
            "name": "include_visitors",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Accepts `true`, `1`, or `yes`. When truthy, include unidentified visitor records; otherwise only known customers are returned (default)."
          },
          {
            "name": "sort_by",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "last_seen",
                "transaction_count",
                "total_spent",
                "tenure",
                "frequency",
                "engagement"
              ],
              "default": "total_spent"
            },
            "description": "Sort key for ranking customers."
          },
          {
            "name": "sort_direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "description": "Sort direction."
          },
          {
            "name": "locations",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated business/location IDs to restrict the browse to (e.g. `12,34`)."
          },
          {
            "name": "connection",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "connected",
                "known",
                "unknown"
              ]
            },
            "description": "Filter by connection state."
          },
          {
            "name": "frequency",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "frequent",
                "infrequent",
                "new"
              ]
            },
            "description": "Filter by brand frequency bucket."
          },
          {
            "name": "engagement",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter by engagement classification when available."
          },
          {
            "name": "customer_group_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Restrict browse to members of one customer group."
          },
          {
            "name": "search_fields",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated search field keys to limit free-text `query` matching."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO-8601 timestamp. When set, only customers with updated_at on or after this instant are returned (compared in UTC)."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/events": {
      "post": {
        "operationId": "postCustomerEvent",
        "summary": "Record a customer event",
        "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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Event recorded on the customer timeline.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "id",
                        "event_name",
                        "event_time",
                        "source"
                      ],
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "event_name": {
                          "type": "string"
                        },
                        "event_time": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "source": {
                          "type": "string"
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer event recorded successfully",
                  "response": {
                    "id": 9001,
                    "event_name": "form_submitted",
                    "event_time": "2026-07-10T08:00:00.000Z",
                    "source": "monday-crm",
                    "external_id": "evt-abc"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or missing event_name.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "event_name is required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "403": {
            "description": "Request included a source field (not allowed on writes).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "source is managed by your API credentials and cannot be set on write requests"
                }
              }
            }
          },
          "404": {
            "description": "Customer not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event_name"
                ],
                "properties": {
                  "event_name": {
                    "type": "string",
                    "description": "Event label on the customer timeline (e.g. form_submitted)."
                  },
                  "event_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "When the event occurred (defaults to now)."
                  },
                  "event_data": {
                    "type": "object",
                    "additionalProperties": true,
                    "description": "Arbitrary JSON payload for the event."
                  },
                  "event_value": {
                    "type": "number"
                  },
                  "location_id": {
                    "type": "integer"
                  },
                  "external_id": {
                    "type": "string",
                    "description": "Optional partner event id for correlation."
                  },
                  "is_visible_to_customer": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/by-external-id/events": {
      "post": {
        "operationId": "postCustomerEventByExternalId",
        "summary": "Record a customer event by external id",
        "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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Event recorded; response includes resolved customer_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "id",
                        "customer_id",
                        "event_name",
                        "event_time",
                        "source"
                      ],
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "customer_id": {
                          "type": "integer"
                        },
                        "event_name": {
                          "type": "string"
                        },
                        "event_time": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "source": {
                          "type": "string"
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer event recorded successfully",
                  "response": {
                    "id": 9001,
                    "customer_id": 104823,
                    "event_name": "form_submitted",
                    "event_time": "2026-07-10T08:00:00.000Z",
                    "source": "monday-crm",
                    "external_id": "evt-abc"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or missing external_id / event_name.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "event_name is required"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "403": {
            "description": "Request included a source field (not allowed on writes).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "source is managed by your API credentials and cannot be set on write requests"
                }
              }
            }
          },
          "404": {
            "description": "No customer for this external_id under your External source.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "external_id",
                  "event_name"
                ],
                "properties": {
                  "external_id": {
                    "type": "string",
                    "description": "Your website or CRM identifier for the customer."
                  },
                  "event_name": {
                    "type": "string",
                    "description": "Event label on the customer timeline (e.g. form_submitted)."
                  },
                  "event_time": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "event_data": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "event_value": {
                    "type": "number"
                  },
                  "location_id": {
                    "type": "integer"
                  },
                  "event_external_id": {
                    "type": "string",
                    "description": "Optional partner id for this event (not the contact external_id)."
                  },
                  "is_visible_to_customer": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "external_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Contact external_id. Prefer the body field; query is accepted for convenience."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/search": {
      "post": {
        "operationId": "searchCustomers",
        "summary": "Search customers with advanced filters",
        "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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "A page of customers. response.next_cursor is null when there are no more pages.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Customer records fetched successfully (known only)",
                  "response": {
                    "data": [
                      {
                        "id": 104823,
                        "external_data": null,
                        "first_name": "Jordan",
                        "last_name": "Lee",
                        "email": "jordan.lee@example.com",
                        "phone": "+61412345678",
                        "location_id": 3,
                        "brand_id": 42,
                        "uuid": "c0b1f7a4-2e3d-4f5a-9b6c-7d8e9f0a1b2c",
                        "predicted_name": null,
                        "type": "customer",
                        "transaction_count": 17,
                        "total_spent": 542.5,
                        "tenure": 412,
                        "latest_transaction": "2026-06-21T03:15:00.000Z",
                        "last_seen": "2026-06-21T03:15:00.000Z",
                        "frequency": "Frequent",
                        "engagement": "engaged",
                        "connection_type": "connected"
                      }
                    ],
                    "has_more": true,
                    "next_cursor": "1|0|2026-06-21T03:15:00.000Z|104823",
                    "total_count": 1287
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body or updated_since.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "updated_since must be a valid ISO-8601 timestamp"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cursor": {
                    "type": "string"
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 15
                  },
                  "query": {
                    "type": "string"
                  },
                  "include_visitors": {
                    "type": "boolean",
                    "default": false
                  },
                  "sort_by": {
                    "type": "string",
                    "enum": [
                      "last_seen",
                      "transaction_count",
                      "total_spent",
                      "tenure",
                      "frequency",
                      "engagement"
                    ],
                    "default": "total_spent"
                  },
                  "sort_direction": {
                    "type": "string",
                    "enum": [
                      "asc",
                      "desc"
                    ],
                    "default": "asc"
                  },
                  "locations": {
                    "oneOf": [
                      {
                        "type": "array",
                        "items": {
                          "type": "integer"
                        }
                      },
                      {
                        "type": "string",
                        "description": "Comma-separated location IDs."
                      }
                    ]
                  },
                  "connection": {
                    "type": "string",
                    "enum": [
                      "connected",
                      "known",
                      "unknown"
                    ]
                  },
                  "frequency": {
                    "type": "string",
                    "enum": [
                      "frequent",
                      "infrequent",
                      "new"
                    ]
                  },
                  "engagement": {
                    "type": "string"
                  },
                  "customer_group_id": {
                    "type": "integer"
                  },
                  "search_fields": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "updated_since": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}": {
      "get": {
        "operationId": "getCustomer",
        "summary": "Get a customer by 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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "A customer returned with identities, loyalty balances, and lifetime stats.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "customer",
                        "stats",
                        "extended"
                      ],
                      "properties": {
                        "customer": {
                          "$ref": "#/components/schemas/Customer"
                        },
                        "stats": {
                          "type": "object",
                          "description": "Lifetime visit count, spend, and last transaction for this brand.",
                          "required": [
                            "transaction_count",
                            "total_spent",
                            "last_transaction_date"
                          ],
                          "properties": {
                            "transaction_count": {
                              "type": "integer"
                            },
                            "total_spent": {
                              "type": "number"
                            },
                            "last_transaction_date": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date-time"
                            }
                          }
                        },
                        "extended": {
                          "type": "array",
                          "description": "Every known source and external_id for this customer, including the originating POS or CRM row.",
                          "items": {
                            "$ref": "#/components/schemas/IntegrationExtendedRow"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer fetched successfully",
                  "response": {
                    "customer": {
                      "id": 104823,
                      "brand_id": 42,
                      "location_id": 3,
                      "phone": "+61412345678",
                      "email": "jordan.lee@example.com",
                      "created_at": "2025-02-15T01:23:45.000Z",
                      "updated_at": "2026-06-21T03:15:00.000Z",
                      "accepts_marketing": true,
                      "first_name": "Jordan",
                      "last_name": "Lee",
                      "archived_at": null,
                      "frequency": "Frequent",
                      "engagement": "engaged",
                      "connection_type": "connected",
                      "pass_id": "ABC123XYZ",
                      "points": 120,
                      "cashback": 18.5
                    },
                    "stats": {
                      "transaction_count": 17,
                      "total_spent": 542.5,
                      "last_transaction_date": "2026-06-21T03:15:00.000Z"
                    },
                    "extended": [
                      {
                        "source": "lightspeed",
                        "external_id": "ext-abc123",
                        "json_data": {
                          "pos_id": "LS-98765"
                        },
                        "updated_at": "2026-06-21T03:15:00.000Z"
                      },
                      {
                        "source": "monday-crm",
                        "external_id": "crm-001",
                        "json_data": {},
                        "updated_at": "2026-05-01T00:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "brand_id or customer_id is missing or not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or customer_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "No customer with the given id exists for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Details are logged server-side.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/extended": {
      "get": {
        "operationId": "getCustomerExtended",
        "summary": "Get extended customer profile",
        "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.",
        "tags": [
          "Customer"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Customer extended profile with timeline.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "external_records",
                        "activities",
                        "transactions",
                        "pagination"
                      ],
                      "properties": {
                        "external_records": {
                          "type": "array",
                          "description": "External data records (e.g. POS source attributes).",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "activities": {
                          "type": "array",
                          "description": "Customer activity/event timeline entries.",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "transactions": {
                          "type": "array",
                          "description": "Transaction timeline entries. Includes payment details when `payments=true`.",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "pagination": {
                          "type": "object",
                          "required": [
                            "limit",
                            "offset"
                          ],
                          "properties": {
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer extended data fetched successfully",
                  "response": {
                    "external_records": [],
                    "activities": [
                      {
                        "type": "event",
                        "source": "lightspeed",
                        "created_at": "2026-06-21T03:15:00.000Z",
                        "data": {
                          "name": "checkin"
                        }
                      }
                    ],
                    "transactions": [
                      {
                        "type": "transaction",
                        "id": 5501,
                        "amount": 42.5,
                        "transaction_date": "2026-06-21T03:15:00.000Z",
                        "payments": []
                      }
                    ],
                    "pagination": {
                      "limit": 25,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "brand_id or customer_id is missing or not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or customer_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "No customer with the given id exists for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Details are logged server-side.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "description": "Maximum timeline records to return per collection (activities, transactions)."
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10000,
              "default": 0
            },
            "description": "Zero-based offset into the timeline."
          },
          {
            "name": "payments",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "When true, include payment detail objects inside each transaction record. Defaults to false."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/filter-groups": {
      "get": {
        "operationId": "listFilterGroups",
        "summary": "List customer groups",
        "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.",
        "tags": [
          "Customer groups"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Active customer groups for the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "group_name"
                        ],
                        "properties": {
                          "id": {
                            "type": "integer",
                            "description": "Customer group ID."
                          },
                          "group_name": {
                            "type": "string",
                            "description": "Human-readable customer group name."
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Filter groups fetched successfully",
                  "response": [
                    {
                      "id": 12,
                      "group_name": "VIP"
                    },
                    {
                      "id": 7,
                      "group_name": "Recent visitors"
                    }
                  ]
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/filter-groups/{group_id}/customers": {
      "get": {
        "operationId": "listFilterGroupCustomers",
        "summary": "Browse customers in a customer group",
        "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.",
        "tags": [
          "Customer groups"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Known customers in the customer group. Pagination matches customer browse.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "data",
                        "has_more",
                        "next_cursor",
                        "total_count"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CustomerRow"
                          }
                        },
                        "has_more": {
                          "type": "boolean"
                        },
                        "next_cursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "total_count": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer records fetched successfully (known only)",
                  "response": {
                    "data": [
                      {
                        "id": 104823,
                        "external_data": null,
                        "first_name": "Jordan",
                        "last_name": "Lee",
                        "email": "jordan.lee@example.com",
                        "phone": "+61412345678",
                        "location_id": 3,
                        "brand_id": 42,
                        "uuid": "c0b1f7a4-2e3d-4f5a-9b6c-7d8e9f0a1b2c",
                        "predicted_name": null,
                        "type": "customer",
                        "transaction_count": 17,
                        "total_spent": 542.5,
                        "tenure": 412,
                        "latest_transaction": "2026-06-21T03:15:00.000Z",
                        "last_seen": "2026-06-21T03:15:00.000Z",
                        "frequency": "Frequent",
                        "engagement": "engaged",
                        "connection_type": "connected"
                      }
                    ],
                    "has_more": true,
                    "next_cursor": "1|0|2026-06-21T03:15:00.000Z|104823",
                    "total_count": 1287
                  }
                }
              }
            }
          },
          "404": {
            "description": "Customer group not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Filter group not found"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "group_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 15
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Pagination cursor from response.next_cursor."
          },
          {
            "name": "sort_by",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "last_seen",
                "transaction_count",
                "total_spent",
                "tenure",
                "frequency",
                "engagement"
              ],
              "default": "total_spent"
            }
          },
          {
            "name": "sort_direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/filter-groups": {
      "get": {
        "operationId": "getCustomerFilterGroups",
        "summary": "List customer groups for a customer",
        "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.",
        "tags": [
          "Customer groups"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of customer groups this customer belongs to.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "group_name"
                        ],
                        "properties": {
                          "id": {
                            "type": "integer",
                            "description": "Customer group ID."
                          },
                          "group_name": {
                            "type": "string",
                            "description": "Human-readable customer group name."
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Successfully fetched customer filter groups",
                  "response": [
                    {
                      "id": 12,
                      "group_name": "VIP"
                    },
                    {
                      "id": 7,
                      "group_name": "Recent visitors"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "brand_id or customer_id is missing or not a positive integer. Invalid updated_since.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or customer_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "No customer with the given id exists for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Details are logged server-side.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/credit": {
      "get": {
        "operationId": "getCustomerCredit",
        "summary": "Get customer credit balance and wallet pass identifiers",
        "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.",
        "tags": [
          "Credit"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Customer credit and wallet pass information. When no credit is linked, giftcard_code is null and balance is 0.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "giftcard_code",
                        "balance",
                        "balance_fetched_at",
                        "apple_wallet",
                        "google_wallet"
                      ],
                      "properties": {
                        "giftcard_code": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Partner credit account code (for example Wrapped/Giftit), or null if none."
                        },
                        "balance": {
                          "type": "number",
                          "description": "Current credit balance in the brand's currency."
                        },
                        "balance_fetched_at": {
                          "type": "string",
                          "format": "date-time",
                          "description": "ISO-8601 timestamp of the balance lookup."
                        },
                        "apple_wallet": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "pass_type_identifier",
                              "serial_number"
                            ],
                            "properties": {
                              "pass_type_identifier": {
                                "type": "string"
                              },
                              "serial_number": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "google_wallet": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "issuer_id",
                              "class_suffix",
                              "object_suffix"
                            ],
                            "properties": {
                              "issuer_id": {
                                "type": "string"
                              },
                              "class_suffix": {
                                "type": "string"
                              },
                              "object_suffix": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "ok",
                  "response": {
                    "giftcard_code": "ABC123XYZ",
                    "balance": 75,
                    "balance_fetched_at": "2026-06-21T03:15:00.000Z",
                    "apple_wallet": [
                      {
                        "pass_type_identifier": "pass.com.myne.network.gift",
                        "serial_number": "abc123def456"
                      }
                    ],
                    "google_wallet": [
                      {
                        "issuer_id": "3388000000022345678",
                        "class_suffix": "giftcard_v1",
                        "object_suffix": "104823"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "brand_id or customer_id is missing or not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or customer_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "No customer with the given id exists for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Details are logged server-side.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      },
      "post": {
        "operationId": "postCustomerCredit",
        "summary": "Add credit to a customer",
        "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.",
        "tags": [
          "Credit"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Credit successfully added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "description": "Result details from the credit / loyalty system.",
                      "additionalProperties": true,
                      "properties": {
                        "giftcard_code": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "balance": {
                          "type": "number"
                        },
                        "adjusted_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "ok",
                  "response": {
                    "giftcard_code": "ABC123XYZ",
                    "balance": 85,
                    "loyalty_system": "wrapped",
                    "customer_giftcard_id": 567,
                    "adjusted_at": "2026-06-21T03:15:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body (bad JSON, missing amount/reason, or customer not found).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "examples": {
                  "invalidBrand": {
                    "summary": "Invalid brand/customer id",
                    "value": {
                      "message": "Invalid brand_id or customer_id"
                    }
                  },
                  "invalidJson": {
                    "summary": "Unparseable JSON body",
                    "value": {
                      "message": "Invalid JSON body"
                    }
                  },
                  "invalidAmount": {
                    "summary": "Validation error on amount",
                    "value": {
                      "message": "Amount must be a positive number"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Customer or credit account not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error. Details are logged server-side.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "amount",
                  "reason"
                ],
                "properties": {
                  "amount": {
                    "type": "number",
                    "description": "Dollar amount of credit to add."
                  },
                  "reason": {
                    "type": "string",
                    "description": "Required reason for the credit transaction (shown in the staff ledger)."
                  },
                  "note": {
                    "type": "string",
                    "description": "Deprecated alias for reason. Prefer reason."
                  },
                  "idempotency_key": {
                    "type": "string",
                    "description": "Optional client-supplied key for safe retries."
                  }
                }
              },
              "example": {
                "amount": 10,
                "reason": "Birthday bonus",
                "idempotency_key": "credit-topup-001"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/credit/ledger": {
      "get": {
        "operationId": "getCustomerCreditLedger",
        "summary": "Get customer credit ledger",
        "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.",
        "tags": [
          "Credit"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Ledger entries for the customer, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "balance",
                        "unit",
                        "entries"
                      ],
                      "properties": {
                        "balance": {
                          "type": "number",
                          "description": "Current balance for the lane."
                        },
                        "unit": {
                          "type": "string",
                          "enum": [
                            "dollars",
                            "points"
                          ],
                          "description": "Balance unit."
                        },
                        "entries": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "id",
                              "amount",
                              "balance_after",
                              "occurred_at",
                              "reason",
                              "unit",
                              "transaction_id",
                              "payment_id"
                            ],
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Ledger entry id."
                              },
                              "amount": {
                                "type": "number",
                                "description": "Credit (positive) or debit (negative) amount."
                              },
                              "balance_after": {
                                "type": "number",
                                "description": "Running balance after this entry."
                              },
                              "occurred_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "date-time",
                                "description": "ISO-8601 timestamp when the entry occurred."
                              },
                              "reason": {
                                "type": "string",
                                "description": "Human-readable reason, including partner top-up notes."
                              },
                              "unit": {
                                "type": "string",
                                "enum": [
                                  "dollars",
                                  "points"
                                ],
                                "description": "Whether the amount is currency or points."
                              },
                              "transaction_id": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Linked transaction id, when applicable."
                              },
                              "payment_id": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Linked payment id, when applicable."
                              },
                              "created_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "date-time",
                                "description": "Row created_at watermark for `updated_since` polling on ledgers."
                              }
                            }
                          },
                          "description": "Ledger entries, newest first."
                        },
                        "active": {
                          "type": "boolean",
                          "description": "Points ledger only: false when Brand Points is not enabled for this brand."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "ok",
                  "response": {
                    "balance": 75,
                    "unit": "dollars",
                    "entries": [
                      {
                        "id": 901,
                        "amount": 10,
                        "balance_after": 75,
                        "occurred_at": "2026-06-21T03:15:00.000Z",
                        "reason": "Staff credit — Birthday bonus",
                        "unit": "dollars",
                        "transaction_id": null,
                        "payment_id": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id, customer_id, or updated_since (ledger watermark is created_at).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or customer_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Customer not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/points": {
      "get": {
        "operationId": "getCustomerPointsBalance",
        "summary": "Get customer points balance",
        "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.",
        "tags": [
          "Points"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Brand Points balance and label. When points are not enabled, active is false and balance is 0.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "balance",
                        "points_label",
                        "active"
                      ],
                      "properties": {
                        "balance": {
                          "type": "integer",
                          "description": "Current points balance."
                        },
                        "points_label": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Brand-configured points label (for example Stars)."
                        },
                        "active": {
                          "type": "boolean",
                          "description": "False when Brand Points is not enabled for this brand."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "ok",
                  "response": {
                    "balance": 1250,
                    "points_label": "Stars",
                    "active": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id or customer_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or customer_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Customer not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      },
      "post": {
        "operationId": "postCustomerPointsTopUp",
        "summary": "Add points to a customer",
        "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.",
        "tags": [
          "Points"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Points successfully added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "additionalProperties": true,
                      "properties": {
                        "loyalty_system": {
                          "type": "string"
                        },
                        "points_amount": {
                          "type": "integer"
                        },
                        "balance_after": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "adjusted_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "ok",
                  "response": {
                    "loyalty_system": "brand_points",
                    "points_amount": 100,
                    "balance_after": 1350,
                    "adjusted_at": "2026-06-21T03:15:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or Brand Points is not enabled for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Brand Points is not enabled for this brand"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Customer not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "points_amount",
                  "reason"
                ],
                "properties": {
                  "points_amount": {
                    "type": "integer",
                    "description": "Number of points to add."
                  },
                  "amount": {
                    "type": "integer",
                    "description": "Deprecated alias for points_amount."
                  },
                  "reason": {
                    "type": "string",
                    "description": "Required reason for the top-up (shown in the staff ledger)."
                  },
                  "note": {
                    "type": "string",
                    "description": "Deprecated alias for reason. Prefer reason."
                  },
                  "idempotency_key": {
                    "type": "string",
                    "description": "Optional client-supplied key for safe retries."
                  }
                }
              },
              "example": {
                "points_amount": 100,
                "reason": "Compensation for delayed order"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/points/ledger": {
      "get": {
        "operationId": "getCustomerPointsLedger",
        "summary": "Get customer points ledger",
        "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.",
        "tags": [
          "Points"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Points ledger entries, or an empty ledger when Brand Points is not enabled.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "balance",
                        "unit",
                        "entries"
                      ],
                      "properties": {
                        "balance": {
                          "type": "number",
                          "description": "Current balance for the lane."
                        },
                        "unit": {
                          "type": "string",
                          "enum": [
                            "dollars",
                            "points"
                          ],
                          "description": "Balance unit."
                        },
                        "entries": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "id",
                              "amount",
                              "balance_after",
                              "occurred_at",
                              "reason",
                              "unit",
                              "transaction_id",
                              "payment_id"
                            ],
                            "properties": {
                              "id": {
                                "type": "integer",
                                "description": "Ledger entry id."
                              },
                              "amount": {
                                "type": "number",
                                "description": "Credit (positive) or debit (negative) amount."
                              },
                              "balance_after": {
                                "type": "number",
                                "description": "Running balance after this entry."
                              },
                              "occurred_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "date-time",
                                "description": "ISO-8601 timestamp when the entry occurred."
                              },
                              "reason": {
                                "type": "string",
                                "description": "Human-readable reason, including partner top-up notes."
                              },
                              "unit": {
                                "type": "string",
                                "enum": [
                                  "dollars",
                                  "points"
                                ],
                                "description": "Whether the amount is currency or points."
                              },
                              "transaction_id": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Linked transaction id, when applicable."
                              },
                              "payment_id": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Linked payment id, when applicable."
                              },
                              "created_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "date-time",
                                "description": "Row created_at watermark for `updated_since` polling on ledgers."
                              }
                            }
                          },
                          "description": "Ledger entries, newest first."
                        },
                        "active": {
                          "type": "boolean",
                          "description": "Points ledger only: false when Brand Points is not enabled for this brand."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "ok",
                  "response": {
                    "balance": 75,
                    "unit": "points",
                    "entries": [
                      {
                        "id": 901,
                        "amount": 10,
                        "balance_after": 75,
                        "occurred_at": "2026-06-21T03:15:00.000Z",
                        "reason": "Staff credit — Birthday bonus",
                        "unit": "points",
                        "transaction_id": null,
                        "payment_id": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id, customer_id, or updated_since (ledger watermark is created_at).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or customer_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Customer not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/transactions": {
      "get": {
        "operationId": "listCustomerTransactions",
        "summary": "List transactions for a customer",
        "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.",
        "tags": [
          "Transactions"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated customer transaction history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Transactions fetched successfully",
                  "response": {
                    "transactions": [
                      {
                        "id": 550012,
                        "transaction_id": 550012,
                        "transacted_at": "2026-06-21T03:15:00.000Z",
                        "location": "Sydney CBD",
                        "location_id": 3,
                        "source": "lightspeed",
                        "external_transaction_id": "POS-88421",
                        "notes": null,
                        "order_type": "dine_in",
                        "total_paid": 24.5,
                        "line_items": [
                          {
                            "id": 88001,
                            "product_id": 1201,
                            "product_name": "Flat White",
                            "quantity": 1,
                            "price_inc_tax": 5.5,
                            "total_price": 5.5,
                            "children": [],
                            "modifiers": []
                          }
                        ],
                        "payments": [
                          {
                            "id": 77001,
                            "method": "Card",
                            "paid_amount": 24.5,
                            "tip_amount": 0,
                            "last_4": "4242"
                          }
                        ]
                      }
                    ],
                    "total": 1,
                    "limit": 25,
                    "offset": 0,
                    "page": 1
                  }
                }
              }
            }
          },
          "404": {
            "description": "Customer not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10000,
              "default": 0
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "payments",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            },
            "description": "When true, include payment detail on each transaction."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/transactions/{transaction_id}": {
      "get": {
        "operationId": "getTransaction",
        "summary": "Get a transaction by id",
        "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.",
        "tags": [
          "Transactions"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Single transaction with line items.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Transaction fetched successfully",
                  "response": {
                    "transaction": {
                      "id": 550012,
                      "transaction_id": 550012,
                      "transacted_at": "2026-06-21T03:15:00.000Z",
                      "location": "Sydney CBD",
                      "location_id": 3,
                      "source": "lightspeed",
                      "external_transaction_id": "POS-88421",
                      "notes": null,
                      "order_type": "dine_in",
                      "total_paid": 24.5,
                      "line_items": [
                        {
                          "id": 88001,
                          "product_id": 1201,
                          "product_name": "Flat White",
                          "quantity": 1,
                          "price_inc_tax": 5.5,
                          "total_price": 5.5,
                          "children": [],
                          "modifiers": []
                        }
                      ],
                      "payments": [
                        {
                          "id": 77001,
                          "method": "Card",
                          "paid_amount": 24.5,
                          "tip_amount": 0,
                          "last_4": "4242"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Transaction not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Transaction not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "transaction_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "payments",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "When false, omit payment rows from the response."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/transactions/search": {
      "post": {
        "operationId": "searchTransactions",
        "summary": "Search transactions with filters",
        "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.",
        "tags": [
          "Transactions"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated transaction search results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Transactions fetched successfully",
                  "response": {
                    "transactions": [
                      {
                        "id": 550012,
                        "transaction_id": 550012,
                        "transacted_at": "2026-06-21T03:15:00.000Z",
                        "location": "Sydney CBD",
                        "location_id": 3,
                        "source": "lightspeed",
                        "external_transaction_id": "POS-88421",
                        "notes": null,
                        "order_type": "dine_in",
                        "total_paid": 24.5,
                        "line_items": [
                          {
                            "id": 88001,
                            "product_id": 1201,
                            "product_name": "Flat White",
                            "quantity": 1,
                            "price_inc_tax": 5.5,
                            "total_price": 5.5,
                            "children": [],
                            "modifiers": []
                          }
                        ],
                        "payments": [
                          {
                            "id": 77001,
                            "method": "Card",
                            "paid_amount": 24.5,
                            "tip_amount": 0,
                            "last_4": "4242"
                          }
                        ],
                        "customer_id": 104823
                      }
                    ],
                    "total": 1,
                    "limit": 25,
                    "offset": 0,
                    "page": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body. Invalid updated_since.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid JSON body"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "integer"
                  },
                  "location_id": {
                    "type": "integer"
                  },
                  "transacted_from": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "transacted_to": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "date_from": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias for transacted_from."
                  },
                  "date_to": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias for transacted_to."
                  },
                  "min_amount": {
                    "type": "number"
                  },
                  "max_amount": {
                    "type": "number"
                  },
                  "payments": {
                    "type": "boolean"
                  },
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 25
                  },
                  "offset": {
                    "type": "integer",
                    "minimum": 0,
                    "default": 0
                  },
                  "updated_since": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (UTC, inclusive)."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/activities/manual-types": {
      "get": {
        "operationId": "listManualActivityTypes",
        "summary": "List manual activity type labels",
        "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.",
        "tags": [
          "Activities"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Distinct manual activity type labels for the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Manual activity types fetched successfully",
                  "response": {
                    "types": [
                      "Call",
                      "Contract",
                      "Demo",
                      "Email",
                      "Follow-up",
                      "Meeting",
                      "Note",
                      "Proposal",
                      "Site visit",
                      "Support"
                    ]
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/activities": {
      "get": {
        "operationId": "listCustomerActivities",
        "summary": "List activities for a customer",
        "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.",
        "tags": [
          "Activities"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated CRM activity feed for the customer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Activities fetched successfully",
                  "response": {
                    "activities": [
                      {
                        "id": 99001,
                        "event_time": "2026-06-20T11:00:00.000Z",
                        "event_name": "Manual activity",
                        "source": "integration_api",
                        "location": "Sydney CBD",
                        "location_id": 3,
                        "business_id": 801,
                        "business_name": "Acme Hospitality Group",
                        "activity_performer_category": "staff",
                        "title": "Follow-up call",
                        "activity_type": "Call",
                        "notes": "Confirmed catering order for Friday.",
                        "created_by_display_name": "Partner CRM",
                        "created_by_integration_source": "integration_api"
                      }
                    ],
                    "total": 1,
                    "limit": 25,
                    "offset": 0,
                    "page": 1
                  }
                }
              }
            }
          },
          "404": {
            "description": "Customer not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 10000,
              "default": 0
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "activity_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Match event_name or manual activity_type label (e.g. Call)."
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Alias for activity_type."
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filter by event source (e.g. business_manual or your integration source)."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/activities/manual": {
      "post": {
        "operationId": "postCustomerManualActivity",
        "summary": "Record a manual CRM activity",
        "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.",
        "tags": [
          "Activities"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Manual activity recorded on the CRM feed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Manual activity recorded successfully",
                  "response": {
                    "id": 99001,
                    "event_time": "2026-06-20T11:00:00.000Z",
                    "event_name": "Manual activity",
                    "source": "integration_api",
                    "title": "Follow-up call",
                    "activity_type": "Call",
                    "notes": "Confirmed catering order for Friday."
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body or business link.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "title is required, or provide both activity_type and notes"
                }
              }
            }
          },
          "403": {
            "description": "source cannot be set on write requests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "source is managed by your API credentials and cannot be set on write requests"
                }
              }
            }
          },
          "404": {
            "description": "Customer not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "activity_type": {
                    "type": "string"
                  },
                  "notes": {
                    "type": "string"
                  },
                  "event_time": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "business_id": {
                    "type": "integer"
                  }
                },
                "description": "Provide title, or both activity_type and notes. source is stamped from your credentials."
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/activities/search": {
      "post": {
        "operationId": "searchActivities",
        "summary": "Search activities with filters",
        "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.",
        "tags": [
          "Activities"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated CRM activity search results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Activities fetched successfully",
                  "response": {
                    "activities": [
                      {
                        "id": 99001,
                        "event_time": "2026-06-20T11:00:00.000Z",
                        "event_name": "Manual activity",
                        "source": "integration_api",
                        "location": "Sydney CBD",
                        "location_id": 3,
                        "business_id": 801,
                        "business_name": "Acme Hospitality Group",
                        "activity_performer_category": "staff",
                        "title": "Follow-up call",
                        "activity_type": "Call",
                        "notes": "Confirmed catering order for Friday.",
                        "created_by_display_name": "Partner CRM",
                        "created_by_integration_source": "integration_api",
                        "customer_id": 104823
                      }
                    ],
                    "total": 1,
                    "limit": 25,
                    "offset": 0,
                    "page": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body. Invalid updated_since.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid JSON body"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "customer_id": {
                    "type": "integer"
                  },
                  "business_id": {
                    "type": "integer"
                  },
                  "activity_type": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string",
                    "description": "Alias for activity_type."
                  },
                  "source": {
                    "type": "string"
                  },
                  "event_from": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "event_to": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "date_from": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias for event_from."
                  },
                  "date_to": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias for event_to."
                  },
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 25
                  },
                  "offset": {
                    "type": "integer",
                    "minimum": 0,
                    "default": 0
                  },
                  "updated_since": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (UTC, inclusive)."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/businesses/by-external-id": {
      "get": {
        "operationId": "getBusinessByExternalId",
        "summary": "Get a business by external id",
        "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.",
        "tags": [
          "Businesses"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Business account for the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                },
                "example": {
                  "message": "Business fetched successfully",
                  "response": {
                    "id": 801,
                    "brand_id": 42,
                    "location_id": 3,
                    "name": "Acme Hospitality Group",
                    "external_id": "biz-001",
                    "source": "integration_api",
                    "address_line_1": "100 George St",
                    "address_line_2": null,
                    "city": "Sydney",
                    "region": "NSW",
                    "postal_code": "2000",
                    "country": "AU",
                    "phone": "+61298765432",
                    "email": "accounts@acme.example.com",
                    "logo_url": null,
                    "website": "https://acme.example.com",
                    "description": "Corporate catering partner",
                    "created_at": "2025-03-01T10:00:00.000Z",
                    "updated_at": "2026-06-15T08:30:00.000Z",
                    "archived_at": null,
                    "contact_count": 5,
                    "primary_contact_customer_id": 104823,
                    "primary_contact_first_name": "Jordan",
                    "primary_contact_last_name": "Lee",
                    "last_contacted_at": "2026-06-18T09:00:00.000Z",
                    "last_active_at": "2026-06-20T14:22:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "external_id query parameter is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "external_id query parameter is required"
                }
              }
            }
          },
          "404": {
            "description": "No business matches the external id for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Business not found"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your integration's identifier for the business."
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/businesses": {
      "get": {
        "operationId": "listBusinesses",
        "summary": "Browse businesses for a brand",
        "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.",
        "tags": [
          "Businesses"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated business accounts for the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "businesses",
                        "total_businesses",
                        "total_pages",
                        "current_page"
                      ],
                      "properties": {
                        "businesses": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "total_businesses": {
                          "type": "integer"
                        },
                        "total_pages": {
                          "type": "integer"
                        },
                        "current_page": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Business records fetched successfully",
                  "response": {
                    "businesses": [
                      {
                        "id": 801,
                        "brand_id": 42,
                        "location_id": 3,
                        "name": "Acme Hospitality Group",
                        "external_id": "biz-001",
                        "source": "integration_api",
                        "address_line_1": "100 George St",
                        "address_line_2": null,
                        "city": "Sydney",
                        "region": "NSW",
                        "postal_code": "2000",
                        "country": "AU",
                        "phone": "+61298765432",
                        "email": "accounts@acme.example.com",
                        "logo_url": null,
                        "website": "https://acme.example.com",
                        "description": "Corporate catering partner",
                        "created_at": "2025-03-01T10:00:00.000Z",
                        "updated_at": "2026-06-15T08:30:00.000Z",
                        "archived_at": null,
                        "contact_count": 5,
                        "primary_contact_customer_id": 104823,
                        "primary_contact_first_name": "Jordan",
                        "primary_contact_last_name": "Lee",
                        "last_contacted_at": "2026-06-18T09:00:00.000Z",
                        "last_active_at": "2026-06-20T14:22:00.000Z"
                      }
                    ],
                    "total_businesses": 24,
                    "total_pages": 3,
                    "current_page": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "brand_id is missing, not a positive integer, or updated_since is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 10
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text search across business fields."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "ISO-8601 timestamp. When set, only businesses with updated_at on or after this instant are returned (compared in UTC)."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/businesses/{business_id}": {
      "get": {
        "operationId": "getBusiness",
        "summary": "Get a business by 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.",
        "tags": [
          "Businesses"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Single business account for the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "additionalProperties": true
                    }
                  }
                },
                "example": {
                  "message": "Business fetched successfully",
                  "response": {
                    "id": 801,
                    "brand_id": 42,
                    "location_id": 3,
                    "name": "Acme Hospitality Group",
                    "external_id": "biz-001",
                    "source": "integration_api",
                    "address_line_1": "100 George St",
                    "address_line_2": null,
                    "city": "Sydney",
                    "region": "NSW",
                    "postal_code": "2000",
                    "country": "AU",
                    "phone": "+61298765432",
                    "email": "accounts@acme.example.com",
                    "logo_url": null,
                    "website": "https://acme.example.com",
                    "description": "Corporate catering partner",
                    "created_at": "2025-03-01T10:00:00.000Z",
                    "updated_at": "2026-06-15T08:30:00.000Z",
                    "archived_at": null,
                    "contact_count": 5,
                    "primary_contact_customer_id": 104823,
                    "primary_contact_first_name": "Jordan",
                    "primary_contact_last_name": "Lee",
                    "last_contacted_at": "2026-06-18T09:00:00.000Z",
                    "last_active_at": "2026-06-20T14:22:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "brand_id or business_id is missing or not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or business_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "No business with the given id exists for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Business not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "business_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/businesses/{business_id}/extended": {
      "get": {
        "operationId": "getBusinessExtended",
        "summary": "Get extended business profile",
        "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.",
        "tags": [
          "Businesses"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Extended metadata records keyed by source and external_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "source",
                          "external_id",
                          "json_data"
                        ],
                        "properties": {
                          "source": {
                            "type": "string"
                          },
                          "external_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "json_data": {
                            "type": "object",
                            "additionalProperties": true
                          },
                          "updated_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Business extended data fetched successfully",
                  "response": [
                    {
                      "source": "integration_api",
                      "external_id": "biz-001",
                      "json_data": {
                        "industry": "hospitality",
                        "account_tier": "gold",
                        "contract_renewal": "2027-01-01"
                      },
                      "updated_at": "2026-06-15T08:30:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "brand_id or business_id is missing or not a positive integer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or business_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "No business with the given id exists for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Business not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "business_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/products": {
      "get": {
        "operationId": "listProducts",
        "summary": "Browse products for a brand",
        "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.",
        "tags": [
          "Products"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated product catalog for the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Products fetched successfully",
                  "response": {
                    "products": [
                      {
                        "id": 501,
                        "name": "Flat white",
                        "description": "Double-shot flat white",
                        "price": 4.5,
                        "is_option": false,
                        "sku": "FW-REG",
                        "image_url": null,
                        "created_at": "2025-11-01T00:00:00.000Z",
                        "updated_at": "2026-06-01T00:00:00.000Z"
                      }
                    ],
                    "total": 1,
                    "limit": 20,
                    "offset": 0,
                    "page": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id. Invalid updated_since.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "location_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Include products scoped to this location (unscoped products still match)."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/products/{product_id}": {
      "get": {
        "operationId": "getProduct",
        "summary": "Get a product by 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.",
        "tags": [
          "Products"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Single product with extended[] integration payloads.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Product fetched successfully",
                  "response": {
                    "id": 501,
                    "name": "Flat white",
                    "description": "Double-shot flat white",
                    "price": 4.5,
                    "is_option": false,
                    "sku": "FW-REG",
                    "image_url": null,
                    "created_at": "2025-11-01T00:00:00.000Z",
                    "updated_at": "2026-06-01T00:00:00.000Z",
                    "extended": [
                      {
                        "source": "lightspeed-k",
                        "external_id": "456",
                        "json_data": {},
                        "updated_at": "2026-06-01T00:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id or product_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or product_id"
                }
              }
            }
          },
          "404": {
            "description": "Product not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Product not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "product_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/products/by-external-id": {
      "get": {
        "operationId": "getProductByExternalId",
        "summary": "Get a product by external id",
        "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.",
        "tags": [
          "Products"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Product resolved from external_id with extended[] payloads.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Product fetched successfully",
                  "response": {
                    "id": 501,
                    "name": "Flat white",
                    "description": "Double-shot flat white",
                    "price": 4.5,
                    "is_option": false,
                    "sku": "FW-REG",
                    "image_url": null,
                    "created_at": "2025-11-01T00:00:00.000Z",
                    "updated_at": "2026-06-01T00:00:00.000Z",
                    "extended": [
                      {
                        "source": "integration_api",
                        "external_id": "POS-456",
                        "json_data": {},
                        "updated_at": "2026-06-01T00:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "external_id is required.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "external_id query parameter is required"
                }
              }
            }
          },
          "404": {
            "description": "Product not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Product not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "external_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Integration source key; defaults to your credential source."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/products/search": {
      "post": {
        "operationId": "searchProducts",
        "summary": "Search products with advanced filters",
        "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.",
        "tags": [
          "Products"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated product search results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Products fetched successfully",
                  "response": {
                    "products": [
                      {
                        "id": 501,
                        "name": "Flat white",
                        "description": "Double-shot flat white",
                        "price": 4.5,
                        "is_option": false,
                        "sku": "FW-REG",
                        "image_url": null,
                        "created_at": "2025-11-01T00:00:00.000Z",
                        "updated_at": "2026-06-01T00:00:00.000Z"
                      }
                    ],
                    "total": 1,
                    "limit": 20,
                    "offset": 0,
                    "page": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body. Invalid updated_since.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid JSON body"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "search": {
                    "type": "string"
                  },
                  "category_ids": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "source": {
                    "type": "string",
                    "description": "Filter to products synced under one integration source."
                  },
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 20
                  },
                  "updated_since": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (UTC, inclusive)."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/categories": {
      "get": {
        "operationId": "listProductCategories",
        "summary": "Browse product categories for a brand",
        "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.",
        "tags": [
          "Products"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated product categories for the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Categories fetched successfully",
                  "response": {
                    "categories": [
                      {
                        "id": 10,
                        "name": "Coffee",
                        "description": "Espresso-based drinks",
                        "external_id": "cat-coffee",
                        "product_count": 12,
                        "created_at": "2025-11-01T00:00:00.000Z",
                        "updated_at": "2026-06-01T00:00:00.000Z"
                      }
                    ],
                    "total": 1,
                    "limit": 20,
                    "offset": 0,
                    "page": 1
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/categories/{category_id}": {
      "get": {
        "operationId": "getProductCategory",
        "summary": "Get a product category by id",
        "description": "Load one product category by id with `product_count`.\n\nReturns partner-safe fields; `extended[]` is reserved for future category integration payloads.",
        "tags": [
          "Products"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Single product category.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Category fetched successfully",
                  "response": {
                    "id": 10,
                    "name": "Coffee",
                    "description": "Espresso-based drinks",
                    "external_id": "cat-coffee",
                    "product_count": 12,
                    "created_at": "2025-11-01T00:00:00.000Z",
                    "updated_at": "2026-06-01T00:00:00.000Z",
                    "extended": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id or category_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or category_id"
                }
              }
            }
          },
          "404": {
            "description": "Category not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Category not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "category_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/promotions": {
      "get": {
        "operationId": "listPromotions",
        "summary": "Browse promotions for a brand",
        "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.",
        "tags": [
          "Promotions"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated promotion catalog for the brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "promotions",
                        "total",
                        "limit",
                        "offset",
                        "page"
                      ],
                      "properties": {
                        "promotions": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true
                          }
                        },
                        "total": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        },
                        "page": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Promotions fetched successfully",
                  "response": {
                    "promotions": [
                      {
                        "id": 901,
                        "brand_id": 42,
                        "name": "Free regular coffee",
                        "description": "One regular coffee on us",
                        "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                        "discount_type": "free_item",
                        "applies_to": "in_store",
                        "channel": "pos",
                        "location_id": null,
                        "valid_from": "2026-01-01T00:00:00.000Z",
                        "valid_until": "2026-12-31T23:59:59.000Z",
                        "created_at": "2025-12-01T00:00:00.000Z",
                        "updated_at": "2026-06-01T00:00:00.000Z",
                        "availability": [
                          "connection_pages",
                          "lsk",
                          "lso",
                          "meandu",
                          "meandu_connect",
                          "shopify"
                        ],
                        "requirements": {
                          "products": [],
                          "categories": [
                            {
                              "id": 10,
                              "name": "Coffee",
                              "extended": []
                            }
                          ]
                        },
                        "targets": {
                          "products": [
                            {
                              "id": 501,
                              "name": "Flat white",
                              "extended": [
                                {
                                  "source": "lightspeed-k",
                                  "external_id": "456",
                                  "json_data": {},
                                  "updated_at": null
                                }
                              ]
                            }
                          ],
                          "categories": []
                        }
                      }
                    ],
                    "total": 1,
                    "limit": 20,
                    "offset": 0,
                    "page": 1
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "location_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Restrict to promotions for one location (includes brand-wide promotions)."
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "valid_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "When true, only promotions within their valid date range are returned."
          },
          {
            "name": "surface",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "connection_pages",
                "meandu",
                "lso",
                "lsk",
                "shopify"
              ]
            },
            "description": "Restrict to promotions visible on one channel."
          },
          {
            "name": "integration_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ordering",
                "listing",
                "ecommerce",
                "pos"
              ]
            },
            "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 need surface (lsk or lso). ordering → meandu; listing and ecommerce → connection_pages."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/promotions": {
      "get": {
        "operationId": "listCustomerPromotions",
        "summary": "List promotions for a customer with eligibility",
        "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.",
        "tags": [
          "Promotions"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Promotions visible to the customer with eligibility, plus current points and cashback balances.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "promotions",
                        "total",
                        "points_balance",
                        "cashback_balance_in_cents"
                      ],
                      "properties": {
                        "promotions": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "additionalProperties": true,
                            "properties": {
                              "eligibility": {
                                "type": "object",
                                "required": [
                                  "visibility_state"
                                ],
                                "properties": {
                                  "visibility_state": {
                                    "type": "string",
                                    "enum": [
                                      "unlocked",
                                      "locked"
                                    ]
                                  },
                                  "unlock_display_text": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                }
                              },
                              "cashback_balance_in_cents": {
                                "type": "integer",
                                "description": "Present on pay-with-cashback promotions. Current wallet balance in cents (not a cart discount)."
                              }
                            }
                          }
                        },
                        "total": {
                          "type": "integer"
                        },
                        "points_balance": {
                          "type": "integer",
                          "description": "Current points balance for this customer (0 when the points lane is off)."
                        },
                        "cashback_balance_in_cents": {
                          "type": "integer",
                          "description": "Current cashback wallet balance in cents (0 when there is no cashback balance)."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Customer promotions fetched successfully",
                  "response": {
                    "promotions": [
                      {
                        "id": 901,
                        "brand_id": 42,
                        "name": "Free regular coffee",
                        "description": "One regular coffee on us",
                        "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                        "discount_type": "free_item",
                        "applies_to": "in_store",
                        "channel": "pos",
                        "location_id": null,
                        "valid_from": "2026-01-01T00:00:00.000Z",
                        "valid_until": "2026-12-31T23:59:59.000Z",
                        "created_at": "2025-12-01T00:00:00.000Z",
                        "updated_at": "2026-06-01T00:00:00.000Z",
                        "availability": [
                          "connection_pages",
                          "lsk",
                          "lso",
                          "meandu",
                          "meandu_connect",
                          "shopify"
                        ],
                        "requirements": {
                          "products": [],
                          "categories": [
                            {
                              "id": 10,
                              "name": "Coffee",
                              "extended": []
                            }
                          ]
                        },
                        "targets": {
                          "products": [
                            {
                              "id": 501,
                              "name": "Flat white",
                              "extended": [
                                {
                                  "source": "lightspeed-k",
                                  "external_id": "456",
                                  "json_data": {},
                                  "updated_at": null
                                }
                              ]
                            }
                          ],
                          "categories": []
                        },
                        "eligibility": {
                          "visibility_state": "locked",
                          "unlock_display_text": "Visit 2 more times this month"
                        }
                      },
                      {
                        "id": 880,
                        "brand_id": 42,
                        "name": "Pay with Cashback",
                        "description": "Spend store credit on this order.",
                        "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                        "discount_type": "free_item",
                        "applies_to": "in_store",
                        "channel": "pos",
                        "location_id": null,
                        "valid_from": "2026-01-01T00:00:00.000Z",
                        "valid_until": "2026-12-31T23:59:59.000Z",
                        "created_at": "2025-12-01T00:00:00.000Z",
                        "updated_at": "2026-06-01T00:00:00.000Z",
                        "availability": [
                          "connection_pages",
                          "lsk",
                          "lso",
                          "meandu",
                          "meandu_connect",
                          "shopify"
                        ],
                        "requirements": {
                          "products": [],
                          "categories": [
                            {
                              "id": 10,
                              "name": "Coffee",
                              "extended": []
                            }
                          ]
                        },
                        "targets": {
                          "products": [
                            {
                              "id": 501,
                              "name": "Flat white",
                              "extended": [
                                {
                                  "source": "lightspeed-k",
                                  "external_id": "456",
                                  "json_data": {},
                                  "updated_at": null
                                }
                              ]
                            }
                          ],
                          "categories": []
                        },
                        "eligibility": {
                          "visibility_state": "unlocked",
                          "unlock_display_text": null
                        },
                        "rules": {
                          "promotion_type": "pay_with_cashback"
                        },
                        "cashback_balance_in_cents": 2500
                      }
                    ],
                    "total": 2,
                    "points_balance": 120,
                    "cashback_balance_in_cents": 2500
                  }
                }
              }
            }
          },
          "404": {
            "description": "Customer not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "valid_only",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "When true (default), omit expired or not-yet-valid promotions."
          },
          {
            "name": "surface",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "connection_pages",
                "meandu",
                "lso",
                "lsk",
                "shopify"
              ]
            },
            "description": "Restrict to promotions visible on one channel."
          },
          {
            "name": "integration_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "ordering",
                "listing",
                "ecommerce",
                "pos"
              ]
            },
            "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 need surface (lsk or lso). ordering → meandu; listing and ecommerce → connection_pages."
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/promotions/{promotion_id}": {
      "get": {
        "operationId": "getPromotion",
        "summary": "Get a promotion by 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.",
        "tags": [
          "Promotions"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Enriched promotion with availability and catalog references.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Promotion fetched successfully",
                  "response": {
                    "id": 901,
                    "brand_id": 42,
                    "name": "Free regular coffee",
                    "description": "One regular coffee on us",
                    "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                    "discount_type": "free_item",
                    "applies_to": "in_store",
                    "channel": "pos",
                    "location_id": null,
                    "valid_from": "2026-01-01T00:00:00.000Z",
                    "valid_until": "2026-12-31T23:59:59.000Z",
                    "created_at": "2025-12-01T00:00:00.000Z",
                    "updated_at": "2026-06-01T00:00:00.000Z",
                    "availability": [
                      "connection_pages",
                      "lsk",
                      "lso",
                      "meandu",
                      "meandu_connect",
                      "shopify"
                    ],
                    "requirements": {
                      "products": [],
                      "categories": [
                        {
                          "id": 10,
                          "name": "Coffee",
                          "extended": []
                        }
                      ]
                    },
                    "targets": {
                      "products": [
                        {
                          "id": 501,
                          "name": "Flat white",
                          "extended": [
                            {
                              "source": "lightspeed-k",
                              "external_id": "456",
                              "json_data": {},
                              "updated_at": null
                            }
                          ]
                        }
                      ],
                      "categories": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id or promotion_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or promotion_id"
                }
              }
            }
          },
          "404": {
            "description": "Promotion not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Promotion not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "promotion_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/promotions/search": {
      "post": {
        "operationId": "searchPromotions",
        "summary": "Search promotions with advanced filters",
        "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.",
        "tags": [
          "Promotions"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated enriched promotion search results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Promotions fetched successfully",
                  "response": {
                    "promotions": [
                      {
                        "id": 901,
                        "brand_id": 42,
                        "name": "Free regular coffee",
                        "description": "One regular coffee on us",
                        "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                        "discount_type": "free_item",
                        "applies_to": "in_store",
                        "channel": "pos",
                        "location_id": null,
                        "valid_from": "2026-01-01T00:00:00.000Z",
                        "valid_until": "2026-12-31T23:59:59.000Z",
                        "created_at": "2025-12-01T00:00:00.000Z",
                        "updated_at": "2026-06-01T00:00:00.000Z",
                        "availability": [
                          "connection_pages",
                          "lsk",
                          "lso",
                          "meandu",
                          "meandu_connect",
                          "shopify"
                        ],
                        "requirements": {
                          "products": [],
                          "categories": [
                            {
                              "id": 10,
                              "name": "Coffee",
                              "extended": []
                            }
                          ]
                        },
                        "targets": {
                          "products": [
                            {
                              "id": 501,
                              "name": "Flat white",
                              "extended": [
                                {
                                  "source": "lightspeed-k",
                                  "external_id": "456",
                                  "json_data": {},
                                  "updated_at": null
                                }
                              ]
                            }
                          ],
                          "categories": []
                        }
                      }
                    ],
                    "total": 1,
                    "limit": 20,
                    "offset": 0,
                    "page": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body or filter. Invalid updated_since.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "integration_type=pos requires an explicit POS register surface"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "search": {
                    "type": "string"
                  },
                  "location_id": {
                    "type": "integer"
                  },
                  "product_ids": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "category_ids": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "surface": {
                    "type": "string",
                    "enum": [
                      "connection_pages",
                      "meandu",
                      "lso",
                      "lsk",
                      "shopify"
                    ]
                  },
                  "integration_type": {
                    "type": "string",
                    "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 need surface (lsk or lso). ordering → meandu; listing and ecommerce → connection_pages."
                  },
                  "valid_only": {
                    "type": "boolean"
                  },
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 20
                  },
                  "updated_since": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (UTC, inclusive)."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/promotions/evaluate": {
      "post": {
        "operationId": "evaluatePromotions",
        "summary": "Check which rewards fit this open order",
        "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.",
        "tags": [
          "Promotions"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Order-aware promotion evaluation result. Rewards include status, optional discount_amount_in_cents, non_redeemable_cause when unavailable, and discount_application (how to put money on the sale for this surface). Optional basket_id is echoed when supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "status",
                        "membership",
                        "rewards"
                      ],
                      "properties": {
                        "status": {
                          "type": "string",
                          "description": "Engine status; typically ok on success."
                        },
                        "basket_id": {
                          "type": "string",
                          "description": "Echo of partner basket_id when supplied on the request. Correlation only—not a MYNE stamp."
                        },
                        "membership": {
                          "type": "object",
                          "additionalProperties": true,
                          "description": "Includes id, points_balance, optional cashback_balance_in_cents, and rewards (same array as top-level rewards).",
                          "properties": {
                            "id": {
                              "oneOf": [
                                {
                                  "type": "string"
                                },
                                {
                                  "type": "integer"
                                }
                              ]
                            },
                            "points_balance": {
                              "type": "integer"
                            },
                            "cashback_balance_in_cents": {
                              "type": "integer",
                              "description": "Current cashback wallet balance in cents when the cashback lane is on."
                            },
                            "rewards": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/EvaluateReward"
                              }
                            }
                          }
                        },
                        "rewards": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/EvaluateReward"
                          }
                        },
                        "surface": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Echo of the resolved surface used for engine source and catalog filtering, or null when omitted."
                        }
                      }
                    }
                  }
                },
                "examples": {
                  "lskAvailable": {
                    "summary": "LSK — available free item (negative promotion product)",
                    "value": {
                      "message": "Promotions evaluated successfully",
                      "response": {
                        "status": "ok",
                        "basket_id": "pos-order-7f3a2c",
                        "membership": {
                          "id": "104823",
                          "points_balance": 120,
                          "rewards": [
                            {
                              "id": "901",
                              "type": "Offer",
                              "name": "Free regular coffee",
                              "description": "One regular coffee on us",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "status": "AVAILABLE_TO_REDEEM",
                              "visibility_state": "unlocked",
                              "application_scope": "line",
                              "cart_application_method": "MANUAL_APPLY",
                              "discount_amount_in_cents": 550,
                              "discount_application": {
                                "method": "negative_product_line",
                                "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."
                              },
                              "promotion": {
                                "id": 901,
                                "brand_id": 42,
                                "name": "Free regular coffee",
                                "description": "One regular coffee on us",
                                "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                                "discount_type": "free_item",
                                "applies_to": "in_store",
                                "channel": "pos",
                                "location_id": null,
                                "valid_from": "2026-01-01T00:00:00.000Z",
                                "valid_until": "2026-12-31T23:59:59.000Z",
                                "created_at": "2025-12-01T00:00:00.000Z",
                                "updated_at": "2026-06-01T00:00:00.000Z",
                                "availability": [
                                  "connection_pages",
                                  "lsk",
                                  "lso",
                                  "meandu",
                                  "meandu_connect",
                                  "shopify"
                                ],
                                "requirements": {
                                  "products": [],
                                  "categories": [
                                    {
                                      "id": 10,
                                      "name": "Coffee",
                                      "extended": []
                                    }
                                  ]
                                },
                                "targets": {
                                  "products": [
                                    {
                                      "id": 501,
                                      "name": "Flat white",
                                      "extended": [
                                        {
                                          "source": "lightspeed-k",
                                          "external_id": "456",
                                          "json_data": {},
                                          "updated_at": null
                                        }
                                      ]
                                    }
                                  ],
                                  "categories": []
                                }
                              }
                            }
                          ]
                        },
                        "rewards": [
                          {
                            "id": "901",
                            "type": "Offer",
                            "name": "Free regular coffee",
                            "description": "One regular coffee on us",
                            "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                            "status": "AVAILABLE_TO_REDEEM",
                            "visibility_state": "unlocked",
                            "application_scope": "line",
                            "cart_application_method": "MANUAL_APPLY",
                            "discount_amount_in_cents": 550,
                            "discount_application": {
                              "method": "negative_product_line",
                              "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."
                            },
                            "promotion": {
                              "id": 901,
                              "brand_id": 42,
                              "name": "Free regular coffee",
                              "description": "One regular coffee on us",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "discount_type": "free_item",
                              "applies_to": "in_store",
                              "channel": "pos",
                              "location_id": null,
                              "valid_from": "2026-01-01T00:00:00.000Z",
                              "valid_until": "2026-12-31T23:59:59.000Z",
                              "created_at": "2025-12-01T00:00:00.000Z",
                              "updated_at": "2026-06-01T00:00:00.000Z",
                              "availability": [
                                "connection_pages",
                                "lsk",
                                "lso",
                                "meandu",
                                "meandu_connect",
                                "shopify"
                              ],
                              "requirements": {
                                "products": [],
                                "categories": [
                                  {
                                    "id": 10,
                                    "name": "Coffee",
                                    "extended": []
                                  }
                                ]
                              },
                              "targets": {
                                "products": [
                                  {
                                    "id": 501,
                                    "name": "Flat white",
                                    "extended": [
                                      {
                                        "source": "lightspeed-k",
                                        "external_id": "456",
                                        "json_data": {},
                                        "updated_at": null
                                      }
                                    ]
                                  }
                                ],
                                "categories": []
                              }
                            }
                          }
                        ],
                        "surface": "lsk"
                      }
                    }
                  },
                  "lskUnavailableWrongCart": {
                    "summary": "LSK — eligible reward, wrong cart",
                    "value": {
                      "message": "Promotions evaluated successfully",
                      "response": {
                        "status": "ok",
                        "basket_id": "pos-order-7f3a2c",
                        "membership": {
                          "id": "104823",
                          "points_balance": 120,
                          "rewards": [
                            {
                              "id": "901",
                              "type": "Offer",
                              "name": "Free regular coffee",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "status": "UNAVAILABLE_TO_REDEEM",
                              "visibility_state": "unlocked",
                              "application_scope": "line",
                              "cart_application_method": "MANUAL_APPLY",
                              "non_redeemable_cause": {
                                "code": "NO_MATCHING_PRODUCTS",
                                "message": "No matching products"
                              },
                              "discount_application": {
                                "method": "negative_product_line",
                                "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."
                              },
                              "promotion": {
                                "id": 901,
                                "brand_id": 42,
                                "name": "Free regular coffee",
                                "description": "One regular coffee on us",
                                "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                                "discount_type": "free_item",
                                "applies_to": "in_store",
                                "channel": "pos",
                                "location_id": null,
                                "valid_from": "2026-01-01T00:00:00.000Z",
                                "valid_until": "2026-12-31T23:59:59.000Z",
                                "created_at": "2025-12-01T00:00:00.000Z",
                                "updated_at": "2026-06-01T00:00:00.000Z",
                                "availability": [
                                  "connection_pages",
                                  "lsk",
                                  "lso",
                                  "meandu",
                                  "meandu_connect",
                                  "shopify"
                                ],
                                "requirements": {
                                  "products": [],
                                  "categories": [
                                    {
                                      "id": 10,
                                      "name": "Coffee",
                                      "extended": []
                                    }
                                  ]
                                },
                                "targets": {
                                  "products": [
                                    {
                                      "id": 501,
                                      "name": "Flat white",
                                      "extended": [
                                        {
                                          "source": "lightspeed-k",
                                          "external_id": "456",
                                          "json_data": {},
                                          "updated_at": null
                                        }
                                      ]
                                    }
                                  ],
                                  "categories": []
                                }
                              }
                            }
                          ]
                        },
                        "rewards": [
                          {
                            "id": "901",
                            "type": "Offer",
                            "name": "Free regular coffee",
                            "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                            "status": "UNAVAILABLE_TO_REDEEM",
                            "visibility_state": "unlocked",
                            "application_scope": "line",
                            "cart_application_method": "MANUAL_APPLY",
                            "non_redeemable_cause": {
                              "code": "NO_MATCHING_PRODUCTS",
                              "message": "No matching products"
                            },
                            "discount_application": {
                              "method": "negative_product_line",
                              "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."
                            },
                            "promotion": {
                              "id": 901,
                              "brand_id": 42,
                              "name": "Free regular coffee",
                              "description": "One regular coffee on us",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "discount_type": "free_item",
                              "applies_to": "in_store",
                              "channel": "pos",
                              "location_id": null,
                              "valid_from": "2026-01-01T00:00:00.000Z",
                              "valid_until": "2026-12-31T23:59:59.000Z",
                              "created_at": "2025-12-01T00:00:00.000Z",
                              "updated_at": "2026-06-01T00:00:00.000Z",
                              "availability": [
                                "connection_pages",
                                "lsk",
                                "lso",
                                "meandu",
                                "meandu_connect",
                                "shopify"
                              ],
                              "requirements": {
                                "products": [],
                                "categories": [
                                  {
                                    "id": 10,
                                    "name": "Coffee",
                                    "extended": []
                                  }
                                ]
                              },
                              "targets": {
                                "products": [
                                  {
                                    "id": 501,
                                    "name": "Flat white",
                                    "extended": [
                                      {
                                        "source": "lightspeed-k",
                                        "external_id": "456",
                                        "json_data": {},
                                        "updated_at": null
                                      }
                                    ]
                                  }
                                ],
                                "categories": []
                              }
                            }
                          }
                        ],
                        "surface": "lsk"
                      }
                    }
                  },
                  "lskVenueUnlockIncomplete": {
                    "summary": "LSK — Collect Across Venues stamps incomplete",
                    "value": {
                      "message": "Promotions evaluated successfully",
                      "response": {
                        "status": "ok",
                        "basket_id": "pos-order-7f3a2c",
                        "membership": {
                          "id": "104823",
                          "points_balance": 120,
                          "rewards": [
                            {
                              "id": "901",
                              "type": "Offer",
                              "name": "Visit three venues",
                              "description": "Collect Across Venues passport",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "status": "UNAVAILABLE_TO_REDEEM",
                              "visibility_state": "unlocked",
                              "application_scope": "order",
                              "cart_application_method": "MANUAL_APPLY",
                              "venue_progress": {
                                "eligible": false,
                                "unlocked": false,
                                "collected_location_ids": [
                                  10
                                ],
                                "collected_brand_ids": [],
                                "required_count": 3,
                                "stamp_set_size": 3,
                                "summary": "1 of 3 venues · 2 to go.",
                                "stamps": [
                                  {
                                    "id": 10,
                                    "label": "Bondi",
                                    "earned": true,
                                    "kind": "location"
                                  },
                                  {
                                    "id": 11,
                                    "label": "Surry Hills",
                                    "earned": false,
                                    "kind": "location"
                                  },
                                  {
                                    "id": 12,
                                    "label": "Newtown",
                                    "earned": false,
                                    "kind": "location"
                                  }
                                ]
                              },
                              "non_redeemable_cause": {
                                "code": "VENUE_UNLOCK_NOT_ELIGIBLE",
                                "message": "1 of 3 venues · 2 to go."
                              },
                              "discount_application": {
                                "method": "negative_product_line",
                                "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."
                              },
                              "promotion": {
                                "id": 901,
                                "brand_id": 42,
                                "name": "Free regular coffee",
                                "description": "One regular coffee on us",
                                "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                                "discount_type": "free_item",
                                "applies_to": "in_store",
                                "channel": "pos",
                                "location_id": null,
                                "valid_from": "2026-01-01T00:00:00.000Z",
                                "valid_until": "2026-12-31T23:59:59.000Z",
                                "created_at": "2025-12-01T00:00:00.000Z",
                                "updated_at": "2026-06-01T00:00:00.000Z",
                                "availability": [
                                  "connection_pages",
                                  "lsk",
                                  "lso",
                                  "meandu",
                                  "meandu_connect",
                                  "shopify"
                                ],
                                "requirements": {
                                  "products": [],
                                  "categories": [
                                    {
                                      "id": 10,
                                      "name": "Coffee",
                                      "extended": []
                                    }
                                  ]
                                },
                                "targets": {
                                  "products": [
                                    {
                                      "id": 501,
                                      "name": "Flat white",
                                      "extended": [
                                        {
                                          "source": "lightspeed-k",
                                          "external_id": "456",
                                          "json_data": {},
                                          "updated_at": null
                                        }
                                      ]
                                    }
                                  ],
                                  "categories": []
                                }
                              }
                            }
                          ]
                        },
                        "rewards": [
                          {
                            "id": "901",
                            "type": "Offer",
                            "name": "Visit three venues",
                            "description": "Collect Across Venues passport",
                            "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                            "status": "UNAVAILABLE_TO_REDEEM",
                            "visibility_state": "unlocked",
                            "application_scope": "order",
                            "cart_application_method": "MANUAL_APPLY",
                            "venue_progress": {
                              "eligible": false,
                              "unlocked": false,
                              "collected_location_ids": [
                                10
                              ],
                              "collected_brand_ids": [],
                              "required_count": 3,
                              "stamp_set_size": 3,
                              "summary": "1 of 3 venues · 2 to go.",
                              "stamps": [
                                {
                                  "id": 10,
                                  "label": "Bondi",
                                  "earned": true,
                                  "kind": "location"
                                },
                                {
                                  "id": 11,
                                  "label": "Surry Hills",
                                  "earned": false,
                                  "kind": "location"
                                },
                                {
                                  "id": 12,
                                  "label": "Newtown",
                                  "earned": false,
                                  "kind": "location"
                                }
                              ]
                            },
                            "non_redeemable_cause": {
                              "code": "VENUE_UNLOCK_NOT_ELIGIBLE",
                              "message": "1 of 3 venues · 2 to go."
                            },
                            "discount_application": {
                              "method": "negative_product_line",
                              "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."
                            },
                            "promotion": {
                              "id": 901,
                              "brand_id": 42,
                              "name": "Free regular coffee",
                              "description": "One regular coffee on us",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "discount_type": "free_item",
                              "applies_to": "in_store",
                              "channel": "pos",
                              "location_id": null,
                              "valid_from": "2026-01-01T00:00:00.000Z",
                              "valid_until": "2026-12-31T23:59:59.000Z",
                              "created_at": "2025-12-01T00:00:00.000Z",
                              "updated_at": "2026-06-01T00:00:00.000Z",
                              "availability": [
                                "connection_pages",
                                "lsk",
                                "lso",
                                "meandu",
                                "meandu_connect",
                                "shopify"
                              ],
                              "requirements": {
                                "products": [],
                                "categories": [
                                  {
                                    "id": 10,
                                    "name": "Coffee",
                                    "extended": []
                                  }
                                ]
                              },
                              "targets": {
                                "products": [
                                  {
                                    "id": 501,
                                    "name": "Flat white",
                                    "extended": [
                                      {
                                        "source": "lightspeed-k",
                                        "external_id": "456",
                                        "json_data": {},
                                        "updated_at": null
                                      }
                                    ]
                                  }
                                ],
                                "categories": []
                              }
                            }
                          }
                        ],
                        "surface": "lsk"
                      }
                    }
                  },
                  "lskClaimRequired": {
                    "summary": "LSK — must claim on the join page",
                    "value": {
                      "message": "Promotions evaluated successfully",
                      "response": {
                        "status": "ok",
                        "basket_id": "pos-order-7f3a2c",
                        "membership": {
                          "id": "104823",
                          "points_balance": 120,
                          "rewards": [
                            {
                              "id": "901",
                              "type": "Offer",
                              "name": "Free regular coffee",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "status": "UNAVAILABLE_TO_REDEEM",
                              "visibility_state": "unlocked",
                              "application_scope": "line",
                              "cart_application_method": "MANUAL_APPLY",
                              "non_redeemable_cause": {
                                "code": "CLAIM_REQUIRED",
                                "message": "This offer must be claimed on the join page before it can be redeemed."
                              },
                              "discount_application": {
                                "method": "negative_product_line",
                                "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."
                              },
                              "promotion": {
                                "id": 901,
                                "brand_id": 42,
                                "name": "Free regular coffee",
                                "description": "One regular coffee on us",
                                "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                                "discount_type": "free_item",
                                "applies_to": "in_store",
                                "channel": "pos",
                                "location_id": null,
                                "valid_from": "2026-01-01T00:00:00.000Z",
                                "valid_until": "2026-12-31T23:59:59.000Z",
                                "created_at": "2025-12-01T00:00:00.000Z",
                                "updated_at": "2026-06-01T00:00:00.000Z",
                                "availability": [
                                  "connection_pages",
                                  "lsk",
                                  "lso",
                                  "meandu",
                                  "meandu_connect",
                                  "shopify"
                                ],
                                "requirements": {
                                  "products": [],
                                  "categories": [
                                    {
                                      "id": 10,
                                      "name": "Coffee",
                                      "extended": []
                                    }
                                  ]
                                },
                                "targets": {
                                  "products": [
                                    {
                                      "id": 501,
                                      "name": "Flat white",
                                      "extended": [
                                        {
                                          "source": "lightspeed-k",
                                          "external_id": "456",
                                          "json_data": {},
                                          "updated_at": null
                                        }
                                      ]
                                    }
                                  ],
                                  "categories": []
                                }
                              }
                            }
                          ]
                        },
                        "rewards": [
                          {
                            "id": "901",
                            "type": "Offer",
                            "name": "Free regular coffee",
                            "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                            "status": "UNAVAILABLE_TO_REDEEM",
                            "visibility_state": "unlocked",
                            "application_scope": "line",
                            "cart_application_method": "MANUAL_APPLY",
                            "non_redeemable_cause": {
                              "code": "CLAIM_REQUIRED",
                              "message": "This offer must be claimed on the join page before it can be redeemed."
                            },
                            "discount_application": {
                              "method": "negative_product_line",
                              "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."
                            },
                            "promotion": {
                              "id": 901,
                              "brand_id": 42,
                              "name": "Free regular coffee",
                              "description": "One regular coffee on us",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "discount_type": "free_item",
                              "applies_to": "in_store",
                              "channel": "pos",
                              "location_id": null,
                              "valid_from": "2026-01-01T00:00:00.000Z",
                              "valid_until": "2026-12-31T23:59:59.000Z",
                              "created_at": "2025-12-01T00:00:00.000Z",
                              "updated_at": "2026-06-01T00:00:00.000Z",
                              "availability": [
                                "connection_pages",
                                "lsk",
                                "lso",
                                "meandu",
                                "meandu_connect",
                                "shopify"
                              ],
                              "requirements": {
                                "products": [],
                                "categories": [
                                  {
                                    "id": 10,
                                    "name": "Coffee",
                                    "extended": []
                                  }
                                ]
                              },
                              "targets": {
                                "products": [
                                  {
                                    "id": 501,
                                    "name": "Flat white",
                                    "extended": [
                                      {
                                        "source": "lightspeed-k",
                                        "external_id": "456",
                                        "json_data": {},
                                        "updated_at": null
                                      }
                                    ]
                                  }
                                ],
                                "categories": []
                              }
                            }
                          }
                        ],
                        "surface": "lsk"
                      }
                    }
                  },
                  "lskTotalRedemptionLimit": {
                    "summary": "LSK — first-X redemption cap exhausted",
                    "value": {
                      "message": "Promotions evaluated successfully",
                      "response": {
                        "status": "ok",
                        "basket_id": "pos-order-7f3a2c",
                        "membership": {
                          "id": "104823",
                          "points_balance": 120,
                          "rewards": [
                            {
                              "id": "901",
                              "type": "Offer",
                              "name": "Free regular coffee",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "status": "UNAVAILABLE_TO_REDEEM",
                              "visibility_state": "unlocked",
                              "application_scope": "line",
                              "cart_application_method": "MANUAL_APPLY",
                              "non_redeemable_cause": {
                                "code": "TOTAL_REDEMPTION_LIMIT_REACHED",
                                "message": "Sorry, this offer is no longer available."
                              },
                              "discount_application": {
                                "method": "negative_product_line",
                                "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."
                              },
                              "promotion": {
                                "id": 901,
                                "brand_id": 42,
                                "name": "Free regular coffee",
                                "description": "One regular coffee on us",
                                "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                                "discount_type": "free_item",
                                "applies_to": "in_store",
                                "channel": "pos",
                                "location_id": null,
                                "valid_from": "2026-01-01T00:00:00.000Z",
                                "valid_until": "2026-12-31T23:59:59.000Z",
                                "created_at": "2025-12-01T00:00:00.000Z",
                                "updated_at": "2026-06-01T00:00:00.000Z",
                                "availability": [
                                  "connection_pages",
                                  "lsk",
                                  "lso",
                                  "meandu",
                                  "meandu_connect",
                                  "shopify"
                                ],
                                "requirements": {
                                  "products": [],
                                  "categories": [
                                    {
                                      "id": 10,
                                      "name": "Coffee",
                                      "extended": []
                                    }
                                  ]
                                },
                                "targets": {
                                  "products": [
                                    {
                                      "id": 501,
                                      "name": "Flat white",
                                      "extended": [
                                        {
                                          "source": "lightspeed-k",
                                          "external_id": "456",
                                          "json_data": {},
                                          "updated_at": null
                                        }
                                      ]
                                    }
                                  ],
                                  "categories": []
                                }
                              }
                            }
                          ]
                        },
                        "rewards": [
                          {
                            "id": "901",
                            "type": "Offer",
                            "name": "Free regular coffee",
                            "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                            "status": "UNAVAILABLE_TO_REDEEM",
                            "visibility_state": "unlocked",
                            "application_scope": "line",
                            "cart_application_method": "MANUAL_APPLY",
                            "non_redeemable_cause": {
                              "code": "TOTAL_REDEMPTION_LIMIT_REACHED",
                              "message": "Sorry, this offer is no longer available."
                            },
                            "discount_application": {
                              "method": "negative_product_line",
                              "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."
                            },
                            "promotion": {
                              "id": 901,
                              "brand_id": 42,
                              "name": "Free regular coffee",
                              "description": "One regular coffee on us",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "discount_type": "free_item",
                              "applies_to": "in_store",
                              "channel": "pos",
                              "location_id": null,
                              "valid_from": "2026-01-01T00:00:00.000Z",
                              "valid_until": "2026-12-31T23:59:59.000Z",
                              "created_at": "2025-12-01T00:00:00.000Z",
                              "updated_at": "2026-06-01T00:00:00.000Z",
                              "availability": [
                                "connection_pages",
                                "lsk",
                                "lso",
                                "meandu",
                                "meandu_connect",
                                "shopify"
                              ],
                              "requirements": {
                                "products": [],
                                "categories": [
                                  {
                                    "id": 10,
                                    "name": "Coffee",
                                    "extended": []
                                  }
                                ]
                              },
                              "targets": {
                                "products": [
                                  {
                                    "id": 501,
                                    "name": "Flat white",
                                    "extended": [
                                      {
                                        "source": "lightspeed-k",
                                        "external_id": "456",
                                        "json_data": {},
                                        "updated_at": null
                                      }
                                    ]
                                  }
                                ],
                                "categories": []
                              }
                            }
                          }
                        ],
                        "surface": "lsk"
                      }
                    }
                  },
                  "lsoOrderDiscount": {
                    "summary": "LSO — order-level price variation",
                    "value": {
                      "message": "Promotions evaluated successfully",
                      "response": {
                        "status": "ok",
                        "basket_id": "lso-order-991",
                        "membership": {
                          "id": "104823",
                          "points_balance": 120,
                          "rewards": [
                            {
                              "id": "940",
                              "type": "Offer",
                              "name": "$10 off order",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "status": "AVAILABLE_TO_REDEEM",
                              "visibility_state": "unlocked",
                              "application_scope": "order",
                              "cart_application_method": "MANUAL_APPLY",
                              "discount_amount_in_cents": 1000,
                              "discount_application": {
                                "method": "order_price_variation",
                                "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."
                              }
                            }
                          ]
                        },
                        "rewards": [
                          {
                            "id": "940",
                            "type": "Offer",
                            "name": "$10 off order",
                            "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                            "status": "AVAILABLE_TO_REDEEM",
                            "visibility_state": "unlocked",
                            "application_scope": "order",
                            "cart_application_method": "MANUAL_APPLY",
                            "discount_amount_in_cents": 1000,
                            "discount_application": {
                              "method": "order_price_variation",
                              "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."
                            }
                          }
                        ],
                        "surface": "lso"
                      }
                    }
                  },
                  "lsoLineDiscount": {
                    "summary": "LSO — line-level price variation",
                    "value": {
                      "message": "Promotions evaluated successfully",
                      "response": {
                        "status": "ok",
                        "basket_id": "lso-order-991",
                        "membership": {
                          "id": "104823",
                          "points_balance": 120,
                          "rewards": [
                            {
                              "id": "901",
                              "type": "Offer",
                              "name": "Free regular coffee",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "status": "AVAILABLE_TO_REDEEM",
                              "visibility_state": "unlocked",
                              "application_scope": "line",
                              "cart_application_method": "MANUAL_APPLY",
                              "discount_amount_in_cents": 550,
                              "order_discount": {
                                "amount_in_cents": 550
                              },
                              "line_discounts": [
                                {
                                  "pos_id": "456",
                                  "amount_in_cents": 550,
                                  "quantity": 1
                                }
                              ],
                              "discount_application": {
                                "method": "line_price_variation",
                                "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."
                              },
                              "promotion": {
                                "id": 901,
                                "brand_id": 42,
                                "name": "Free regular coffee",
                                "description": "One regular coffee on us",
                                "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                                "discount_type": "free_item",
                                "applies_to": "in_store",
                                "channel": "pos",
                                "location_id": null,
                                "valid_from": "2026-01-01T00:00:00.000Z",
                                "valid_until": "2026-12-31T23:59:59.000Z",
                                "created_at": "2025-12-01T00:00:00.000Z",
                                "updated_at": "2026-06-01T00:00:00.000Z",
                                "availability": [
                                  "connection_pages",
                                  "lsk",
                                  "lso",
                                  "meandu",
                                  "meandu_connect",
                                  "shopify"
                                ],
                                "requirements": {
                                  "products": [],
                                  "categories": [
                                    {
                                      "id": 10,
                                      "name": "Coffee",
                                      "extended": []
                                    }
                                  ]
                                },
                                "targets": {
                                  "products": [
                                    {
                                      "id": 501,
                                      "name": "Flat white",
                                      "extended": [
                                        {
                                          "source": "lightspeed-k",
                                          "external_id": "456",
                                          "json_data": {},
                                          "updated_at": null
                                        }
                                      ]
                                    }
                                  ],
                                  "categories": []
                                }
                              }
                            }
                          ]
                        },
                        "rewards": [
                          {
                            "id": "901",
                            "type": "Offer",
                            "name": "Free regular coffee",
                            "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                            "status": "AVAILABLE_TO_REDEEM",
                            "visibility_state": "unlocked",
                            "application_scope": "line",
                            "cart_application_method": "MANUAL_APPLY",
                            "discount_amount_in_cents": 550,
                            "order_discount": {
                              "amount_in_cents": 550
                            },
                            "line_discounts": [
                              {
                                "pos_id": "456",
                                "amount_in_cents": 550,
                                "quantity": 1
                              }
                            ],
                            "discount_application": {
                              "method": "line_price_variation",
                              "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."
                            },
                            "promotion": {
                              "id": 901,
                              "brand_id": 42,
                              "name": "Free regular coffee",
                              "description": "One regular coffee on us",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "discount_type": "free_item",
                              "applies_to": "in_store",
                              "channel": "pos",
                              "location_id": null,
                              "valid_from": "2026-01-01T00:00:00.000Z",
                              "valid_until": "2026-12-31T23:59:59.000Z",
                              "created_at": "2025-12-01T00:00:00.000Z",
                              "updated_at": "2026-06-01T00:00:00.000Z",
                              "availability": [
                                "connection_pages",
                                "lsk",
                                "lso",
                                "meandu",
                                "meandu_connect",
                                "shopify"
                              ],
                              "requirements": {
                                "products": [],
                                "categories": [
                                  {
                                    "id": 10,
                                    "name": "Coffee",
                                    "extended": []
                                  }
                                ]
                              },
                              "targets": {
                                "products": [
                                  {
                                    "id": 501,
                                    "name": "Flat white",
                                    "extended": [
                                      {
                                        "source": "lightspeed-k",
                                        "external_id": "456",
                                        "json_data": {},
                                        "updated_at": null
                                      }
                                    ]
                                  }
                                ],
                                "categories": []
                              }
                            }
                          }
                        ],
                        "surface": "lso"
                      }
                    }
                  },
                  "meanduAvailable": {
                    "summary": "meandu — available without discount amount",
                    "value": {
                      "message": "Promotions evaluated successfully",
                      "response": {
                        "status": "ok",
                        "membership": {
                          "id": "104823",
                          "points_balance": 120,
                          "rewards": [
                            {
                              "id": "901",
                              "type": "Offer",
                              "name": "Free regular coffee",
                              "description": "One regular coffee on us",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "status": "AVAILABLE_TO_REDEEM",
                              "cart_application_method": "MANUAL_APPLY",
                              "discount_application": {
                                "method": "ordering_platform",
                                "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."
                              },
                              "promotion": {
                                "id": 901,
                                "brand_id": 42,
                                "name": "Free regular coffee",
                                "description": "One regular coffee on us",
                                "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                                "discount_type": "free_item",
                                "applies_to": "in_store",
                                "channel": "pos",
                                "location_id": null,
                                "valid_from": "2026-01-01T00:00:00.000Z",
                                "valid_until": "2026-12-31T23:59:59.000Z",
                                "created_at": "2025-12-01T00:00:00.000Z",
                                "updated_at": "2026-06-01T00:00:00.000Z",
                                "availability": [
                                  "connection_pages",
                                  "lsk",
                                  "lso",
                                  "meandu",
                                  "meandu_connect",
                                  "shopify"
                                ],
                                "requirements": {
                                  "products": [],
                                  "categories": [
                                    {
                                      "id": 10,
                                      "name": "Coffee",
                                      "extended": []
                                    }
                                  ]
                                },
                                "targets": {
                                  "products": [
                                    {
                                      "id": 501,
                                      "name": "Flat white",
                                      "extended": [
                                        {
                                          "source": "lightspeed-k",
                                          "external_id": "456",
                                          "json_data": {},
                                          "updated_at": null
                                        }
                                      ]
                                    }
                                  ],
                                  "categories": []
                                }
                              }
                            }
                          ]
                        },
                        "rewards": [
                          {
                            "id": "901",
                            "type": "Offer",
                            "name": "Free regular coffee",
                            "description": "One regular coffee on us",
                            "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                            "status": "AVAILABLE_TO_REDEEM",
                            "cart_application_method": "MANUAL_APPLY",
                            "discount_application": {
                              "method": "ordering_platform",
                              "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."
                            },
                            "promotion": {
                              "id": 901,
                              "brand_id": 42,
                              "name": "Free regular coffee",
                              "description": "One regular coffee on us",
                              "image_url": "https://assets.myne.network/images/demo/free-coffee.png",
                              "discount_type": "free_item",
                              "applies_to": "in_store",
                              "channel": "pos",
                              "location_id": null,
                              "valid_from": "2026-01-01T00:00:00.000Z",
                              "valid_until": "2026-12-31T23:59:59.000Z",
                              "created_at": "2025-12-01T00:00:00.000Z",
                              "updated_at": "2026-06-01T00:00:00.000Z",
                              "availability": [
                                "connection_pages",
                                "lsk",
                                "lso",
                                "meandu",
                                "meandu_connect",
                                "shopify"
                              ],
                              "requirements": {
                                "products": [],
                                "categories": [
                                  {
                                    "id": 10,
                                    "name": "Coffee",
                                    "extended": []
                                  }
                                ]
                              },
                              "targets": {
                                "products": [
                                  {
                                    "id": 501,
                                    "name": "Flat white",
                                    "extended": [
                                      {
                                        "source": "lightspeed-k",
                                        "external_id": "456",
                                        "json_data": {},
                                        "updated_at": null
                                      }
                                    ]
                                  }
                                ],
                                "categories": []
                              }
                            }
                          }
                        ],
                        "surface": "meandu"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body (missing customer/order lines, invalid order lines in cart.items, source passed instead of surface, or invalid surface).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Do not pass source. Use surface for the rewards channel."
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Customer not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          },
          "502": {
            "description": "Promotions engine unavailable or rejected the request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Promotions engine unavailable. Please try again shortly."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "cart"
                ],
                "description": "Check which rewards fit this open order. Send order lines in cart.items. Each metadata.pos_id must be the product id for this surface (the register item id when surface=lsk). Do not use your Custom API External source for order lines. Send basket_id so later calls use the same order; myne sends it back unchanged. This call does not lock a reward. Send surface for the app on this open order (lsk or lso). Do not send integration_type. Recording a promotion redemption does not create the paid purchase. That purchase is created when the POS or ordering system sends the sale.",
                "properties": {
                  "customer_id": {
                    "type": "integer",
                    "description": "myne customer id (use this or external_id)."
                  },
                  "external_id": {
                    "type": "string",
                    "description": "Your partner/PMS/CRM id previously synced under your credential External source (for example a CRM id). Resolved when customer_id is omitted (or via customer_source when set). Not email/phone matching. Not used for order-line product matching. Do not pass source on this endpoint."
                  },
                  "customer_source": {
                    "type": "string",
                    "description": "Optional External source for customer external_id lookup only. Defaults to your credential source. Never used for order-line pos_id matching."
                  },
                  "basket_id": {
                    "type": "string",
                    "description": "Your order id (POS order id or a unique id you generate). myne sends it back unchanged. This does not lock a reward. Reuse the same value on Apply a promotion."
                  },
                  "cart": {
                    "type": "object",
                    "required": [
                      "items"
                    ],
                    "properties": {
                      "items": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "required": [
                            "metadata",
                            "amount_in_cents",
                            "quantity"
                          ],
                          "properties": {
                            "metadata": {
                              "type": "object",
                              "required": [
                                "pos_id"
                              ],
                              "properties": {
                                "pos_id": {
                                  "type": "string",
                                  "description": "Product id in the requested surface catalog (register item id for surface=lsk, ordering product id for surface=lso, and so on)."
                                }
                              }
                            },
                            "amount_in_cents": {
                              "type": "integer",
                              "description": "Pre-discount unit price including tax in cents (not the line total). Line value is amount_in_cents × quantity."
                            },
                            "quantity": {
                              "type": "number",
                              "exclusiveMinimum": 0
                            }
                          }
                        }
                      }
                    }
                  },
                  "venue_id": {
                    "type": "string",
                    "description": "Venue id for location-scoped rules on ordering surfaces. Do not use this instead of business_location_id for register (surface=lsk)."
                  },
                  "business_location_id": {
                    "type": "string",
                    "description": "POS business location id (location.external_location_id in myne). Prefer for surface=lsk (register)."
                  },
                  "surface": {
                    "type": "string",
                    "enum": [
                      "connection_pages",
                      "meandu",
                      "lso",
                      "lsk",
                      "shopify"
                    ],
                    "description": "Channel for this open order. Send lsk (register) or lso (ordering). Do not send integration_type on this basket call. lsk/meandu/lso are forwarded to the rewards engine as source."
                  },
                  "order_type": {
                    "type": "string",
                    "enum": [
                      "pickup",
                      "delivery",
                      "dine_in"
                    ],
                    "description": "Optional fulfillment type for this open order (pickup, delivery, or dine_in). Omit it and unrestricted promotions still evaluate. A promotion restricted to order types is not eligible when this is omitted or does not match (ORDER_TYPE_REQUIRED or ORDER_TYPE_MISMATCH on that reward, not a failed call). Any other value returns 400. Bopple maps their types to these three: ASAP / Scheduled / Catering Pickup → pickup; ASAP / Scheduled / Catering Delivery → delivery; Dine in / Room Service / Order to seat → dine_in. Web, App, and Kiosk stay as surface, not order_type."
                  }
                }
              },
              "examples": {
                "lsk": {
                  "summary": "Register (surface=lsk) — coffee line + basket_id",
                  "value": {
                    "customer_id": 104823,
                    "surface": "lsk",
                    "basket_id": "pos-order-7f3a2c",
                    "business_location_id": "loc-sydney-01",
                    "cart": {
                      "items": [
                        {
                          "metadata": {
                            "pos_id": "456"
                          },
                          "amount_in_cents": 550,
                          "quantity": 1
                        }
                      ]
                    }
                  }
                },
                "lso": {
                  "summary": "Ordering (surface=lso) — order-level discount cart",
                  "value": {
                    "customer_id": 104823,
                    "surface": "lso",
                    "basket_id": "lso-order-991",
                    "venue_id": "venue-42",
                    "cart": {
                      "items": [
                        {
                          "metadata": {
                            "pos_id": "1001"
                          },
                          "amount_in_cents": 4500,
                          "quantity": 1
                        }
                      ]
                    }
                  }
                },
                "meandu": {
                  "summary": "Ordering (surface=meandu) — eligibility",
                  "value": {
                    "customer_id": 104823,
                    "surface": "meandu",
                    "venue_id": "meandu-venue-1",
                    "cart": {
                      "items": [
                        {
                          "metadata": {
                            "pos_id": "sku-coffee-1"
                          },
                          "amount_in_cents": 550,
                          "quantity": 1
                        }
                      ]
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/promotions/apply": {
      "post": {
        "operationId": "applyPromotions",
        "summary": "Apply a promotion (lock the reward on this open order)",
        "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.",
        "tags": [
          "Promotions"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "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.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "status",
                        "basket_id",
                        "promotion_id",
                        "surface"
                      ],
                      "properties": {
                        "status": {
                          "type": "string"
                        },
                        "basket_id": {
                          "oneOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "integer"
                            }
                          ],
                          "description": "Value to keep using on this open order (your basket_id when supplied, else myne selection id). Reuse it on Complete promotions, or mark the order with prefix MYNE if paid sales already go to myne."
                        },
                        "selection_id": {
                          "type": "integer",
                          "description": "myne internal customer_redeem_promotion row id for this selection."
                        },
                        "promotion_id": {
                          "type": "integer",
                          "description": "Promotion that was selected against the open order."
                        },
                        "surface": {
                          "type": "string"
                        },
                        "external_reference_prefix": {
                          "type": "string",
                          "description": "Present when surface=lsk (always MYNE). Use as the order-mark prefix with basket_id as the value, only if paid sales already go to myne."
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Promotion applied to open order successfully",
                  "response": {
                    "status": "ok",
                    "basket_id": 9001,
                    "selection_id": 9001,
                    "promotion_id": 901,
                    "surface": "lsk",
                    "external_reference_prefix": "MYNE"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body, missing surface, promotion not on this surface or location, or apply rejected (e.g. points inactive).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "surface must be lsk or lso to apply a promotion (open order + basket_id ref)"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "403": {
            "description": "Offer cannot be applied. `code` is `CLAIM_REQUIRED` (must claim on the join page) or `TOTAL_REDEMPTION_LIMIT_REACHED` (first-X redemption cap is exhausted).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "`CLAIM_REQUIRED` when the offer must be claimed on the join page. `TOTAL_REDEMPTION_LIMIT_REACHED` when the first-X redemption cap is exhausted. Register and ordering POS keep `MAXIMUM_USES_REACHED` for that same total cap."
                    }
                  }
                },
                "examples": {
                  "claimRequired": {
                    "summary": "Must claim on the join page",
                    "value": {
                      "message": "This offer must be claimed on the join page before it can be redeemed.",
                      "code": "CLAIM_REQUIRED"
                    }
                  },
                  "totalCap": {
                    "summary": "First-X redemption cap exhausted",
                    "value": {
                      "message": "Sorry, this offer is no longer available.",
                      "code": "TOTAL_REDEMPTION_LIMIT_REACHED"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Customer or promotion not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer or promotion not found"
                }
              }
            }
          },
          "409": {
            "description": "Insufficient points for this promotion.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Insufficient points to redeem this promotion"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          },
          "502": {
            "description": "Promotions engine unavailable or rejected the request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Promotions engine unavailable. Please try again shortly."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "promotion_id",
                  "surface"
                ],
                "description": "Apply a promotion (step 3): lock this promotion_id on the open order. It does not take money off the ticket and it does not record the redemption. surface must be lsk (register) or lso (ordering). Do not send integration_type. Send customer_id or external_id. Send the same basket_id as evaluate so later calls use the same order. myne sends that id back and also returns selection_id. If you omit basket_id, myne uses its internal selection id. You may apply more than one promotion on the same open order (reuse the same basket_id) when stacking is allowed. Then either mark the order with prefix MYNE and value basket_id if paid sales already go to myne, or send Complete promotions with that same basket_id when the customer has paid. Do not write the promotion id instead of basket_id. Recording a promotion redemption does not create the paid purchase. That purchase is created when the POS or ordering system sends the sale.",
                "properties": {
                  "customer_id": {
                    "type": "integer",
                    "description": "myne customer id (use this or external_id)."
                  },
                  "external_id": {
                    "type": "string",
                    "description": "Your partner/PMS/CRM id under your credential External source (for example a CRM id). Resolved when customer_id is omitted (or via customer_source when set)."
                  },
                  "customer_source": {
                    "type": "string",
                    "description": "Optional External source for customer external_id lookup only. Defaults to your credential source."
                  },
                  "promotion_id": {
                    "type": "integer",
                    "description": "Promotion to apply (lock) on the open order (from evaluate or catalog)."
                  },
                  "basket_id": {
                    "type": "string",
                    "description": "Your order id (POS order id or a unique id you generate). Field name remains basket_id. Reuse the same value on every apply for this open order, then on Complete promotions or on the MYNE order mark."
                  },
                  "surface": {
                    "type": "string",
                    "enum": [
                      "lsk",
                      "lso"
                    ],
                    "description": "Where this open order is sold. Required. Send lsk (register) or lso (ordering) — the same value as evaluate. Apply can lock a reward only on those two surfaces, not join page, meandu, or shopify. When surface=lsk, the response includes MYNE mark details if paid sales already go to myne."
                  },
                  "location_id": {
                    "type": "integer",
                    "description": "myne location id when the offer depends on place."
                  },
                  "venue_id": {
                    "type": "string",
                    "description": "Venue id for location-scoped rules on ordering. Do not use this instead of business_location_id for register (surface=lsk)."
                  },
                  "business_location_id": {
                    "type": "string",
                    "description": "POS business location id (location.external_location_id in myne). Prefer for surface=lsk (register)."
                  },
                  "order_type": {
                    "type": "string",
                    "enum": [
                      "pickup",
                      "delivery",
                      "dine_in"
                    ],
                    "description": "Optional fulfillment type for this open order (pickup, delivery, or dine_in). Omit it and unrestricted promotions still evaluate. A promotion restricted to order types is not eligible when this is omitted or does not match (ORDER_TYPE_REQUIRED or ORDER_TYPE_MISMATCH on that reward, not a failed call). Any other value returns 400. Bopple maps their types to these three: ASAP / Scheduled / Catering Pickup → pickup; ASAP / Scheduled / Catering Delivery → delivery; Dine in / Room Service / Order to seat → dine_in. Web, App, and Kiosk stay as surface, not order_type."
                  }
                }
              },
              "example": {
                "customer_id": 104823,
                "promotion_id": 901,
                "surface": "lsk",
                "basket_id": "pos-order-7f3a2c"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/promotions/complete": {
      "post": {
        "operationId": "completePromotions",
        "summary": "Record the redemption after the customer has paid",
        "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.",
        "tags": [
          "Promotions"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Locked rewards for this order were recorded as redeemed, or were already recorded. redeemed_count is 0 when nothing was still locked, including when the venue does not match the offer. A later POS or ordering sale for the same cart does not redeem again. Recording the redemption does not create the paid purchase.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "status",
                        "redeemed_count",
                        "promotion_ids",
                        "basket_id",
                        "surface"
                      ],
                      "properties": {
                        "status": {
                          "type": "string"
                        },
                        "redeemed_count": {
                          "type": "integer",
                          "description": "Number of locked rewards recorded as redeemed on this call. 0 if already recorded or nothing was locked."
                        },
                        "promotion_ids": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          },
                          "description": "Promotion ids redeemed on this call."
                        },
                        "basket_id": {
                          "type": "string",
                          "description": "The basket_id that was completed."
                        },
                        "surface": {
                          "type": "string"
                        }
                      }
                    }
                  }
                },
                "examples": {
                  "redeemed": {
                    "summary": "Customer paid — redemption recorded",
                    "value": {
                      "message": "Promotions completed for this order successfully",
                      "response": {
                        "status": "ok",
                        "redeemed_count": 1,
                        "promotion_ids": [
                          901
                        ],
                        "basket_id": "pos-order-7f3a2c",
                        "surface": "lsk"
                      }
                    }
                  },
                  "alreadyFinalised": {
                    "summary": "Already recorded — nothing locked",
                    "value": {
                      "message": "Promotions completed for this order successfully",
                      "response": {
                        "status": "ok",
                        "redeemed_count": 0,
                        "promotion_ids": [],
                        "basket_id": "pos-order-7f3a2c",
                        "surface": "lsk"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid body (missing basket_id or cart, source passed instead of surface, or surface not lsk/lso).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Do not pass source. Use surface for the rewards channel."
                }
              }
            }
          },
          "403": {
            "description": "Offer cannot be recorded. `code` is `CLAIM_REQUIRED` or `TOTAL_REDEMPTION_LIMIT_REACHED`. Complete does not create Claimed-group membership.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "code": {
                      "type": "string",
                      "description": "`CLAIM_REQUIRED` when the offer must be claimed on the join page. `TOTAL_REDEMPTION_LIMIT_REACHED` when the first-X redemption cap is exhausted. Register and ordering POS keep `MAXIMUM_USES_REACHED` for that same total cap."
                    }
                  }
                },
                "examples": {
                  "claimRequired": {
                    "summary": "Must claim on the join page",
                    "value": {
                      "message": "This offer must be claimed on the join page before it can be redeemed.",
                      "code": "CLAIM_REQUIRED"
                    }
                  },
                  "totalCap": {
                    "summary": "First-X redemption cap exhausted",
                    "value": {
                      "message": "Sorry, this offer is no longer available.",
                      "code": "TOTAL_REDEMPTION_LIMIT_REACHED"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Customer not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "409": {
            "description": "Insufficient points for a promotion on this basket.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Insufficient points to redeem this promotion"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          },
          "502": {
            "description": "Promotions engine unavailable or rejected the request.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Promotions engine unavailable. Please try again shortly."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "basket_id",
                  "surface",
                  "cart"
                ],
                "description": "Record the redemption when the customer has paid and paid sales from this POS do not already arrive in myne (Promotions step 5). We need the same basket_id from apply, the paid product lines (same line shape as evaluate — not the discount line), and surface lsk or lso. Do not send integration_type. One call records every locked reward on that order. Do not pass source. Optional discounts[] are for reporting only. Skip the MYNE order mark on this path. Recording the redemption does not create the paid purchase. That purchase is created when the POS or ordering system sends the sale.",
                "properties": {
                  "customer_id": {
                    "type": "integer",
                    "description": "myne customer id (use this or external_id)."
                  },
                  "external_id": {
                    "type": "string",
                    "description": "Your partner/PMS/CRM id under your credential External source. Resolved when customer_id is omitted (or via customer_source when set)."
                  },
                  "customer_source": {
                    "type": "string",
                    "description": "Optional External source for customer external_id lookup only. Defaults to your credential source."
                  },
                  "basket_id": {
                    "type": "string",
                    "description": "Required. Same open-order id returned from apply (or the partner id you sent on apply)."
                  },
                  "surface": {
                    "type": "string",
                    "enum": [
                      "lsk",
                      "lso"
                    ],
                    "description": "Where you sold. Required. Must be the same lsk (register) or lso (ordering) value used on evaluate and apply."
                  },
                  "cart": {
                    "type": "object",
                    "required": [
                      "items"
                    ],
                    "description": "Paid product lines sold on this order (not the discount line). Same line shape as evaluate. We need these lines to record the redemption.",
                    "properties": {
                      "items": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "required": [
                            "metadata",
                            "amount_in_cents",
                            "quantity"
                          ],
                          "properties": {
                            "metadata": {
                              "type": "object",
                              "required": [
                                "pos_id"
                              ],
                              "properties": {
                                "pos_id": {
                                  "type": "string",
                                  "description": "Product id in the requested surface catalog (same as evaluate)."
                                }
                              }
                            },
                            "amount_in_cents": {
                              "type": "integer",
                              "description": "Pre-discount unit price including tax in cents (not the line total). Line value is amount_in_cents × quantity."
                            },
                            "quantity": {
                              "type": "number",
                              "exclusiveMinimum": 0
                            }
                          }
                        }
                      }
                    }
                  },
                  "location_id": {
                    "type": "integer",
                    "description": "myne location id when the offer depends on place."
                  },
                  "venue_id": {
                    "type": "string",
                    "description": "Optional venue id for location-scoped rules."
                  },
                  "business_location_id": {
                    "type": "string",
                    "description": "POS business location id (location.external_location_id in myne)."
                  },
                  "order_type": {
                    "type": "string",
                    "enum": [
                      "pickup",
                      "delivery",
                      "dine_in"
                    ],
                    "description": "Optional fulfillment type for this open order (pickup, delivery, or dine_in). Omit it and unrestricted promotions still evaluate. A promotion restricted to order types is not eligible when this is omitted or does not match (ORDER_TYPE_REQUIRED or ORDER_TYPE_MISMATCH on that reward, not a failed call). Any other value returns 400. Bopple maps their types to these three: ASAP / Scheduled / Catering Pickup → pickup; ASAP / Scheduled / Catering Delivery → delivery; Dine in / Room Service / Order to seat → dine_in. Web, App, and Kiosk stay as surface, not order_type."
                  },
                  "discounts": {
                    "type": "array",
                    "description": "Optional amounts already taken off the paid order, for reporting only. Omit if you do not have amounts. Does not cause the redemption.",
                    "items": {
                      "type": "object",
                      "required": [
                        "promotion_id",
                        "amount_in_cents"
                      ],
                      "properties": {
                        "promotion_id": {
                          "type": "integer"
                        },
                        "amount_in_cents": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "customer_id": 104823,
                "basket_id": "pos-order-7f3a2c",
                "surface": "lsk",
                "location_id": 12,
                "cart": {
                  "items": [
                    {
                      "metadata": {
                        "pos_id": "ITEM-1"
                      },
                      "amount_in_cents": 550,
                      "quantity": 1
                    }
                  ]
                },
                "discounts": [
                  {
                    "promotion_id": 901,
                    "amount_in_cents": 550
                  }
                ]
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/tasks": {
      "get": {
        "operationId": "listTasks",
        "summary": "Browse tasks for a brand",
        "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.",
        "tags": [
          "Tasks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated open/recent tasks for the brand (partner-safe fields).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Tasks fetched successfully",
                  "response": {
                    "tasks": [
                      {
                        "id": 501,
                        "brand_id": 42,
                        "title": "Call venue about catering enquiry",
                        "description": "Follow up on Monday intake form.",
                        "due_date": "2026-07-15",
                        "due_time": "09:00:00",
                        "status": "todo",
                        "created_at": "2026-07-01T02:30:00.000Z",
                        "updated_at": "2026-07-10T04:15:00.000Z",
                        "status_changed_at": null,
                        "comment_count": 1,
                        "latest_comment": {
                          "created_at": "2026-07-10T04:15:00.000Z",
                          "preview": "Left voicemail",
                          "author_first_name": "Alex",
                          "author_last_name": "Nguyen"
                        },
                        "assignees": [
                          {
                            "user_id": 12,
                            "first_name": "Alex",
                            "last_name": "Nguyen"
                          }
                        ],
                        "businesses": [
                          {
                            "business_id": 801,
                            "name": "Harbour Catering Co"
                          }
                        ],
                        "customers": [
                          {
                            "customer_id": 104823,
                            "first_name": "Jordan",
                            "last_name": "Lee"
                          }
                        ],
                        "locations": [
                          {
                            "location_id": 3,
                            "name": "Main"
                          }
                        ],
                        "attachments": []
                      }
                    ],
                    "pagination": {
                      "page": 1,
                      "limit": 20,
                      "total_tasks": 1,
                      "total_pages": 1
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "query",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Free-text search on title and linked record names."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "todo",
                "in_progress",
                "complete",
                "parked"
              ]
            }
          },
          {
            "name": "statuses",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated status values."
          },
          {
            "name": "exclude_statuses",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated statuses to omit."
          },
          {
            "name": "assignee_user_ids",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated myne user IDs."
          },
          {
            "name": "assignee_filter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "unassigned"
              ]
            },
            "description": "Special assignee filter. Use unassigned for tasks with no assignee."
          },
          {
            "name": "due_date_filter",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "today",
                "tomorrow",
                "this_week",
                "past",
                "upcoming",
                "no_due_date"
              ]
            }
          },
          {
            "name": "business_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sort_by",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "title",
                "status",
                "due_date",
                "updated_at"
              ],
              "default": "updated_at"
            }
          },
          {
            "name": "sort_dir",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/tasks/{task_id}": {
      "get": {
        "operationId": "getTask",
        "summary": "Get a task by id",
        "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.",
        "tags": [
          "Tasks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Single task with linked records (partner-safe fields).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Task fetched successfully",
                  "response": {
                    "id": 501,
                    "brand_id": 42,
                    "title": "Call venue about catering enquiry",
                    "description": "Follow up on Monday intake form.",
                    "due_date": "2026-07-15",
                    "due_time": "09:00:00",
                    "status": "todo",
                    "created_at": "2026-07-01T02:30:00.000Z",
                    "updated_at": "2026-07-10T04:15:00.000Z",
                    "status_changed_at": null,
                    "comment_count": 1,
                    "latest_comment": {
                      "created_at": "2026-07-10T04:15:00.000Z",
                      "preview": "Left voicemail",
                      "author_first_name": "Alex",
                      "author_last_name": "Nguyen"
                    },
                    "assignees": [
                      {
                        "user_id": 12,
                        "first_name": "Alex",
                        "last_name": "Nguyen"
                      }
                    ],
                    "businesses": [
                      {
                        "business_id": 801,
                        "name": "Harbour Catering Co"
                      }
                    ],
                    "customers": [
                      {
                        "customer_id": 104823,
                        "first_name": "Jordan",
                        "last_name": "Lee"
                      }
                    ],
                    "locations": [
                      {
                        "location_id": 3,
                        "name": "Main"
                      }
                    ],
                    "attachments": [],
                    "updates": [
                      {
                        "id": 9001,
                        "task_id": 501,
                        "brand_id": 42,
                        "update_kind": "comment",
                        "status_from": null,
                        "status_to": null,
                        "comment_text": "Left voicemail",
                        "created_at": "2026-07-10T04:15:00.000Z",
                        "created_by_first_name": "Alex",
                        "created_by_last_name": "Nguyen"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid brand_id or task_id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid brand_id or task_id"
                }
              }
            }
          },
          "404": {
            "description": "Task not found.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Task not found"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "task_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "include_updates",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "default": true
            },
            "description": "When false, omit the updates array from the response."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/customers/{customer_id}/tasks": {
      "get": {
        "operationId": "listCustomerTasks",
        "summary": "List tasks for a customer",
        "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.",
        "tags": [
          "Tasks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated tasks linked to the customer.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Customer tasks fetched successfully",
                  "response": {
                    "tasks": [
                      {
                        "id": 501,
                        "brand_id": 42,
                        "title": "Call venue about catering enquiry",
                        "description": "Follow up on Monday intake form.",
                        "due_date": "2026-07-15",
                        "due_time": "09:00:00",
                        "status": "todo",
                        "created_at": "2026-07-01T02:30:00.000Z",
                        "updated_at": "2026-07-10T04:15:00.000Z",
                        "status_changed_at": null,
                        "comment_count": 1,
                        "latest_comment": {
                          "created_at": "2026-07-10T04:15:00.000Z",
                          "preview": "Left voicemail",
                          "author_first_name": "Alex",
                          "author_last_name": "Nguyen"
                        },
                        "assignees": [
                          {
                            "user_id": 12,
                            "first_name": "Alex",
                            "last_name": "Nguyen"
                          }
                        ],
                        "businesses": [
                          {
                            "business_id": 801,
                            "name": "Harbour Catering Co"
                          }
                        ],
                        "customers": [
                          {
                            "customer_id": 104823,
                            "first_name": "Jordan",
                            "last_name": "Lee"
                          }
                        ],
                        "locations": [
                          {
                            "location_id": 3,
                            "name": "Main"
                          }
                        ],
                        "attachments": []
                      }
                    ],
                    "pagination": {
                      "page": 1,
                      "limit": 20,
                      "total_tasks": 1,
                      "total_pages": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid customer_id. Invalid updated_since.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid customer_id"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "updated_since",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "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."
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/tasks/search": {
      "post": {
        "operationId": "searchTasks",
        "summary": "Search tasks with advanced filters",
        "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.",
        "tags": [
          "Tasks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated task search results (partner-safe fields).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                },
                "example": {
                  "message": "Tasks fetched successfully",
                  "response": {
                    "tasks": [
                      {
                        "id": 501,
                        "brand_id": 42,
                        "title": "Call venue about catering enquiry",
                        "description": "Follow up on Monday intake form.",
                        "due_date": "2026-07-15",
                        "due_time": "09:00:00",
                        "status": "todo",
                        "created_at": "2026-07-01T02:30:00.000Z",
                        "updated_at": "2026-07-10T04:15:00.000Z",
                        "status_changed_at": null,
                        "comment_count": 1,
                        "latest_comment": {
                          "created_at": "2026-07-10T04:15:00.000Z",
                          "preview": "Left voicemail",
                          "author_first_name": "Alex",
                          "author_last_name": "Nguyen"
                        },
                        "assignees": [
                          {
                            "user_id": 12,
                            "first_name": "Alex",
                            "last_name": "Nguyen"
                          }
                        ],
                        "businesses": [
                          {
                            "business_id": 801,
                            "name": "Harbour Catering Co"
                          }
                        ],
                        "customers": [
                          {
                            "customer_id": 104823,
                            "first_name": "Jordan",
                            "last_name": "Lee"
                          }
                        ],
                        "locations": [
                          {
                            "location_id": 3,
                            "name": "Main"
                          }
                        ],
                        "attachments": []
                      }
                    ],
                    "pagination": {
                      "page": 1,
                      "limit": 20,
                      "total_tasks": 1,
                      "total_pages": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body. Invalid updated_since.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid JSON body"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "query": {
                    "type": "string"
                  },
                  "search": {
                    "type": "string",
                    "description": "Alias for query."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "todo",
                      "in_progress",
                      "complete",
                      "parked"
                    ]
                  },
                  "statuses": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "exclude_statuses": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "assignee_user_ids": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    }
                  },
                  "assignee_filter": {
                    "type": "string",
                    "enum": [
                      "unassigned"
                    ],
                    "description": "Special assignee filter. Use unassigned for tasks with no assignee."
                  },
                  "due_date_filter": {
                    "type": "string",
                    "enum": [
                      "today",
                      "tomorrow",
                      "this_week",
                      "past",
                      "upcoming",
                      "no_due_date"
                    ]
                  },
                  "business_id": {
                    "type": "integer"
                  },
                  "customer_id": {
                    "type": "integer"
                  },
                  "sort_by": {
                    "type": "string",
                    "enum": [
                      "title",
                      "status",
                      "due_date",
                      "updated_at"
                    ]
                  },
                  "sort_dir": {
                    "type": "string",
                    "enum": [
                      "asc",
                      "desc"
                    ]
                  },
                  "page": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100,
                    "default": 20
                  },
                  "updated_since": {
                    "type": "string",
                    "format": "date-time",
                    "description": "ISO-8601 timestamp. When set, only rows with updated_at on or after this instant are returned (UTC, inclusive)."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/sync/batch": {
      "post": {
        "operationId": "syncBatch",
        "summary": "Batch upsert contacts, businesses, and links",
        "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.",
        "tags": [
          "Sync"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Batch upsert completed. Each result entry matches the item type.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message",
                    "response"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "response": {
                      "type": "object",
                      "required": [
                        "count",
                        "results"
                      ],
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "results": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "type",
                              "result"
                            ],
                            "properties": {
                              "type": {
                                "type": "string"
                              },
                              "result": {
                                "type": "object",
                                "additionalProperties": true
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "message": "Batch upsert complete",
                  "response": {
                    "count": 2,
                    "results": [
                      {
                        "type": "contact",
                        "result": {
                          "customer_id": 104823,
                          "external_id": "crm-001",
                          "created": true
                        }
                      },
                      {
                        "type": "business",
                        "result": {
                          "business_id": 801,
                          "external_id": "biz-001",
                          "is_new": false
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, validation error, unsupported item type, or source fields in item bodies.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Invalid JSON body"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Something went wrong. Please try again later."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "items"
                ],
                "properties": {
                  "items": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "object",
                      "required": [
                        "type",
                        "body"
                      ],
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "contact",
                            "business",
                            "business_contact",
                            "business_extended",
                            "contact_extended",
                            "business_parent_link",
                            "health_topic"
                          ]
                        },
                        "body": {
                          "type": "object",
                          "additionalProperties": true
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/crm-embed/token": {
      "post": {
        "operationId": "crmEmbedToken",
        "summary": "Mint a short-lived Bearer token for CRM embed",
        "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.",
        "tags": [
          "CRM Embed"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Short-lived Bearer token for CRM embed bootstrap.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "response"
                  ],
                  "properties": {
                    "response": {
                      "type": "object",
                      "required": [
                        "embed_token",
                        "expires_in",
                        "token_type"
                      ],
                      "properties": {
                        "embed_token": {
                          "type": "string"
                        },
                        "expires_in": {
                          "type": "integer",
                          "description": "Token lifetime in seconds (900 = 15 minutes)."
                        },
                        "token_type": {
                          "type": "string",
                          "enum": [
                            "Bearer"
                          ]
                        }
                      }
                    }
                  }
                },
                "example": {
                  "response": {
                    "embed_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJicmFuZF9pZCI6NDJ9.example",
                    "expires_in": 900,
                    "token_type": "Bearer"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body or entity_type.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "entity_type must be \"customer\" or \"business\""
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "403": {
            "description": "Path brand_id does not match credential scope.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Unauthorized"
                }
              }
            }
          },
          "404": {
            "description": "Customer or business not found for this brand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "Customer not found"
                }
              }
            }
          },
          "503": {
            "description": "CRM embed signing is not configured in this environment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "message"
                  ],
                  "properties": {
                    "message": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "message": "CRM embed is not configured."
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "entity_type",
                  "entity_id"
                ],
                "properties": {
                  "entity_type": {
                    "type": "string",
                    "enum": [
                      "customer",
                      "business"
                    ]
                  },
                  "entity_id": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/webhooks": {
      "get": {
        "operationId": "listOutboundWebhooks",
        "summary": "List outbound webhook subscriptions",
        "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.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Resource not found"
          },
          "500": {
            "description": "Server error"
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      },
      "post": {
        "operationId": "createOutboundWebhook",
        "summary": "Create an outbound webhook subscription",
        "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.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "201": {
            "description": "Created. `response.secret` is returned once. Signing secret is never returned again except on rotate."
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Resource not found"
          },
          "500": {
            "description": "Server error"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "topics"
                ],
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "topics": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "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"
                      ]
                    }
                  },
                  "ignore_own_writes": {
                    "type": "boolean",
                    "default": true
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/webhooks/topics": {
      "get": {
        "operationId": "listOutboundWebhookTopics",
        "summary": "List outbound webhook 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.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Resource not found"
          },
          "500": {
            "description": "Server error"
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/webhooks/{webhook_id}": {
      "get": {
        "operationId": "getOutboundWebhook",
        "summary": "Get an outbound webhook subscription",
        "description": "Load one destination this app owns. The signing secret is not included.\n\nSee glossary: Outbound webhook.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Resource not found"
          },
          "500": {
            "description": "Server error"
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      },
      "post": {
        "operationId": "updateOutboundWebhook",
        "summary": "Update an outbound webhook subscription",
        "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.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Resource not found"
          },
          "500": {
            "description": "Server error"
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/webhooks/{webhook_id}/rotate": {
      "post": {
        "operationId": "rotateOutboundWebhook",
        "summary": "Rotate the outbound webhook signing secret",
        "description": "Replace the HMAC signing secret. The new secret is returned **once** and cannot be shown again.\n\nSee glossary: Outbound webhook, Signature.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Resource not found"
          },
          "500": {
            "description": "Server error"
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/webhooks/{webhook_id}/remove": {
      "post": {
        "operationId": "removeOutboundWebhook",
        "summary": "Remove an outbound webhook subscription",
        "description": "Soft-delete this destination. Delivery stops. This app can only remove rows it created.\n\nSee glossary: Outbound webhook.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Resource not found"
          },
          "500": {
            "description": "Server error"
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/webhooks/{webhook_id}/ping": {
      "post": {
        "operationId": "pingOutboundWebhook",
        "summary": "Send a signed ping to the destination",
        "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.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Resource not found"
          },
          "500": {
            "description": "Server error"
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/brands/{brand_id}/webhooks/{webhook_id}/deliveries": {
      "get": {
        "operationId": "listOutboundWebhookDeliveries",
        "summary": "List recent outbound webhook 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.",
        "tags": [
          "Webhooks"
        ],
        "security": [
          {
            "basicAuth": []
          },
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Success"
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Missing or invalid Authorization (HTTP Basic or Bearer)."
          },
          "404": {
            "description": "Resource not found"
          },
          "500": {
            "description": "Server error"
          }
        },
        "parameters": [
          {
            "name": "brand_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/v1/token": {
      "post": {
        "operationId": "postConnectToken",
        "summary": "Get an access 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.",
        "tags": [
          "OAuth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "client_id",
                  "client_secret"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "client_credentials",
                      "authorization_code",
                      "refresh_token"
                    ]
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  },
                  "code": {
                    "type": "string"
                  },
                  "redirect_uri": {
                    "type": "string"
                  },
                  "refresh_token": {
                    "type": "string"
                  },
                  "code_verifier": {
                    "type": "string"
                  }
                }
              },
              "examples": {
                "client_credentials": {
                  "summary": "Creating brand",
                  "value": {
                    "grant_type": "client_credentials",
                    "client_id": "myne_api_abc",
                    "client_secret": "your-client-secret"
                  }
                },
                "authorization_code": {
                  "summary": "Other brand (after authorize)",
                  "value": {
                    "grant_type": "authorization_code",
                    "client_id": "myne_api_abc",
                    "client_secret": "your-client-secret",
                    "code": "replace-with-code",
                    "redirect_uri": "https://partner.example/oauth/callback"
                  }
                },
                "refresh_token": {
                  "summary": "Rotate refresh token",
                  "value": {
                    "grant_type": "refresh_token",
                    "client_id": "myne_api_abc",
                    "client_secret": "your-client-secret",
                    "refresh_token": "rt_replace-with-refresh-token"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "access_token",
                    "token_type",
                    "expires_in",
                    "brand_id"
                  ],
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "token_type": {
                      "type": "string",
                      "example": "Bearer"
                    },
                    "expires_in": {
                      "type": "integer",
                      "example": 3600
                    },
                    "brand_id": {
                      "type": "integer"
                    },
                    "refresh_token": {
                      "type": "string"
                    }
                  }
                },
                "examples": {
                  "client_credentials": {
                    "summary": "Creating brand (no refresh token)",
                    "value": {
                      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
                      "token_type": "Bearer",
                      "expires_in": 3600,
                      "brand_id": 42
                    }
                  },
                  "authorization_code": {
                    "summary": "Granted brand (includes refresh token)",
                    "value": {
                      "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
                      "token_type": "Bearer",
                      "expires_in": 3600,
                      "brand_id": 99,
                      "refresh_token": "rt_example"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "invalid_request, invalid_grant, or unsupported_grant_type",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "error_description"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "error_description": {
                      "type": "string"
                    }
                  }
                },
                "examples": {
                  "invalid_request": {
                    "summary": "Missing grant_type",
                    "value": {
                      "error": "invalid_request",
                      "error_description": "grant_type is required"
                    }
                  },
                  "invalid_grant": {
                    "summary": "Code or refresh token rejected",
                    "value": {
                      "error": "invalid_grant",
                      "error_description": "Authorization code is invalid"
                    }
                  },
                  "unsupported_grant_type": {
                    "summary": "Unknown grant_type",
                    "value": {
                      "error": "unsupported_grant_type",
                      "error_description": "grant_type must be client_credentials, authorization_code, or refresh_token"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "invalid_client",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "error_description"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "error_description": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "invalid_client",
                  "error_description": "Client authentication failed"
                }
              }
            }
          },
          "500": {
            "description": "server_error",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "error",
                    "error_description"
                  ],
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "error_description": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "error": "server_error",
                  "error_description": "Token service is unavailable"
                }
              }
            }
          }
        }
      }
    }
  }
}