{
  "info": {
    "name": "HolidayOS Connect (your-tenant-slug)",
    "description": "HolidayOS Connect 1.0. Set connectKey and connectSecret, then send. Full reference: https://holidayos.ai/developers/connect",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "apiOrigin",
      "value": "https://api.new.holidayos.ai"
    },
    {
      "key": "tenantSlug",
      "value": "your-tenant-slug"
    },
    {
      "key": "connectKey",
      "value": "",
      "type": "string"
    },
    {
      "key": "connectSecret",
      "value": "",
      "type": "secret"
    },
    {
      "key": "clientId",
      "value": "",
      "type": "string"
    },
    {
      "key": "leadId",
      "value": "",
      "type": "string"
    }
  ],
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Signs the request body with the Connect API key secret.",
          "// Set connectKey / connectSecret as collection or environment variables.",
          "const rawBody = pm.request.body ? pm.request.body.raw : '';",
          "const timestamp = Math.floor(Date.now() / 1000);",
          "const digest = CryptoJS.HmacSHA256(",
          "  timestamp + '.' + rawBody,",
          "  pm.variables.get('connectSecret'),",
          ").toString(CryptoJS.enc.Hex);",
          "pm.request.headers.upsert({ key: 'X-Connect-Key', value: pm.variables.get('connectKey') });",
          "pm.request.headers.upsert({ key: 'X-Connect-Tenant', value: pm.variables.get('tenantSlug') });",
          "pm.request.headers.upsert({",
          "  key: 'X-Connect-Signature',",
          "  value: 't=' + timestamp + ',v1=' + digest,",
          "});"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "Events",
      "item": [
        {
          "name": "contact.identified",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "A traveller identified themselves — signed in, or filled in a form.\n\nCreates the client, or updates the one it matches, and appends a timeline entry. No enquiry is opened. Matching is by `email` when you send one, otherwise `externalId`, otherwise `phone`; an address on a company domain (not a free mailbox) joins that company's account as one of its contacts. A new client without a `name` is named after its email.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_contact_identified_1\",\n      \"eventType\": \"contact.identified\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "contact.updated",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "A known traveller's details changed on your system.\n\nPatches the matched contact's `name` and `phone` with the values sent and appends a timeline entry; a field you omit is left as it is. It cannot change an email: the event is matched by the email it carries. Matching works as for `contact.identified`; on a company account the change lands on that colleague's contact entry, and an unknown address on the company's domain is added to it. Otherwise it never creates a client: when none matches, the event comes back `failed` with `error: \"contact_not_found\"` and nothing is written — send `contact.identified` first, then retry the same `eventId`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_contact_updated_1\",\n      \"eventType\": \"contact.updated\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "enquiry.submitted",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "A traveller asked for a quote. This is the event most integrations send.\n\nUpserts the contact, appends a timeline entry, and opens an enquiry at stage `inquiry` with a trip workspace in the inbox. `paxCount` (or a `party` object with `adults`/`children`/`rooms`), `travelDates` (`start`/`end`) and `budget` become the enquiry's brief and seat the trip workspace; omit any of them and it stays visibly unstated rather than being assumed. Send `budget` as free text (`around $3k pp`) and it is filed verbatim as budget notes — send `{ amount, currency }` only when you actually know the denomination. An optional `attribution` object (`source`, `medium`, `campaign`, `term`, `content`, `landingPath`, `referrer`) is recorded on the enquiry's marketing block and drives the pipeline campaign filter — send the campaign that earned the visit, not the last URL before submit. Send your own ID for the enquiry as `externalLeadId` to find it later with `GET /leads?externalId=`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_enquiry_submitted_1\",\n      \"eventType\": \"enquiry.submitted\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\",\n        \"travelDates\": {\n          \"startDate\": \"2026-11-04\",\n          \"endDate\": \"2026-11-10\"\n        },\n        \"party\": {\n          \"adults\": 3,\n          \"children\": 0,\n          \"rooms\": 1\n        },\n        \"message\": \"Customer requested advisor pricing before checkout.\",\n        \"attribution\": {\n          \"source\": \"google\",\n          \"medium\": \"cpc\",\n          \"campaign\": \"bali-nov\"\n        }\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "traveller.discovery_upserted",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "A traveller's discovery answers (destination, dates, party, pace, interests, constraints) were captured or changed on your system.\n\nUpserts the contact, appends a timeline entry and, like `enquiry.submitted`, reuses the open enquiry or opens one at stage `inquiry`. It then upserts the traveller's profile and trip discovery from the payload's discovery fields; unknown keys are ignored. Available only to tenants enabled for Traveller Discovery — for any other tenant the event is still accepted, with the warning `traveller_discovery_not_enabled`. The accepted result carries `travellerDiscovery.discoveryId`: send it back as `payload.hosTripDiscoveryId` so later events update the same record. Parts that did not apply are listed in `warnings` (`traveller_discovery_field_rejected:<field>`, `traveller_discovery_upsert_failed`) while the event itself is recorded, so re-send your current state as a new event rather than retrying this one.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_traveller_discovery_upserted_1\",\n      \"eventType\": \"traveller.discovery_upserted\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"discovery\": {\n          \"destinationInterest\": \"Bali\",\n          \"tripNotes\": \"Anniversary trip; prefers quiet villas over resorts.\"\n        }\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "trip.planning_started",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "The traveller began building a trip on your site.\n\nTimeline only — a planning signal carries no enquiry obligation.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_trip_planning_started_1\",\n      \"eventType\": \"trip.planning_started\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "trip.draft_updated",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "The traveller changed their in-progress trip draft.\n\nTimeline only.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_trip_draft_updated_1\",\n      \"eventType\": \"trip.draft_updated\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "quote.requested",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "A price was fetched — often automatically, while the visitor browses.\n\nTimeline only. Deliberately does not open an enquiry: only an explicit `enquiry.submitted` may create or advance one.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_quote_requested_1\",\n      \"eventType\": \"quote.requested\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "booking.started",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "The traveller entered checkout.\n\nAdvances the open enquiry to `proposal_approved` if that is further along than its current stage. Never regresses a stage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_booking_started_1\",\n      \"eventType\": \"booking.started\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "booking.abandoned",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "The traveller left checkout without completing.\n\nFlags the open enquiry for follow-up. Leaves its stage untouched.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_booking_abandoned_1\",\n      \"eventType\": \"booking.abandoned\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "booking.completed",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "The traveller paid and the booking is confirmed.\n\nForces the enquiry to stage `trip_booked`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_booking_completed_1\",\n      \"eventType\": \"booking.completed\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "booking.credit_applied",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "Your reply to `booking.confirmed`: how much of the traveller's corporate credit you actually took (`tripId`, `bookingRef`, `requestedRupees`, `appliedRupees`, `reason`). Needs the `credit:write` scope.\n\nMarks the invoice's credit confirmed. When less was taken than the invoice counted as paid, reopens the difference as a balance due and notifies the trip owner to collect it.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_booking_credit_applied_1\",\n      \"eventType\": \"booking.credit_applied\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "employee.credit_updated",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('accepted', function () {",
                  "  pm.response.to.have.status(200);",
                  "  const body = pm.response.json();",
                  "  pm.expect(body.failed, 'failed count').to.eql(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/events",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "events"
              ]
            },
            "description": "The traveller's corporate credit balance changed on your side (`membershipId`, `corporateName`, `availablePaise`, `grantedPaise`, `redeemablePercent`, `asOf`). Send it on every change. Needs the `credit:write` scope.\n\nRefreshes the credit shown on the client's open enquiries and their trips (newest `asOf` wins), so advisors see the current balance. Never opens an enquiry.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"specVersion\": \"1.0\",\n      \"eventId\": \"evt_employee_credit_updated_1\",\n      \"eventType\": \"employee.credit_updated\",\n      \"occurredAt\": \"2026-08-23T09:15:00Z\",\n      \"tenant\": \"your-tenant-slug\",\n      \"origin\": \"source-system\",\n      \"actor\": {\n        \"type\": \"contact\",\n        \"email\": \"traveler@example.com\",\n        \"name\": \"Ana Silva\",\n        \"phone\": \"+60123456789\"\n      },\n      \"payload\": {\n        \"destination\": \"Bali\"\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Records",
      "item": [
        {
          "name": "POST /clients",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
                  "const timestamp = Math.floor(Date.now() / 1000);",
                  "const url = pm.request.url;",
                  "const target = '/' + url.getPath(true).replace(/^\\//, '') + (url.getQueryString() ? '?' + url.getQueryString() : '');",
                  "const digest = CryptoJS.HmacSHA256(",
                  "  timestamp + '.' + pm.request.method + '.' + pm.variables.replaceIn(target) + '.' + pm.variables.replaceIn(rawBody),",
                  "  pm.variables.get('connectSecret'),",
                  ").toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Connect-Signature', value: 't=' + timestamp + ',v2=' + digest });",
                  "if (pm.request.method === 'POST') {",
                  "  pm.request.headers.upsert({ key: 'Idempotency-Key', value: 'postman-' + Date.now() });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/clients",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "clients"
              ]
            },
            "description": "Create a client — needs `clients:write`.\n\nCreates a client owned by your Connect identity. Needs at least one of `email`, `phone` or `externalId`. When that identity already exists the answer is `409 client_exists` with its `clientId` — read or patch that one instead. An address on a company domain your agency already has as a company account joins that account.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Ana Silva\",\n  \"email\": \"ana@example.com\",\n  \"phone\": \"+60123456789\",\n  \"externalId\": \"user_8841\",\n  \"marketingOptIn\": true,\n  \"travelers\": [\n    {\n      \"name\": \"Leo Silva\",\n      \"type\": \"child\",\n      \"age\": 9\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /clients",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
                  "const timestamp = Math.floor(Date.now() / 1000);",
                  "const url = pm.request.url;",
                  "const target = '/' + url.getPath(true).replace(/^\\//, '') + (url.getQueryString() ? '?' + url.getQueryString() : '');",
                  "const digest = CryptoJS.HmacSHA256(",
                  "  timestamp + '.' + pm.request.method + '.' + pm.variables.replaceIn(target) + '.' + pm.variables.replaceIn(rawBody),",
                  "  pm.variables.get('connectSecret'),",
                  ").toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Connect-Signature', value: 't=' + timestamp + ',v2=' + digest });",
                  "if (pm.request.method === 'POST') {",
                  "  pm.request.headers.upsert({ key: 'Idempotency-Key', value: 'postman-' + Date.now() });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/clients",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "clients"
              ]
            },
            "description": "Look a client up — needs `clients:read`.\n\nExact identity lookup, with the same precedence as matching: `email`, else `externalId`, else `phone`. Returns an empty list or a list of one."
          }
        },
        {
          "name": "GET /clients/{clientId}",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
                  "const timestamp = Math.floor(Date.now() / 1000);",
                  "const url = pm.request.url;",
                  "const target = '/' + url.getPath(true).replace(/^\\//, '') + (url.getQueryString() ? '?' + url.getQueryString() : '');",
                  "const digest = CryptoJS.HmacSHA256(",
                  "  timestamp + '.' + pm.request.method + '.' + pm.variables.replaceIn(target) + '.' + pm.variables.replaceIn(rawBody),",
                  "  pm.variables.get('connectSecret'),",
                  ").toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Connect-Signature', value: 't=' + timestamp + ',v2=' + digest });",
                  "if (pm.request.method === 'POST') {",
                  "  pm.request.headers.upsert({ key: 'Idempotency-Key', value: 'postman-' + Date.now() });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/clients/{{clientId}}",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "clients",
                "{{clientId}}"
              ]
            },
            "description": "Read a client — needs `clients:read`.\n\nA client of another agency is a 404, never a 403."
          }
        },
        {
          "name": "PATCH /clients/{clientId}",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
                  "const timestamp = Math.floor(Date.now() / 1000);",
                  "const url = pm.request.url;",
                  "const target = '/' + url.getPath(true).replace(/^\\//, '') + (url.getQueryString() ? '?' + url.getQueryString() : '');",
                  "const digest = CryptoJS.HmacSHA256(",
                  "  timestamp + '.' + pm.request.method + '.' + pm.variables.replaceIn(target) + '.' + pm.variables.replaceIn(rawBody),",
                  "  pm.variables.get('connectSecret'),",
                  ").toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Connect-Signature', value: 't=' + timestamp + ',v2=' + digest });",
                  "if (pm.request.method === 'POST') {",
                  "  pm.request.headers.upsert({ key: 'Idempotency-Key', value: 'postman-' + Date.now() });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/clients/{{clientId}}",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "clients",
                "{{clientId}}"
              ]
            },
            "description": "Update a client — needs `clients:write`.\n\nChanges only the fields you send; an omitted field is left as it is. Changing `email` or `externalId` to one another client holds is `409 identity_taken` with that client's ID.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"phone\": \"+447700900123\",\n  \"marketingOptIn\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "POST /leads",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
                  "const timestamp = Math.floor(Date.now() / 1000);",
                  "const url = pm.request.url;",
                  "const target = '/' + url.getPath(true).replace(/^\\//, '') + (url.getQueryString() ? '?' + url.getQueryString() : '');",
                  "const digest = CryptoJS.HmacSHA256(",
                  "  timestamp + '.' + pm.request.method + '.' + pm.variables.replaceIn(target) + '.' + pm.variables.replaceIn(rawBody),",
                  "  pm.variables.get('connectSecret'),",
                  ").toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Connect-Signature', value: 't=' + timestamp + ',v2=' + digest });",
                  "if (pm.request.method === 'POST') {",
                  "  pm.request.headers.upsert({ key: 'Idempotency-Key', value: 'postman-' + Date.now() });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/leads",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "leads"
              ]
            },
            "description": "Create an enquiry — needs `leads:write`.\n\nRuns exactly the path an `enquiry.submitted` event takes: the client is matched or created, the enquiry opens at `inquiry` with an enquiry number, is routed to an advisor, gets a trip workspace, and `message` opens its inbox thread. Send `clientId` to attach it to a client you already have, or `contact` to match one. Returns the enquiry with `clientId` and `tripId`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"clientId\": \"6ab3d852869e40b4db978f8e\",\n  \"externalId\": \"enquiry_2207\",\n  \"destination\": \"Bali\",\n  \"travelDates\": {\n    \"startDate\": \"2026-11-04\",\n    \"endDate\": \"2026-11-10\"\n  },\n  \"party\": {\n    \"adults\": 2,\n    \"children\": 1,\n    \"childAges\": [\n      7\n    ],\n    \"rooms\": 1\n  },\n  \"budget\": {\n    \"notes\": \"around $3k per person\"\n  },\n  \"message\": \"Anniversary trip; we'd like quiet villas.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /leads",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
                  "const timestamp = Math.floor(Date.now() / 1000);",
                  "const url = pm.request.url;",
                  "const target = '/' + url.getPath(true).replace(/^\\//, '') + (url.getQueryString() ? '?' + url.getQueryString() : '');",
                  "const digest = CryptoJS.HmacSHA256(",
                  "  timestamp + '.' + pm.request.method + '.' + pm.variables.replaceIn(target) + '.' + pm.variables.replaceIn(rawBody),",
                  "  pm.variables.get('connectSecret'),",
                  ").toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Connect-Signature', value: 't=' + timestamp + ',v2=' + digest });",
                  "if (pm.request.method === 'POST') {",
                  "  pm.request.headers.upsert({ key: 'Idempotency-Key', value: 'postman-' + Date.now() });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/leads",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "leads"
              ]
            },
            "description": "Look an enquiry up by your ID — needs `leads:read`.\n\nFinds the enquiry you created with that `externalId` (or sent as `payload.externalLeadId`), in your agency only."
          }
        },
        {
          "name": "GET /leads/{leadId}",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
                  "const timestamp = Math.floor(Date.now() / 1000);",
                  "const url = pm.request.url;",
                  "const target = '/' + url.getPath(true).replace(/^\\//, '') + (url.getQueryString() ? '?' + url.getQueryString() : '');",
                  "const digest = CryptoJS.HmacSHA256(",
                  "  timestamp + '.' + pm.request.method + '.' + pm.variables.replaceIn(target) + '.' + pm.variables.replaceIn(rawBody),",
                  "  pm.variables.get('connectSecret'),",
                  ").toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Connect-Signature', value: 't=' + timestamp + ',v2=' + digest });",
                  "if (pm.request.method === 'POST') {",
                  "  pm.request.headers.upsert({ key: 'Idempotency-Key', value: 'postman-' + Date.now() });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/leads/{{leadId}}",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "leads",
                "{{leadId}}"
              ]
            },
            "description": "Read an enquiry — needs `leads:read`.\n\nThe enquiry with its trip summary. Once a trip exists it owns the pipeline stage, so `stage` is the trip's — what the advisor's board shows."
          }
        },
        {
          "name": "PATCH /leads/{leadId}",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
                  "const timestamp = Math.floor(Date.now() / 1000);",
                  "const url = pm.request.url;",
                  "const target = '/' + url.getPath(true).replace(/^\\//, '') + (url.getQueryString() ? '?' + url.getQueryString() : '');",
                  "const digest = CryptoJS.HmacSHA256(",
                  "  timestamp + '.' + pm.request.method + '.' + pm.variables.replaceIn(target) + '.' + pm.variables.replaceIn(rawBody),",
                  "  pm.variables.get('connectSecret'),",
                  ").toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Connect-Signature', value: 't=' + timestamp + ',v2=' + digest });",
                  "if (pm.request.method === 'POST') {",
                  "  pm.request.headers.upsert({ key: 'Idempotency-Key', value: 'postman-' + Date.now() });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/leads/{{leadId}}",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "leads",
                "{{leadId}}"
              ]
            },
            "description": "Update an enquiry — needs `leads:write`.\n\nEnquiry-level changes. `stage` goes through the same rules an advisor meets: proposal, booking and travel stages are set by their own workflows (`422 stage_not_allowed`), and `lost` needs a `reason` of 5+ characters (`422 reason_required`). `owner` must be an active member of your agency (`422 owner_not_member`); the new owner is notified.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"stage\": \"lost\",\n  \"reason\": \"Booked with another agency\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "GET /leads/{leadId}/trip",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
                  "const timestamp = Math.floor(Date.now() / 1000);",
                  "const url = pm.request.url;",
                  "const target = '/' + url.getPath(true).replace(/^\\//, '') + (url.getQueryString() ? '?' + url.getQueryString() : '');",
                  "const digest = CryptoJS.HmacSHA256(",
                  "  timestamp + '.' + pm.request.method + '.' + pm.variables.replaceIn(target) + '.' + pm.variables.replaceIn(rawBody),",
                  "  pm.variables.get('connectSecret'),",
                  ").toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Connect-Signature', value: 't=' + timestamp + ',v2=' + digest });",
                  "if (pm.request.method === 'POST') {",
                  "  pm.request.headers.upsert({ key: 'Idempotency-Key', value: 'postman-' + Date.now() });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/leads/{{leadId}}/trip",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "leads",
                "{{leadId}}",
                "trip"
              ]
            },
            "description": "Read an enquiry's trip — needs `leads:read`.\n\nThe trip workspace: brief, stage, owner and whether a proposal has been sent. `409 trip_not_open` when the enquiry has no trip yet."
          }
        },
        {
          "name": "PATCH /leads/{leadId}/trip",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const rawBody = pm.request.body && pm.request.body.raw ? pm.request.body.raw : '';",
                  "const timestamp = Math.floor(Date.now() / 1000);",
                  "const url = pm.request.url;",
                  "const target = '/' + url.getPath(true).replace(/^\\//, '') + (url.getQueryString() ? '?' + url.getQueryString() : '');",
                  "const digest = CryptoJS.HmacSHA256(",
                  "  timestamp + '.' + pm.request.method + '.' + pm.variables.replaceIn(target) + '.' + pm.variables.replaceIn(rawBody),",
                  "  pm.variables.get('connectSecret'),",
                  ").toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Connect-Signature', value: 't=' + timestamp + ',v2=' + digest });",
                  "if (pm.request.method === 'POST') {",
                  "  pm.request.headers.upsert({ key: 'Idempotency-Key', value: 'postman-' + Date.now() });",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{apiOrigin}}/api/v1/crm/connect/leads/{{leadId}}/trip",
              "host": [
                "{{apiOrigin}}"
              ],
              "path": [
                "api",
                "v1",
                "crm",
                "connect",
                "leads",
                "{{leadId}}",
                "trip"
              ]
            },
            "description": "Post a customer's changes to their trip — needs `leads:write`.\n\nUse this when the customer changes their trip on your side. Brief fields update the trip workspace; a date change is recorded as a correction with `changeSummary` as its reason. A new `itinerary` replaces the draft proposal only while the enquiry is at `inquiry`/`follow_up` and nothing has been sent — otherwise (or if the update itself fails) the response says `itineraryApplied: false` with `itineraryNotAppliedReason` (`advisor_working`, `proposal_sent`, `no_proposal` or `update_failed`) and the advisor decides. Either way, what changed (before → after), `changeSummary` and the customer's `message` are posted to the enquiry's inbox thread and its owner is notified. A closed enquiry (lost, archived, booked or travelled) answers `409 trip_closed`: send the change as a new enquiry instead.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"travelDates\": {\n    \"startDate\": \"2026-12-01\",\n    \"endDate\": \"2026-12-07\"\n  },\n  \"party\": {\n    \"adults\": 3\n  },\n  \"changeSummary\": \"Customer moved the trip to December and added a traveller\",\n  \"message\": \"My sister is joining us!\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    }
  ]
}