Customer Alliance Reviews API
Collected reviews and owner replies, and pushing reviews in from other platforms.
Collected reviews and owner replies, and pushing reviews in from other platforms.
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-reviews-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 Reviews 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: Reviews
description: Collected reviews and owner replies, and pushing reviews in from other platforms.
paths:
/reviews:
get:
summary: List reviews for the key's node subtree (newest first)
description: 'Requires `reviews:read`. Returns every review on your account''s subtree, including platforms that may not be redisplayed publicly and first-party sources — this is your own data, the same set your dashboard shows. If you are rendering the response to the public, pass `?display=public`.
**Responses to your Customer Alliance surveys are never returned here**, with or without `display=public`. A survey-bridged review is a representation of a survey response that `GET /surveys/{id}/responses` already serves; 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 (`POST /survey-responses` or a CSV upload) are the exception: the surveys endpoints do not serve them, so they are returned here as `upload` reviews.
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'
- $ref: '#/components/parameters/display'
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'
post:
summary: Push reviews from another platform into a property
description: 'Requires `reviews:write`. Writes up to 100 reviews from a platform Customer Alliance has no connector for, under a named `source` — the automated form of the review inbox''s CSV upload. They reach the inbox, sentiment, AI insights, alerts and the `review.received` webhook, and are kept out of reputation scores and public surfaces. See **Pushing reviews and survey responses**.
Identity is (source, `external_id`): a repeat push updates the stored review rather than duplicating it. Validation is all-or-nothing (`400`, naming the first failing field). Each item''s outcome is in `results`; an item that cannot be written is `skipped` with a `reason`, never dropped.'
operationId: pushReviews
tags:
- Reviews
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ReviewPushInput'
responses:
'200':
description: The batch was processed; see each item's `status`.
content:
application/json:
schema:
$ref: '#/components/schemas/ReviewPushResult'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'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'
/reviews/{id}:
get:
summary: Get a single review by id
description: 'Requires `reviews:read`. Returns any review on your account''s subtree by default, including hidden ones (marked `is_hidden=true`) and platforms that may not be redisplayed publicly. If you are rendering the review to the public, pass `?display=public`; a review excluded under that opt-in responds `404`.
**Responses to your Customer Alliance surveys are never returned here**, with or without `display=public`. A survey-bridged review is a representation of a survey response that `GET /surveys/{id}/responses` already serves; 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. A survey-bridged review id responds `404` here. (Imported survey responses are `upload` reviews and are returned.)'
operationId: getReview
tags:
- Reviews
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
- $ref: '#/components/parameters/display'
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'
components:
parameters:
limit:
name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 25
display:
name: display
in: query
schema:
type: string
enum:
- public
description: 'Pass `public` when the response will be shown to the public — a custom widget, an embed, anything a site visitor reads. It excludes two groups of reviews:
- **Portals whose content may not be republished.** Some review portals license redisplay of their content as a paid product, or have terms that have not been cleared for it. Those reviews are yours to read, analyse and act on, but not to republish.
- **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`). Legitimate internal signal, but nobody confirmed a guest wrote them.
Only the exact value `public` opts in. Any other value is ignored and you get the default, unfiltered behaviour — a typo must not silently withhold data.
Omit it — the default — to read everything on your account. Responses to your Customer Alliance surveys are never served by these endpoints either way; see `GET /surveys/{id}/responses`.'
cursor:
name: cursor
in: query
schema:
type: string
description: Opaque pagination cursor from a prior response's next_cursor.
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'
NotFound:
description: Not found or outside the key's scope.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
ReviewPushItem:
type: object
required:
- external_id
- rating
- reviewed_at
additionalProperties: false
properties:
external_id:
type: string
minLength: 1
maxLength: 255
description: 'Your id for this review: its identity within the source. A repeat push replaces the stored review; a title or owner reply already stored is kept when the push omits it.'
rating:
type: number
description: 'On the batch''s `rating_scale`: 1–5, 1–10 or 0–100.'
reviewed_at:
anyOf:
- type: string
format: date
- type: string
format: date-time
description: When the review was written. An ISO-8601 date (kept as that calendar day) or a date-time with its zone (`Z` or an offset such as `+02:00`); a date-time without a zone is refused.
title:
type:
- string
- 'null'
maxLength: 500
text:
type:
- string
- 'null'
maxLength: 20000
author_name:
type:
- string
- 'null'
maxLength: 200
description: 'Defaults to the name of the guest matched by `email`, when there is one. Not stored when the hotel has anonymized a review from the same `email`: the item is stored anonymized.'
email:
type:
- string
- 'null'
format: email
description: The guest's email. Links the item to a guest with that email at the property and makes it reachable by GDPR erasure and export; an email erased under GDPR is skipped. Never returned by the read endpoints.
language:
type:
- string
- 'null'
description: e.g. en or en-GB.
owner_reply:
type:
- string
- 'null'
maxLength: 20000
description: A reply already published on the source platform.
owner_replied_at:
anyOf:
- type: string
format: date
- type: string
format: date-time
- type: 'null'
description: When the reply was published. An ISO-8601 date (kept as that calendar day) or a date-time with its zone (`Z` or an offset such as `+02:00`); a date-time without a zone is refused.
anonymized:
type: boolean
description: The source says the reviewer is anonymous; no author name is stored.
ReviewPushResult:
type: object
required:
- source
- property_id
- created
- updated
- skipped
- results
properties:
source:
$ref: '#/components/schemas/PushSource'
property_id:
type: string
format: uuid
created:
type: integer
updated:
type: integer
skipped:
type: integer
results:
type: array
description: One outcome per item, in request order.
items:
$ref: '#/components/schemas/ReviewPushOutcome'
Error:
type: object
required:
- error
properties:
error:
type: string
example:
error: Invalid API key
ReviewPushOutcome:
type: object
required:
- external_id
- status
- review_id
properties:
external_id:
type: string
status:
type: string
enum:
- created
- updated
- skipped
review_id:
type:
- string
- 'null'
format: uuid
description: The stored review; null when skipped.
reason:
type: string
enum:
- erased
- other_property
description: 'Present when `status` is `skipped`. `erased`: the email belongs to a guest erased under GDPR. `other_property`: this external_id already belongs to a different property in this source; a push never moves a record between properties.'
ReviewPushInput:
type: object
required:
- source
- rating_scale
- reviews
additionalProperties: false
description: A batch of reviews for one property, from one source. Validation is all-or-nothing.
properties:
source:
type: string
minLength: 1
maxLength: 120
description: Where the data comes from, e.g. Zenchef. Created on first use and matched case-insensitively after that — the same sources the review inbox's CSV upload offers.
property_id:
type: string
format: uuid
description: The property (leaf node) to write to. Optional when the key is bound to a single property; required when it is bound to a group. Must be within the key's node scope.
rating_scale:
type: integer
enum:
- 5
- 10
- 100
description: The scale every rating in the batch is on.
reviews:
type: array
minItems: 1
maxItems: 100
items:
$ref: '#/components/schemas/ReviewPushItem'
example:
source: Zenchef
property_id: 3fa85f64-5717-4562-b3fc-2c963f66afa6
rating_scale: 5
reviews:
- external_id: zc-1001
rating: 4
reviewed_at: '2026-09-01'
text: Lovely terrace, slow service.
author_name: Jane D.
language: en
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
example: google
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'
description: Where to manage this review on the source platform — not a guest-facing link. May require a portal login and may embed internal property identifiers. Do not republish it publicly.
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
PushSource:
type: object
description: The upload source a push landed under — the same sources the review inbox's CSV upload offers.
properties:
id:
type: string
format: uuid
name:
type: string
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/