Customer Alliance Guests API
Push guests into a property (write-only; grants no read of guest data).
Push guests into a property (write-only; grants no read of guest data).
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/customer-alliance:customer-alliance-guests-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Customer Alliance Guests API
summary: Review management, surveys & feedback for hospitality — as an API.
version: 4.2.0
description: 'The Customer Alliance API gives you programmatic access to your reviews, surveys, analytics and AI insights, lets you push guests, reviews and survey responses in from your other systems, and lets you subscribe to events via outbound webhooks. ## 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 `. 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`, `reviews:write`, `surveys: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, and **write** endpoints (`POST /guests`, `POST /reviews`, `POST /survey-responses`) write to one property within it. The webhook-management endpoints (`/webhooks*`) are the exception — they operate account-wide and ignore the key''s node binding (see **Webhooks**) — and so are the attribute definitions of `GET /property-attributes`, which describe the account rather than a property (see **Property attributes**). ## Display scope
By default, read endpoints return everything on your account''s subtree. An API key authenticates the account owner reading their own data — the same data the dashboard shows — so nothing is withheld from you. If the response will be shown to the public, pass `?display=public`:
```bash
curl "https://app.customer-alliance.com/api/v4/reviews?display=public" \
-H "Authorization: Bearer ca_live_your_key_here"
```
Supported on `GET /reviews` and `GET /reviews/{id}` (`reviews:read`), and `GET /insights/mentions` (`insights:read`). It excludes two groups: review portals whose content may not be republished (some license redisplay as a paid product, or have terms that have not been cleared for it), and sources whose authorship was never verified — reviews entered by hand (`manual`) and imported ones (`upload`: CSV uploads, and everything pushed through `POST /reviews` or `POST /survey-responses`). Only the exact value `public` opts in. Any other value is ignored and you get the default, unfiltered behaviour — deliberately, so that a typo cannot silently withhold data. Rather than tracking which platforms may be republished yourself, use `?display=public` and let the API apply the current set. > **Upgrading an existing integration.** `GET /reviews` previously withheld these platforms from every caller. It no longer does. If you use it to power anything the public sees, add `?display=public` before upgrading — without it you will begin receiving reviews from platforms that may not be republished. If you use it for internal analytics, reporting or warehousing, you need to do nothing; you simply get more complete data. ### Survey responses
Responses to your Customer Alliance surveys are never returned by `GET /reviews`, `GET /reviews/{id}` or `GET /insights/mentions`, with or without `display=public`. This is not a publishing restriction: a survey-bridged review is a representation of a response that `GET /surveys/{id}/responses` already serves, and returning it from both would make a consumer summing across them count the same feedback twice. To work with survey feedback, use the surveys endpoints. Survey responses imported from another tool — pushed through `POST /survey-responses` or uploaded as a CSV — are the reverse: they are returned by the review endpoints as `upload` reviews, and the surveys endpoints do not list them. Either way, each response is served exactly once.'
termsOfService: https://www.customer-alliance.com/en/terms-and-conditions/
contact:
name: Customer Alliance Support
email: info@customer-alliance.com
url: https://www.customer-alliance.com/
servers:
- url: https://app.customer-alliance.com/api/v4
description: Production
security:
- bearerAuth: []
tags:
- name: Guests
description: Push guests into a property (write-only; grants no read of guest data).
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, `external_id`) when you send your own customer id in `external_id`, so a corrected email updates the same guest; without it, identity is (property, email), so a repeat push of the same email updates that guest (`200`) rather than creating a duplicate (`201` on first create), and a different email is a different guest. 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 (or the email it is being corrected to) has been erased under GDPR, or `reservation.external_id` already belongs to a different guest. Nothing is written.
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'
components:
responses:
Unauthorized:
description: Missing or invalid API key.
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'
BadRequest:
description: Invalid request.
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'
schemas:
GuestPushInput:
type: object
required:
- email
description: 'A guest to create or update at one property. **With `external_id`**, identity is (property, `external_id`): a repeat push with the same `external_id` updates that guest, even when its email changed — this is how you correct an email. **Without `external_id`**, identity is (property, email): a repeat push of the same email updates that guest, and a different email creates a NEW guest. 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.
external_id:
type: string
minLength: 1
maxLength: 255
description: 'Optional — your own customer id. When sent, it is the guest''s identity: re-push the same `external_id` with a corrected `email` and the same guest (and its reservations) gets the new address. The first push carrying an `external_id` for a guest you created earlier without one adopts that guest rather than duplicating it; from then on, keep sending the `external_id`. Two different `external_id`s are two guests, even with the same email.'
email:
type: string
format: email
maxLength: 320
description: 'Required — normalized (trimmed + lower-cased). Without `external_id` it is the guest''s identity: a different email is a different guest.'
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'
example:
property_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6
external_id: CUST-48213
email: jane.doe@example.com
first_name: Jane
last_name: Doe
marketing_consent: true
reservation:
external_id: BK-1001
status: checked_out
check_in: '2026-08-01'
check_out: '2026-08-04'
channel: booking.com
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
- unknown
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
example:
error: Invalid API key
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
example: api
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.
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
- unknown
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.
example:
external_id: BK-1001
status: checked_out
check_in: '2026-08-01'
check_out: '2026-08-04'
channel: booking.com
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: An API key created in Settings → API (`ca_live_…`).
externalDocs:
description: Customer Alliance
url: https://www.customer-alliance.com/