> For the complete documentation index, see [llms.txt](https://docs.customer-alliance.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.customer-alliance.com/guests.md).

# 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, \`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\`).

```json
{"openapi":"3.1.0","info":{"title":"Customer Alliance API","version":"4.2.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. **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"}}},"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."}}},"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","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"}}}},"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, `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"}}}}}}
```
