# Introduction

## Customer Alliance API

Review management, surveys, and feedback for hospitality, as an API. Pull your reviews, surveys and responses, analytics and AI insights, and subscribe to events with outbound webhooks.

### What you can do

* **Reviews** — list and fetch collected reviews and your published replies.
* **Surveys** — read published surveys, their responses and answers (PII-free).
* **Analytics** — aggregate reputation and rating scores for any part of your hierarchy.
* **Insights** — AI recommendations, topic mentions and the topic taxonomy.
* **Guests —** Update or create new guests per connected property.
* **Nodes** — discover the business-unit / property hierarchy your key can see.
* **Webhooks** — register endpoints and receive signed event deliveries.

### Base URL

All endpoints are relative to:

[https://app.customer-alliance.com/api/v4<br>](<https://indigo-v2.vercel.app/api/v4&#xA;&#xA;&#xA;>)

### Authentication

Every request needs your API key as a Bearer token. Create and scope keys in the Customer Alliance dashboard under **Settings → API** — the key (`ca_live_…`) is shown once, so store it somewhere safe.

`curl https://app.customer-alliance.com/api/v4/reviews`\
`-H "Authorization: Bearer ca_live_your_key_here"`

### Your first request

Call `/whoami` to confirm your key resolves to the account, node and scopes you expect:

`curl https://app.customer-alliance.com/api/v4/whoami`\
`-H "Authorization: Bearer ca_live_your_key_here"`

`{`\
`"account_id": "a1b2c3d4-…",`\
`"node_id": "e5f6a7b8-…",`\
`"scopes": ["reviews:read", "surveys:read", "webhooks:manage"]`\
`}`

### Scopes

Each key holds only the scopes you grant it — `reviews:read`, `surveys:read`, `analytics:read`, `insights:read`, `nodes:read`, `webhooks:manage`. A request missing its scope returns `403`.

### Pagination

List endpoints are cursor-paginated. Pass the previous response's `next_cursor` as `?cursor=`, up to `?limit=` (max 100), and stop when `has_more` is `false`.

### Rate limits

Requests are rate-limited per key. If you exceed the limit you'll get a `429` with a `Retry-After` header — wait that many seconds before retrying.

### Webhooks

Rather than polling, register a webhook endpoint and we'll POST signed events to you — new reviews, completed survey responses, alerts and more. See **Webhook Events** for every payload, and verify the `X-CA-Signature` header on each delivery. Manage endpoints under **Webhooks**.

### Need help?

Email support[@customer-alliance.com](mailto:info@customer-alliance.com) or visit [customer-alliance.com](https://www.customer-alliance.com/).


# Getting started

1. In the dashboard, go to **Settings → API** and create a key, granting only the scopes you need.
2. Copy the key (`ca_live_…`) — it's shown once.
3. Send it as a Bearer token on every request:

```bash
curl https://app.customer-alliance.com/api/v4/reviews \
  -H "Authorization: Bearer ca_live_your_key_here"
```

Call `GET /whoami` to confirm the key resolves to the account, node and scopes you expect.


# Authentication

Every request needs `Authorization: Bearer <key>`. Keys are created and scoped in **Settings → API**.


# Scopes

Each key holds a subset of `reviews:read`, `surveys:read`, `analytics:read`, `insights:read`, `nodes:read`, `webhooks:manage`, `guests:write`. A call missing its scope returns `403`.


# Hierarchy

A key is bound to one node in your account hierarchy; **read** endpoints cover that node and its subtree. The webhook-management endpoints (`/webhooks*`) are the exception — they operate account-wide and ignore the key's node binding (see **Webhooks**).


# Pagination

List endpoints are cursor-paginated: pass the previous response's `next_cursor` as `?cursor=`, up to `?limit=` (max 100).


# Rate limiting

Requests are rate-limited per key (fixed window). A `429` response includes a `Retry-After` header — honour it before retrying.


# Webhooks

Register endpoints to receive signed event deliveries (see **Webhook events**). Verify the `X-CA-Signature` HMAC on every delivery before trusting it.

Webhook endpoints and event delivery are **account-scoped**: a `webhooks:manage` key manages every endpoint in the account and its endpoints receive events from the whole account, regardless of the node the key is bound to. Node binding applies to read endpoints only.


# Identity

Who the key is.

## GET /whoami

> The authenticated key's account, node and scopes

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Identity","description":"Who the key is."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"WhoAmI":{"type":"object","properties":{"account_id":{"type":"string","format":"uuid"},"node_id":{"type":"string","format":"uuid"},"scopes":{"type":"array","items":{"type":"string"}}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/whoami":{"get":{"summary":"The authenticated key's account, node and scopes","operationId":"whoami","tags":["Identity"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhoAmI"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}}}
```


# Reviews

Collected reviews and owner replies.

## List reviews for the key's node subtree (newest first)

> Requires \`reviews:read\`. Hidden reviews are included and marked \`is\_hidden=true\` — filter on that flag to exclude them (unlike the public widget/certificate surfaces, which omit hidden reviews entirely).

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Reviews","description":"Collected reviews and owner replies."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"parameters":{"cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque pagination cursor from a prior response's next_cursor."},"limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}},"schemas":{"Review":{"type":"object","description":"A collected review. Hidden reviews are included and flagged `is_hidden`; the owner's own reply text is `owner_reply`.","properties":{"id":{"type":"string","format":"uuid"},"platform":{"type":"string"},"external_id":{"type":"string","description":"The review's id on the source platform."},"author":{"type":["string","null"],"description":"Null when the review is anonymised."},"rating":{"type":["number","null"],"description":"Rating on a 0–5 scale."},"rating_normalized":{"type":["number","null"],"description":"Rating normalised to 0–100."},"title":{"type":["string","null"]},"text":{"type":["string","null"]},"url":{"type":["string","null"]},"language":{"type":["string","null"]},"sentiment":{"type":["string","null"],"description":"positive | neutral | negative | null."},"owner_reply":{"type":["string","null"]},"replied_at":{"type":["string","null"],"format":"date-time"},"reviewed_at":{"type":"string","format":"date-time"},"node_id":{"type":"string","format":"uuid"},"is_hidden":{"type":"boolean"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/reviews":{"get":{"summary":"List reviews for the key's node subtree (newest first)","description":"Requires `reviews:read`. Hidden reviews are included and marked `is_hidden=true` — filter on that flag to exclude them (unlike the public widget/certificate surfaces, which omit hidden reviews entirely).","operationId":"listReviews","tags":["Reviews"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"A page of reviews.","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Review"}},"next_cursor":{"type":["string","null"],"description":"Pass as `?cursor=` to fetch the next page; null on the last page."},"has_more":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```

## Get a single review by id

> Requires \`reviews:read\`. Returns hidden reviews too (marked \`is\_hidden=true\`).

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Reviews","description":"Collected reviews and owner replies."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"Review":{"type":"object","description":"A collected review. Hidden reviews are included and flagged `is_hidden`; the owner's own reply text is `owner_reply`.","properties":{"id":{"type":"string","format":"uuid"},"platform":{"type":"string"},"external_id":{"type":"string","description":"The review's id on the source platform."},"author":{"type":["string","null"],"description":"Null when the review is anonymised."},"rating":{"type":["number","null"],"description":"Rating on a 0–5 scale."},"rating_normalized":{"type":["number","null"],"description":"Rating normalised to 0–100."},"title":{"type":["string","null"]},"text":{"type":["string","null"]},"url":{"type":["string","null"]},"language":{"type":["string","null"]},"sentiment":{"type":["string","null"],"description":"positive | neutral | negative | null."},"owner_reply":{"type":["string","null"]},"replied_at":{"type":["string","null"],"format":"date-time"},"reviewed_at":{"type":"string","format":"date-time"},"node_id":{"type":"string","format":"uuid"},"is_hidden":{"type":"boolean"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found or outside the key's scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/reviews/{id}":{"get":{"summary":"Get a single review by id","description":"Requires `reviews:read`. Returns hidden reviews too (marked `is_hidden=true`).","operationId":"getReview","tags":["Reviews"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Review"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```


# Analytics

Score and rating aggregates.

## Aggregate reputation scores for the key's node subtree

> Requires \`analytics:read\`.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Analytics","description":"Score and rating aggregates."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"AnalyticsScores":{"type":"object","description":"Aggregate reputation scores for the key's node subtree.","properties":{"node_id":{"type":"string","format":"uuid"},"overall_score":{"type":["number","null"],"description":"Overall score on a 0–5 scale (one decimal)."},"overall_score_normalized":{"type":["number","null"],"description":"Overall score normalised to 0–100 (may carry one decimal; round for whole-percent display)."},"total_reviews":{"type":"integer"},"recommend_rate":{"type":["number","null"]},"response_rate":{"type":["number","null"]},"platform_scores":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string"},"score":{"type":["number","null"],"description":"Platform score on a 0–5 scale (one decimal)."},"score_normalized":{"type":["number","null"],"description":"Platform score normalised to 0–100 (may carry one decimal; round for whole-percent display)."},"reviews":{"type":"integer"}}}}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/analytics/scores":{"get":{"summary":"Aggregate reputation scores for the key's node subtree","description":"Requires `analytics:read`.","operationId":"getAnalyticsScores","tags":["Analytics"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnalyticsScores"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}}}
```


# Nodes

Hierarchy discovery.

## List hierarchy nodes within the key's scope

> Requires \`nodes:read\`.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Nodes","description":"Hierarchy discovery."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"Node":{"type":"object","description":"A hierarchy node (business unit / property) within the key's scope.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"parent_id":{"type":["string","null"],"format":"uuid"},"type":{"type":"string"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/nodes":{"get":{"summary":"List hierarchy nodes within the key's scope","description":"Requires `nodes:read`.","operationId":"listNodes","tags":["Nodes"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Node"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}}}
```


# Surveys

Surveys, responses and answers (PII-free).

## List published surveys whose owner node is in the key's subtree

> Requires \`surveys:read\`.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Surveys","description":"Surveys, responses and answers (PII-free)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"parameters":{"cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque pagination cursor from a prior response's next_cursor."},"limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}},"schemas":{"Survey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"node_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"primary_language":{"type":"string"},"published_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/surveys":{"get":{"summary":"List published surveys whose owner node is in the key's subtree","description":"Requires `surveys:read`.","operationId":"listSurveys","tags":["Surveys"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"A page of surveys.","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Survey"}},"next_cursor":{"type":["string","null"],"description":"Pass as `?cursor=` to fetch the next page; null on the last page."},"has_more":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}}}
```

## Get a single published survey (with its published definition)

> Requires \`surveys:read\`.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Surveys","description":"Surveys, responses and answers (PII-free)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"SurveyDetail":{"allOf":[{"$ref":"#/components/schemas/Survey"},{"type":"object","properties":{"published_definition":{"type":"object","description":"The published survey definition (pages/questions)."}}}]},"Survey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"node_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"primary_language":{"type":"string"},"published_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found or outside the key's scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/surveys/{id}":{"get":{"summary":"Get a single published survey (with its published definition)","description":"Requires `surveys:read`.","operationId":"getSurvey","tags":["Surveys"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SurveyDetail"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```

## List responses for a survey (newest first)

> Requires \`surveys:read\`. Respondent PII is not included.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Surveys","description":"Surveys, responses and answers (PII-free)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"parameters":{"cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque pagination cursor from a prior response's next_cursor."},"limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}},"schemas":{"SurveyResponse":{"type":"object","description":"A survey response. Respondent PII (email, personalisation context, tokens) is never exposed.","properties":{"id":{"type":"string","format":"uuid"},"survey_id":{"type":"string","format":"uuid"},"node_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"channel":{"type":"string"},"overall_score":{"type":["number","null"]},"started_at":{"type":"string","format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found or outside the key's scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/surveys/{id}/responses":{"get":{"summary":"List responses for a survey (newest first)","description":"Requires `surveys:read`. Respondent PII is not included.","operationId":"listSurveyResponses","tags":["Surveys"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"A page of responses.","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SurveyResponse"}},"next_cursor":{"type":["string","null"],"description":"Pass as `?cursor=` to fetch the next page; null on the last page."},"has_more":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```

## List the answers for a single response

> Requires \`surveys:read\`.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Surveys","description":"Surveys, responses and answers (PII-free)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"SurveyAnswer":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"question_id":{"type":"string"},"question_path":{"type":"string"},"question_type":{"type":"string"},"value":{"description":"The answer value; shape depends on question_type."},"numeric_value":{"type":["number","null"]},"comment":{"type":["string","null"]},"answered_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found or outside the key's scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/surveys/{id}/responses/{responseId}/answers":{"get":{"summary":"List the answers for a single response","description":"Requires `surveys:read`.","operationId":"listResponseAnswers","tags":["Surveys"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"responseId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/SurveyAnswer"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```


# Insights

AI recommendations, mentions and topics.

## List AI recommendations for the account (newest first)

> Requires \`insights:read\`.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Insights","description":"AI recommendations, mentions and topics."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"parameters":{"cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque pagination cursor from a prior response's next_cursor."},"limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}},"schemas":{"Recommendation":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"body":{"type":"string"},"priority":{"type":"string"},"impact":{"type":["string","null"]},"canonical_label_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/insights/recommendations":{"get":{"summary":"List AI recommendations for the account (newest first)","description":"Requires `insights:read`.","operationId":"listRecommendations","tags":["Insights"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"A page of recommendations.","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Recommendation"}},"next_cursor":{"type":["string","null"],"description":"Pass as `?cursor=` to fetch the next page; null on the last page."},"has_more":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}}}
```

## List topic mentions extracted from reviews in the key's subtree (newest first)

> Requires \`insights:read\`.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Insights","description":"AI recommendations, mentions and topics."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"parameters":{"cursor":{"name":"cursor","in":"query","schema":{"type":"string"},"description":"Opaque pagination cursor from a prior response's next_cursor."},"limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":25}}},"schemas":{"Mention":{"type":"object","description":"A topic mention extracted from a review. `is_hidden` mirrors the source review's flag.","properties":{"id":{"type":"string","format":"uuid"},"review_id":{"type":"string","format":"uuid"},"source_field":{"type":"string"},"text":{"type":"string"},"sentiment":{"type":["string","null"]},"canonical_label_id":{"type":"string","format":"uuid"},"custom_category_id":{"type":["string","null"],"format":"uuid"},"confidence":{"type":["number","null"]},"is_hidden":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/insights/mentions":{"get":{"summary":"List topic mentions extracted from reviews in the key's subtree (newest first)","description":"Requires `insights:read`.","operationId":"listMentions","tags":["Insights"],"parameters":[{"$ref":"#/components/parameters/cursor"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"A page of mentions.","content":{"application/json":{"schema":{"type":"object","required":["data","next_cursor","has_more"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Mention"}},"next_cursor":{"type":["string","null"],"description":"Pass as `?cursor=` to fetch the next page; null on the last page."},"has_more":{"type":"boolean"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}}}
```

## List the canonical topic labels readable by the account

> Requires \`insights:read\`.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Insights","description":"AI recommendations, mentions and topics."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"Topic":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"language":{"type":["string","null"]},"parent_label_id":{"type":["string","null"],"format":"uuid"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/insights/topics":{"get":{"summary":"List the canonical topic labels readable by the account","description":"Requires `insights:read`.","operationId":"listTopics","tags":["Insights"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Topic"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}}}
```


# Guests

Push guests into a property (write-only; grants no read of guest data).

## Create or update a guest at a property

> Requires \`guests:write\`. Push a guest into the Guests section — for a CRM/PMS/booking integration to feed guests in. Write-only: this scope grants no read of guest data. Identity is (property, email), so a repeat push of the same email to the same property updates that guest (\`200\`) rather than creating a duplicate (\`201\` on first create). Include an optional \`reservation\` to upsert the guest's stay in the same call (identity (property, reservation.external\_id)), so you can build audiences on stay status, booking channel, and recently-ended stays. A guest erased under GDPR is never repopulated (\`409\`).

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Guests","description":"Push guests into a property (write-only; grants no read of guest data)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"GuestPushInput":{"type":"object","required":["email"],"description":"A guest to create or update at one property. Identity is (property, email): a repeat push of the same email to the same property updates that guest rather than creating a duplicate. Optionally include a `reservation` to upsert the guest's stay in the same call.","properties":{"property_id":{"type":"string","format":"uuid","description":"The property (leaf node) to attach the guest to. Optional when the key is bound to a single property; required when the key is bound to a group. Must be a property within the key's node scope."},"email":{"type":"string","format":"email","maxLength":320,"description":"Required — normalized (trimmed + lower-cased) and used as the dedupe key."},"salutation":{"type":"string","maxLength":50},"first_name":{"type":"string","maxLength":200},"last_name":{"type":"string","maxLength":200},"phone":{"type":"string","maxLength":40},"country_code":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 (e.g. DE)."},"language_code":{"type":"string","maxLength":5,"description":"e.g. de or de-DE."},"custom_fields":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Merged into the guest's custom fields; omitted keys are preserved."},"marketing_consent":{"type":"boolean","description":"When set, records invitation-email consent. An explicit prior withdrawal is never overridden by a grant (GDPR)."},"reservation":{"$ref":"#/components/schemas/ReservationInput"}}},"ReservationInput":{"type":"object","required":["external_id"],"description":"A reservation (stay) to create or update for the guest, mirroring what a PMS integration syncs. Identity is (property, external_id): a repeat push of the same external_id updates the stay in place (e.g. a status change from booked to checked_out) rather than creating a duplicate. Pushing stays lets you build audiences on stay status, booking channel, and recently-ended stays.","properties":{"external_id":{"type":"string","minLength":1,"maxLength":255,"description":"Required — your booking reference; the stay's dedupe key."},"status":{"type":"string","enum":["booked","arrived","checked_out","cancelled","no_show"],"description":"Canonical stay status. Defaults to `booked` when omitted."},"raw_status":{"type":"string","maxLength":120,"description":"Your own status label, kept verbatim for display."},"check_in":{"type":"string","format":"date-time","description":"Stay start. ISO-8601 date or date-time."},"check_out":{"type":"string","format":"date-time","description":"Stay end. ISO-8601 date or date-time — drives the recently-ended-stay audience filter."},"checked_in_at":{"type":"string","format":"date-time"},"checked_out_at":{"type":"string","format":"date-time"},"channel":{"type":"string","maxLength":120,"description":"Booking origin, e.g. booking.com."},"custom_fields":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Merged into the stay's custom fields; omitted keys are preserved."}}},"Guest":{"type":"object","description":"A guest record as stored. `source` is `api` for guests created through this endpoint.","properties":{"id":{"type":"string","format":"uuid"},"property_id":{"type":"string","format":"uuid"},"source":{"type":"string"},"email":{"type":["string","null"]},"salutation":{"type":["string","null"]},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"phone":{"type":["string","null"]},"country_code":{"type":["string","null"]},"language_code":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"reservation":{"$ref":"#/components/schemas/Reservation","description":"Present only when the push included a reservation."}}},"Reservation":{"type":"object","description":"A reservation (stay) as stored. Returned inside a `Guest` when the push included a `reservation`.","properties":{"id":{"type":"string","format":"uuid"},"external_id":{"type":"string"},"status":{"type":"string","enum":["booked","arrived","checked_out","cancelled","no_show"]},"raw_status":{"type":["string","null"]},"check_in":{"type":["string","null"],"format":"date-time"},"check_out":{"type":["string","null"],"format":"date-time"},"checked_in_at":{"type":["string","null"],"format":"date-time"},"checked_out_at":{"type":["string","null"],"format":"date-time"},"channel":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Rate limit exceeded. Honour the `Retry-After` header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/guests":{"post":{"summary":"Create or update a guest at a property","description":"Requires `guests:write`. Push a guest into the Guests section — for a CRM/PMS/booking integration to feed guests in. Write-only: this scope grants no read of guest data. Identity is (property, email), so a repeat push of the same email to the same property updates that guest (`200`) rather than creating a duplicate (`201` on first create). Include an optional `reservation` to upsert the guest's stay in the same call (identity (property, reservation.external_id)), so you can build audiences on stay status, booking channel, and recently-ended stays. A guest erased under GDPR is never repopulated (`409`).","operationId":"pushGuest","tags":["Guests"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GuestPushInput"}}}},"responses":{"200":{"description":"Existing guest updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guest"}}}},"201":{"description":"Guest created.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Guest"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"The guest has been erased (GDPR) and can no longer be updated.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"property_id is missing, out of scope, or not a property (leaf) node.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}}}
```


# Webhooks

Manage outbound webhook endpoints and inspect deliveries. Account-scoped: these endpoints ignore the key's node binding (unlike the read endpoints).

## List the account's outbound webhook endpoints

> Requires \`webhooks:manage\`. Signing secrets are never returned.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Webhooks","description":"Manage outbound webhook endpoints and inspect deliveries. Account-scoped: these endpoints ignore the key's node binding (unlike the read endpoints)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"WebhookEndpoint":{"type":"object","description":"A registered outbound webhook endpoint. The signing secret is never returned after creation.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["review.received","review.replied","review.alert_triggered","survey.response_completed","guest.created","guest.unsubscribed"]}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/webhooks":{"get":{"summary":"List the account's outbound webhook endpoints","description":"Requires `webhooks:manage`. Signing secrets are never returned.","operationId":"listWebhooks","tags":["Webhooks"],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEndpoint"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"}}}}}}
```

## Register an outbound webhook endpoint

> Requires \`webhooks:manage\`. The signing secret is returned exactly once — store it now.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Webhooks","description":"Manage outbound webhook endpoints and inspect deliveries. Account-scoped: these endpoints ignore the key's node binding (unlike the read endpoints)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"WebhookEndpointInput":{"type":"object","required":["name","url","events"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"url":{"type":"string","format":"uri","description":"Delivery URL. Must be http(s); private/reserved addresses are rejected at delivery time."},"events":{"type":"array","minItems":1,"items":{"type":"string","enum":["review.received","review.replied","review.alert_triggered","survey.response_completed","guest.created","guest.unsubscribed"]}},"is_active":{"type":"boolean","description":"PATCH only — pause/resume deliveries without deleting the endpoint."}}},"WebhookEndpointCreated":{"type":"object","required":["secret","endpoint"],"properties":{"secret":{"type":"string","description":"The signing secret (`whsec_…`). Returned exactly once — store it now; you cannot retrieve it again."},"endpoint":{"$ref":"#/components/schemas/WebhookEndpoint"}}},"WebhookEndpoint":{"type":"object","description":"A registered outbound webhook endpoint. The signing secret is never returned after creation.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["review.received","review.replied","review.alert_triggered","survey.response_completed","guest.created","guest.unsubscribed"]}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/webhooks":{"post":{"summary":"Register an outbound webhook endpoint","description":"Requires `webhooks:manage`. The signing secret is returned exactly once — store it now.","operationId":"createWebhook","tags":["Webhooks"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointInput"}}}},"responses":{"201":{"description":"Created — secret shown once.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreated"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"description":"Per-account endpoint limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Delete a webhook endpoint (cascades its deliveries)

> Requires \`webhooks:manage\`.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Webhooks","description":"Manage outbound webhook endpoints and inspect deliveries. Account-scoped: these endpoints ignore the key's node binding (unlike the read endpoints)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found or outside the key's scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}}},"paths":{"/webhooks/{id}":{"delete":{"summary":"Delete a webhook endpoint (cascades its deliveries)","description":"Requires `webhooks:manage`.","operationId":"deleteWebhook","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```

## Update a webhook endpoint

> Requires \`webhooks:manage\`. Any subset of name / url / events / is\_active.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Webhooks","description":"Manage outbound webhook endpoints and inspect deliveries. Account-scoped: these endpoints ignore the key's node binding (unlike the read endpoints)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"WebhookEndpointInput":{"type":"object","required":["name","url","events"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"url":{"type":"string","format":"uri","description":"Delivery URL. Must be http(s); private/reserved addresses are rejected at delivery time."},"events":{"type":"array","minItems":1,"items":{"type":"string","enum":["review.received","review.replied","review.alert_triggered","survey.response_completed","guest.created","guest.unsubscribed"]}},"is_active":{"type":"boolean","description":"PATCH only — pause/resume deliveries without deleting the endpoint."}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"BadRequest":{"description":"Invalid request.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found or outside the key's scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/webhooks/{id}":{"patch":{"summary":"Update a webhook endpoint","description":"Requires `webhooks:manage`. Any subset of name / url / events / is_active.","operationId":"updateWebhook","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointInput"}}}},"responses":{"200":{"description":"OK"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```

## Recent delivery attempts for one endpoint (newest first)

> Requires \`webhooks:manage\`.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Webhooks","description":"Manage outbound webhook endpoints and inspect deliveries. Account-scoped: these endpoints ignore the key's node binding (unlike the read endpoints)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"schemas":{"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"event_type":{"type":"string"},"status":{"type":"string","enum":["pending","delivered","failed"]},"attempt_count":{"type":"integer"},"last_status_code":{"type":["integer","null"]},"last_attempt_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found or outside the key's scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/webhooks/{id}/deliveries":{"get":{"summary":"Recent delivery attempts for one endpoint (newest first)","description":"Requires `webhooks:manage`.","operationId":"listWebhookDeliveries","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","required":["data"],"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```

## Send a test \`ping\` delivery to one endpoint

> Requires \`webhooks:manage\`. Queues a signed ping regardless of the endpoint's subscribed events.

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"tags":[{"name":"Webhooks","description":"Manage outbound webhook endpoints and inspect deliveries. Account-scoped: these endpoints ignore the key's node binding (unlike the read endpoints)."}],"servers":[{"url":"https://app.customer-alliance.com/api/v4","description":"Production"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key created in Settings → API (`ca_live_…`)."}},"responses":{"Unauthorized":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"The key lacks the required scope, or the account's plan does not include it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"NotFound":{"description":"Not found or outside the key's scope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}}},"paths":{"/webhooks/{id}/test":{"post":{"summary":"Send a test `ping` delivery to one endpoint","description":"Requires `webhooks:manage`. Queues a signed ping regardless of the endpoint's subscribed events.","operationId":"testWebhook","tags":["Webhooks"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"OK — ping queued."},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}}}}
```


# Webhook Events

The signed events Customer Alliance POSTs to your endpoints.

{% openapi-webhook spec="API-Guests" name="review\.received" method="post" %}
[API-Guests](https://4401d86825a13bf607936cc3a9f3897a.r2.cloudflarestorage.com/gitbook-x-prod-openapi/raw/4f62fffd1bf8dce16d09c0fbbcf4c7cd144825718bcf4ed44eeb0bb0ecbae7d4.json?X-Amz-Algorithm=AWS4-HMAC-SHA256\&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD\&X-Amz-Credential=dce48141f43c0191a2ad043a6888781c%2F20260913%2Fauto%2Fs3%2Faws4_request\&X-Amz-Date=20260913T231733Z\&X-Amz-Expires=172800\&X-Amz-Signature=586c3469f64d556f42849012e5656a1ade773aa40cc88117e5d7b787862a2f19\&X-Amz-SignedHeaders=host\&x-amz-checksum-mode=ENABLED\&x-id=GetObject)
{% endopenapi-webhook %}

{% openapi-webhook spec="API-Guests" name="review\.replied" method="post" %}
[API-Guests](https://4401d86825a13bf607936cc3a9f3897a.r2.cloudflarestorage.com/gitbook-x-prod-openapi/raw/4f62fffd1bf8dce16d09c0fbbcf4c7cd144825718bcf4ed44eeb0bb0ecbae7d4.json?X-Amz-Algorithm=AWS4-HMAC-SHA256\&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD\&X-Amz-Credential=dce48141f43c0191a2ad043a6888781c%2F20260913%2Fauto%2Fs3%2Faws4_request\&X-Amz-Date=20260913T231733Z\&X-Amz-Expires=172800\&X-Amz-Signature=586c3469f64d556f42849012e5656a1ade773aa40cc88117e5d7b787862a2f19\&X-Amz-SignedHeaders=host\&x-amz-checksum-mode=ENABLED\&x-id=GetObject)
{% endopenapi-webhook %}

{% openapi-webhook spec="API-Guests" name="review\.alert\_triggered" method="post" %}
[API-Guests](https://4401d86825a13bf607936cc3a9f3897a.r2.cloudflarestorage.com/gitbook-x-prod-openapi/raw/4f62fffd1bf8dce16d09c0fbbcf4c7cd144825718bcf4ed44eeb0bb0ecbae7d4.json?X-Amz-Algorithm=AWS4-HMAC-SHA256\&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD\&X-Amz-Credential=dce48141f43c0191a2ad043a6888781c%2F20260913%2Fauto%2Fs3%2Faws4_request\&X-Amz-Date=20260913T231733Z\&X-Amz-Expires=172800\&X-Amz-Signature=586c3469f64d556f42849012e5656a1ade773aa40cc88117e5d7b787862a2f19\&X-Amz-SignedHeaders=host\&x-amz-checksum-mode=ENABLED\&x-id=GetObject)
{% endopenapi-webhook %}

{% openapi-webhook spec="API-Guests" name="survey.response\_completed" method="post" %}
[API-Guests](https://4401d86825a13bf607936cc3a9f3897a.r2.cloudflarestorage.com/gitbook-x-prod-openapi/raw/4f62fffd1bf8dce16d09c0fbbcf4c7cd144825718bcf4ed44eeb0bb0ecbae7d4.json?X-Amz-Algorithm=AWS4-HMAC-SHA256\&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD\&X-Amz-Credential=dce48141f43c0191a2ad043a6888781c%2F20260913%2Fauto%2Fs3%2Faws4_request\&X-Amz-Date=20260913T231733Z\&X-Amz-Expires=172800\&X-Amz-Signature=586c3469f64d556f42849012e5656a1ade773aa40cc88117e5d7b787862a2f19\&X-Amz-SignedHeaders=host\&x-amz-checksum-mode=ENABLED\&x-id=GetObject)
{% endopenapi-webhook %}

{% openapi-webhook spec="API-Guests" name="guest.created" method="post" %}
[API-Guests](https://4401d86825a13bf607936cc3a9f3897a.r2.cloudflarestorage.com/gitbook-x-prod-openapi/raw/4f62fffd1bf8dce16d09c0fbbcf4c7cd144825718bcf4ed44eeb0bb0ecbae7d4.json?X-Amz-Algorithm=AWS4-HMAC-SHA256\&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD\&X-Amz-Credential=dce48141f43c0191a2ad043a6888781c%2F20260913%2Fauto%2Fs3%2Faws4_request\&X-Amz-Date=20260913T231733Z\&X-Amz-Expires=172800\&X-Amz-Signature=586c3469f64d556f42849012e5656a1ade773aa40cc88117e5d7b787862a2f19\&X-Amz-SignedHeaders=host\&x-amz-checksum-mode=ENABLED\&x-id=GetObject)
{% endopenapi-webhook %}

{% openapi-webhook spec="API-Guests" name="guest.unsubscribed" method="post" %}
[API-Guests](https://4401d86825a13bf607936cc3a9f3897a.r2.cloudflarestorage.com/gitbook-x-prod-openapi/raw/4f62fffd1bf8dce16d09c0fbbcf4c7cd144825718bcf4ed44eeb0bb0ecbae7d4.json?X-Amz-Algorithm=AWS4-HMAC-SHA256\&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD\&X-Amz-Credential=dce48141f43c0191a2ad043a6888781c%2F20260913%2Fauto%2Fs3%2Faws4_request\&X-Amz-Date=20260913T231733Z\&X-Amz-Expires=172800\&X-Amz-Signature=586c3469f64d556f42849012e5656a1ade773aa40cc88117e5d7b787862a2f19\&X-Amz-SignedHeaders=host\&x-amz-checksum-mode=ENABLED\&x-id=GetObject)
{% endopenapi-webhook %}


# Models

## The Error object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}}}}}
```

## The Review object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"Review":{"type":"object","description":"A collected review. Hidden reviews are included and flagged `is_hidden`; the owner's own reply text is `owner_reply`.","properties":{"id":{"type":"string","format":"uuid"},"platform":{"type":"string"},"external_id":{"type":"string","description":"The review's id on the source platform."},"author":{"type":["string","null"],"description":"Null when the review is anonymised."},"rating":{"type":["number","null"],"description":"Rating on a 0–5 scale."},"rating_normalized":{"type":["number","null"],"description":"Rating normalised to 0–100."},"title":{"type":["string","null"]},"text":{"type":["string","null"]},"url":{"type":["string","null"]},"language":{"type":["string","null"]},"sentiment":{"type":["string","null"],"description":"positive | neutral | negative | null."},"owner_reply":{"type":["string","null"]},"replied_at":{"type":["string","null"],"format":"date-time"},"reviewed_at":{"type":"string","format":"date-time"},"node_id":{"type":"string","format":"uuid"},"is_hidden":{"type":"boolean"}}}}}}
```

## The AnalyticsScores object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"AnalyticsScores":{"type":"object","description":"Aggregate reputation scores for the key's node subtree.","properties":{"node_id":{"type":"string","format":"uuid"},"overall_score":{"type":["number","null"],"description":"Overall score on a 0–5 scale (one decimal)."},"overall_score_normalized":{"type":["number","null"],"description":"Overall score normalised to 0–100 (may carry one decimal; round for whole-percent display)."},"total_reviews":{"type":"integer"},"recommend_rate":{"type":["number","null"]},"response_rate":{"type":["number","null"]},"platform_scores":{"type":"array","items":{"type":"object","properties":{"platform":{"type":"string"},"score":{"type":["number","null"],"description":"Platform score on a 0–5 scale (one decimal)."},"score_normalized":{"type":["number","null"],"description":"Platform score normalised to 0–100 (may carry one decimal; round for whole-percent display)."},"reviews":{"type":"integer"}}}}}}}}}
```

## The Node object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"Node":{"type":"object","description":"A hierarchy node (business unit / property) within the key's scope.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"parent_id":{"type":["string","null"],"format":"uuid"},"type":{"type":"string"}}}}}}
```

## The Survey object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"Survey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"node_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"primary_language":{"type":"string"},"published_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The SurveyDetail object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"SurveyDetail":{"allOf":[{"$ref":"#/components/schemas/Survey"},{"type":"object","properties":{"published_definition":{"type":"object","description":"The published survey definition (pages/questions)."}}}]},"Survey":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"description":{"type":"string"},"node_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"primary_language":{"type":"string"},"published_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The SurveyResponse object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"SurveyResponse":{"type":"object","description":"A survey response. Respondent PII (email, personalisation context, tokens) is never exposed.","properties":{"id":{"type":"string","format":"uuid"},"survey_id":{"type":"string","format":"uuid"},"node_id":{"type":"string","format":"uuid"},"status":{"type":"string"},"channel":{"type":"string"},"overall_score":{"type":["number","null"]},"started_at":{"type":"string","format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The SurveyAnswer object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"SurveyAnswer":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"question_id":{"type":"string"},"question_path":{"type":"string"},"question_type":{"type":"string"},"value":{"description":"The answer value; shape depends on question_type."},"numeric_value":{"type":["number","null"]},"comment":{"type":["string","null"]},"answered_at":{"type":"string","format":"date-time"}}}}}}
```

## The Recommendation object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"Recommendation":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"title":{"type":"string"},"body":{"type":"string"},"priority":{"type":"string"},"impact":{"type":["string","null"]},"canonical_label_id":{"type":["string","null"],"format":"uuid"},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The Mention object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"Mention":{"type":"object","description":"A topic mention extracted from a review. `is_hidden` mirrors the source review's flag.","properties":{"id":{"type":"string","format":"uuid"},"review_id":{"type":"string","format":"uuid"},"source_field":{"type":"string"},"text":{"type":"string"},"sentiment":{"type":["string","null"]},"canonical_label_id":{"type":"string","format":"uuid"},"custom_category_id":{"type":["string","null"],"format":"uuid"},"confidence":{"type":["number","null"]},"is_hidden":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The Topic object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"Topic":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"language":{"type":["string","null"]},"parent_label_id":{"type":["string","null"],"format":"uuid"}}}}}}
```

## The WhoAmI object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"WhoAmI":{"type":"object","properties":{"account_id":{"type":"string","format":"uuid"},"node_id":{"type":"string","format":"uuid"},"scopes":{"type":"array","items":{"type":"string"}}}}}}}
```

## The WebhookEndpoint object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"WebhookEndpoint":{"type":"object","description":"A registered outbound webhook endpoint. The signing secret is never returned after creation.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["review.received","review.replied","review.alert_triggered","survey.response_completed","guest.created","guest.unsubscribed"]}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}
```

## The WebhookEndpointInput object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"WebhookEndpointInput":{"type":"object","required":["name","url","events"],"properties":{"name":{"type":"string","minLength":1,"maxLength":120},"url":{"type":"string","format":"uri","description":"Delivery URL. Must be http(s); private/reserved addresses are rejected at delivery time."},"events":{"type":"array","minItems":1,"items":{"type":"string","enum":["review.received","review.replied","review.alert_triggered","survey.response_completed","guest.created","guest.unsubscribed"]}},"is_active":{"type":"boolean","description":"PATCH only — pause/resume deliveries without deleting the endpoint."}}}}}}
```

## The WebhookEndpointCreated object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"WebhookEndpointCreated":{"type":"object","required":["secret","endpoint"],"properties":{"secret":{"type":"string","description":"The signing secret (`whsec_…`). Returned exactly once — store it now; you cannot retrieve it again."},"endpoint":{"$ref":"#/components/schemas/WebhookEndpoint"}}},"WebhookEndpoint":{"type":"object","description":"A registered outbound webhook endpoint. The signing secret is never returned after creation.","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"url":{"type":"string","format":"uri"},"events":{"type":"array","items":{"type":"string","enum":["review.received","review.replied","review.alert_triggered","survey.response_completed","guest.created","guest.unsubscribed"]}},"is_active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}
```

## The WebhookDelivery object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"event_type":{"type":"string"},"status":{"type":"string","enum":["pending","delivered","failed"]},"attempt_count":{"type":"integer"},"last_status_code":{"type":["integer","null"]},"last_attempt_at":{"type":["string","null"],"format":"date-time"},"created_at":{"type":"string","format":"date-time"}}}}}}
```

## The ReservationInput object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"ReservationInput":{"type":"object","required":["external_id"],"description":"A reservation (stay) to create or update for the guest, mirroring what a PMS integration syncs. Identity is (property, external_id): a repeat push of the same external_id updates the stay in place (e.g. a status change from booked to checked_out) rather than creating a duplicate. Pushing stays lets you build audiences on stay status, booking channel, and recently-ended stays.","properties":{"external_id":{"type":"string","minLength":1,"maxLength":255,"description":"Required — your booking reference; the stay's dedupe key."},"status":{"type":"string","enum":["booked","arrived","checked_out","cancelled","no_show"],"description":"Canonical stay status. Defaults to `booked` when omitted."},"raw_status":{"type":"string","maxLength":120,"description":"Your own status label, kept verbatim for display."},"check_in":{"type":"string","format":"date-time","description":"Stay start. ISO-8601 date or date-time."},"check_out":{"type":"string","format":"date-time","description":"Stay end. ISO-8601 date or date-time — drives the recently-ended-stay audience filter."},"checked_in_at":{"type":"string","format":"date-time"},"checked_out_at":{"type":"string","format":"date-time"},"channel":{"type":"string","maxLength":120,"description":"Booking origin, e.g. booking.com."},"custom_fields":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Merged into the stay's custom fields; omitted keys are preserved."}}}}}}
```

## The GuestPushInput object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"GuestPushInput":{"type":"object","required":["email"],"description":"A guest to create or update at one property. Identity is (property, email): a repeat push of the same email to the same property updates that guest rather than creating a duplicate. Optionally include a `reservation` to upsert the guest's stay in the same call.","properties":{"property_id":{"type":"string","format":"uuid","description":"The property (leaf node) to attach the guest to. Optional when the key is bound to a single property; required when the key is bound to a group. Must be a property within the key's node scope."},"email":{"type":"string","format":"email","maxLength":320,"description":"Required — normalized (trimmed + lower-cased) and used as the dedupe key."},"salutation":{"type":"string","maxLength":50},"first_name":{"type":"string","maxLength":200},"last_name":{"type":"string","maxLength":200},"phone":{"type":"string","maxLength":40},"country_code":{"type":"string","minLength":2,"maxLength":2,"description":"ISO 3166-1 alpha-2 (e.g. DE)."},"language_code":{"type":"string","maxLength":5,"description":"e.g. de or de-DE."},"custom_fields":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Merged into the guest's custom fields; omitted keys are preserved."},"marketing_consent":{"type":"boolean","description":"When set, records invitation-email consent. An explicit prior withdrawal is never overridden by a grant (GDPR)."},"reservation":{"$ref":"#/components/schemas/ReservationInput"}}},"ReservationInput":{"type":"object","required":["external_id"],"description":"A reservation (stay) to create or update for the guest, mirroring what a PMS integration syncs. Identity is (property, external_id): a repeat push of the same external_id updates the stay in place (e.g. a status change from booked to checked_out) rather than creating a duplicate. Pushing stays lets you build audiences on stay status, booking channel, and recently-ended stays.","properties":{"external_id":{"type":"string","minLength":1,"maxLength":255,"description":"Required — your booking reference; the stay's dedupe key."},"status":{"type":"string","enum":["booked","arrived","checked_out","cancelled","no_show"],"description":"Canonical stay status. Defaults to `booked` when omitted."},"raw_status":{"type":"string","maxLength":120,"description":"Your own status label, kept verbatim for display."},"check_in":{"type":"string","format":"date-time","description":"Stay start. ISO-8601 date or date-time."},"check_out":{"type":"string","format":"date-time","description":"Stay end. ISO-8601 date or date-time — drives the recently-ended-stay audience filter."},"checked_in_at":{"type":"string","format":"date-time"},"checked_out_at":{"type":"string","format":"date-time"},"channel":{"type":"string","maxLength":120,"description":"Booking origin, e.g. booking.com."},"custom_fields":{"type":"object","additionalProperties":{"type":["string","number","boolean"]},"description":"Merged into the stay's custom fields; omitted keys are preserved."}}}}}}
```

## The Reservation object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"Reservation":{"type":"object","description":"A reservation (stay) as stored. Returned inside a `Guest` when the push included a `reservation`.","properties":{"id":{"type":"string","format":"uuid"},"external_id":{"type":"string"},"status":{"type":"string","enum":["booked","arrived","checked_out","cancelled","no_show"]},"raw_status":{"type":["string","null"]},"check_in":{"type":["string","null"],"format":"date-time"},"check_out":{"type":["string","null"],"format":"date-time"},"checked_in_at":{"type":["string","null"],"format":"date-time"},"checked_out_at":{"type":["string","null"],"format":"date-time"},"channel":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}
```

## The Guest object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"Guest":{"type":"object","description":"A guest record as stored. `source` is `api` for guests created through this endpoint.","properties":{"id":{"type":"string","format":"uuid"},"property_id":{"type":"string","format":"uuid"},"source":{"type":"string"},"email":{"type":["string","null"]},"salutation":{"type":["string","null"]},"first_name":{"type":["string","null"]},"last_name":{"type":["string","null"]},"phone":{"type":["string","null"]},"country_code":{"type":["string","null"]},"language_code":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"},"reservation":{"$ref":"#/components/schemas/Reservation","description":"Present only when the push included a reservation."}}},"Reservation":{"type":"object","description":"A reservation (stay) as stored. Returned inside a `Guest` when the push included a `reservation`.","properties":{"id":{"type":"string","format":"uuid"},"external_id":{"type":"string"},"status":{"type":"string","enum":["booked","arrived","checked_out","cancelled","no_show"]},"raw_status":{"type":["string","null"]},"check_in":{"type":["string","null"],"format":"date-time"},"check_out":{"type":["string","null"],"format":"date-time"},"checked_in_at":{"type":["string","null"],"format":"date-time"},"checked_out_at":{"type":["string","null"],"format":"date-time"},"channel":{"type":["string","null"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}}}}}
```

## The WebhookEnvelope object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"WebhookEnvelope":{"type":"object","description":"Every delivery POSTs this envelope. Verify the `X-CA-Signature: sha256=<hex>` header — an HMAC-SHA256 of the raw request body keyed by your endpoint's signing secret — before trusting it.","required":["id","event","created_at","data"],"properties":{"id":{"type":"string","format":"uuid","description":"Delivery id (also in the X-CA-Delivery-Id header); stable across retries — use it to dedupe."},"event":{"type":"string","enum":["review.received","review.replied","review.alert_triggered","survey.response_completed","guest.created","guest.unsubscribed"]},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","description":"The event payload; shape depends on `event` (see the schemas below)."}}}}}}
```

## The ReviewReceivedData object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"ReviewReceivedData":{"type":"object","properties":{"review_id":{"type":"string","format":"uuid"},"node_id":{"type":"string","format":"uuid"},"platform":{"type":"string"}}}}}}
```

## The ReviewRepliedData object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"ReviewRepliedData":{"type":"object","properties":{"review_id":{"type":"string","format":"uuid"},"node_id":{"type":"string","format":"uuid"},"reply_id":{"type":"string","format":"uuid"}}}}}}
```

## The ReviewAlertTriggeredData object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"ReviewAlertTriggeredData":{"type":"object","description":"Note: this payload uses camelCase keys (historical).","properties":{"alertId":{"type":"string","format":"uuid"},"ruleId":{"type":"string","format":"uuid"},"type":{"type":"string"},"message":{"type":"string"},"context":{"type":"object"}}}}}}
```

## The SurveyResponseCompletedData object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"SurveyResponseCompletedData":{"type":"object","properties":{"survey_id":{"type":"string","format":"uuid"},"response_id":{"type":"string","format":"uuid"},"node_id":{"type":"string","format":"uuid"},"overall_score":{"type":["number","null"]}}}}}}
```

## The GuestCreatedData object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"GuestCreatedData":{"type":"object","properties":{"guest_id":{"type":"string","format":"uuid"},"node_id":{"type":"string","format":"uuid"},"source":{"type":"string"}}}}}}
```

## The GuestUnsubscribedData object

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.0.0"},"components":{"schemas":{"GuestUnsubscribedData":{"type":"object","properties":{"guest_id":{"type":["string","null"],"format":"uuid"},"unsubscribe_id":{"type":"string","format":"uuid"}}}}}}
```


