Annwn Docs Docs annwn.app
All documentation

Partner API

A read-only REST API to query stays data, secured with property-scoped API keys.

Overview

The Partner API is a REST API that lets integration partners query stays (bookings) and rate plans for a property, and push prices and restrictions to its rate plans. Every API key belongs to a single property and, like a team member, is given roles that decide what it can read and change. All responses are JSON.

Authentication

Property administrators create API keys under Configuration → Integration API Keys in the host dashboard. Keys start with sa_ and the full secret is shown only once, at creation time — store it securely. Keys can be given an optional expiration date and can be revoked at any time.

A key is assigned API roles when it is created. Administrators create these roles on the API keys page; they are separate from team members' roles and carry only permissions the API uses. A key can only use the permissions that both its roles and the current roles of the person who created it grant, and only while that person still has access to the property. If their permissions change later, the key's access follows immediately.

Send the key on every request, either as a bearer token or in the X-Api-Key header:

Authorization: Bearer sa_your_api_key
# or
X-Api-Key: sa_your_api_key

Requests with a missing, invalid, expired, or revoked key receive 401 Unauthorized. A valid key whose permissions don't cover the endpoint (reading reservations for stays, viewing rates for rate plans and their rates, managing rates for updates) receives 403 Forbidden.

Base URL

https://stay.annwn.app/api/partner/v1

List stays

GET /stays

Returns the property's stays, newest bookings first. Cancelled stays are included (with "status": "cancelled") so you can reconcile changes; preview and merged-away duplicate stays are never returned.

Query parameters

Parameter Type Description
check_in_from date Only stays whose check-in date (first night) is on or after this ISO 8601 date.
check_in_to date Only stays whose check-in date is on or before this ISO 8601 date.
booked_from date Only stays booked on or after this date (day boundaries in the property's timezone).
booked_to date Only stays booked on or before this date.
page integer Page number, starting at 1 (default 1).
per_page integer Results per page (default 25, maximum 100).

Example request

curl -H "Authorization: Bearer sa_your_api_key" \
  "https://stay.annwn.app/api/partner/v1/stays?check_in_from=2026-09-01&check_in_to=2026-09-30&per_page=50"

Example response

{
  "data": [
    {
      "id": "5f0c1f6e-8a4b-4c2d-9e3f-1a2b3c4d5e6f",
      "status": "confirmed",
      "cancelled_at": null,
      "no_show": false,
      "check_in": "2026-09-10",
      "check_out": "2026-09-12",
      "number_of_nights": 2,
      "guest_count": 2,
      "booked_at": "2026-07-01T12:34:56.000Z",
      "property": { "id": 12, "name": "Casa Oliva" },
      "guest_notes": null,
      "special_requests": "Late arrival",
      "guests": [
        { "name": "Ada Lovelace", "country_code": "GB", "city": "London",
          "checked_in": false, "checked_out": false, "is_contact": true }
      ],
      "units": [
        { "unit_type": "Double Room", "unit": "Room 12",
          "check_in": "2026-09-10", "check_out": "2026-09-12", "pets": 0 }
      ],
      "channel": { "name": "booking", "display_name": "Booking.com", "remote_id": "4012345678" },
      "created_at": "2026-07-01T12:34:56.000Z",
      "updated_at": "2026-07-01T12:34:56.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 50,
    "total_count": 1,
    "total_pages": 1,
    "next_page": null,
    "prev_page": null
  }
}

Get a stay

GET /stays/:id

Fetches a single stay by its id (a UUID, as returned by the list endpoint). Returns 404 when the stay does not exist or belongs to a different property. The response wraps the stay object in a data key.

The stay object

The reservation fields below are present for every key that can read the property's reservations. Two additions appear only if the key's permissions include them: guest contact details and financials.

Reservation fields (always present)

Field Description
idStable UUID for the stay.
statusOne of confirmed, checked_in, checked_out, cancelled.
cancelled_atTimestamp of cancellation, or null.
no_showTrue when the cancellation was recorded as a no-show; status remains cancelled.
check_in / check_outStay dates (check_out is the departure day).
number_of_nightsNight count.
guest_countNumber of guests on the stay.
booked_atWhen the booking entered the system.
propertyThe property: { "id", "name" }.
guest_notes / special_requestsNotes and special requests recorded on the booking, or null.
guestsOne entry per guest: name, country_code, city, checked_in, checked_out, and is_contact (true for the booking contact).
unitsOne entry per booked room: unit_type (room category name), unit (assigned room name, null while unassigned), the room's own check_in / check_out dates, and pets booked for it.
channelBooking source: name (e.g. booking, airbnb, direct), display_name, and remote_id (the OTA reservation ID, null for direct bookings).
created_at / updated_atRecord timestamps.

Guest contact details permission: View Customer Contact

Adds email and phone to each entry in guests, and channel_email (the guest's email address for the booking, often an OTA relay address) to channel.

Financials permission: View Financial Information

Adds a financials object. All amounts are integers in the smallest currency unit (cents).

"financials": {
  "currency": "USD",
  "price_total_cents": 48000,
  "payments_total_cents": 20000,
  "pending_balance_cents": 28000,
  "deposit_amount_cents": 5000,
  "refundable_total_cents": 43000,
  "ota_commission_cents": 7200,
  "payment_state": "partial"
}

payment_state is paid, partial, unpaid, or null when the stay has no charges yet. deposit_amount_cents is null when no deposit is configured.

List rate plans permission: View Rates & Availability

GET /rate_plans

Returns every rate plan of the property. Use a rate plan's id (a UUID) to read or update its rates and availability. Keys that can manage rates can also list them.

Field Description
idStable UUID for the rate plan.
nameThe rate plan's name.
unit_typeName of the room category it prices, or null.
sell_modeper_room or per_person. Prices can only be pushed to per_room plans.
currencyISO 4217 currency code of its prices.
derived_fromThe id of the rate plan this one is derived from, or null.

Example response

{
  "data": [
    {
      "id": "8d3e2b1a-6c4f-4e9a-b2d7-0f1e2d3c4b5a",
      "name": "Best Available Rate",
      "unit_type": "Double Room",
      "sell_mode": "per_room",
      "currency": "USD",
      "derived_from": null
    }
  ]
}

Get rates & availability permission: View Rates & Availability

GET /rate_plans/:id/rates_and_availability

Returns a rate plan's price and restrictions for each date from start_date through end_date, both included, one entry per date, earliest first. The values are what the plan sells at right now, after every adjustment the property has set up, so a date reads back what an update set once that update is completed. Dates in the past, or more than 730 days ahead, are left out.

Query parameters

Parameter Type Description
start_datedate, requiredFirst date (ISO 8601).
end_datedate, requiredLast date, included. On or after start_date.
pageintegerPage number, starting at 1 (default 1).
per_pageintegerDates per page (default 100, maximum 366).

Date fields

The fields are the ones an update takes, with every restriction always present.

Field Description
dateThe date (ISO 8601).
priceNightly price in the rate plan's currency, in major units (e.g. 149.5). Null for per_person plans.
occupancy_pricesper_person plans only, null otherwise: the nightly price by number of guests, as [{ "occupancy", "price" }].
stop_sellTrue when the date is closed for sale.
closed_to_arrivalTrue when no check-ins are allowed on the date.
closed_to_departureTrue when no check-outs are allowed on the date.
min_stay_arrivalMinimum nights for stays arriving on the date.
min_stay_throughMinimum nights for any stay including the date.
max_stayMaximum nights; 0 means no limit.

Example request

curl -H "Authorization: Bearer sa_your_api_key" \
  "https://stay.annwn.app/api/partner/v1/rate_plans/8d3e2b1a-6c4f-4e9a-b2d7-0f1e2d3c4b5a/rates_and_availability?start_date=2026-12-20&end_date=2027-01-31&per_page=31"

Example response

{
  "data": [
    {
      "date": "2026-12-20",
      "price": 189.0,
      "occupancy_prices": null,
      "stop_sell": false,
      "closed_to_arrival": false,
      "closed_to_departure": false,
      "min_stay_arrival": 3,
      "min_stay_through": 1,
      "max_stay": 0
    }
  ],
  "pagination": {
    "page": 1,
    "per_page": 31,
    "total_count": 43,
    "total_pages": 2,
    "next_page": 2,
    "prev_page": null
  }
}

Update rates & availability permission: Manage Rates & Availability

PATCH /rate_plans/:id/rates_and_availability

Sets prices and restrictions on a rate plan for one or more date ranges. The body is a JSON array of periods. Each period covers every date from start_date through end_date, both included, so a single date is a period whose start_date equals its end_date.

This is a partial update. A field you leave out is not touched. A restriction sent as null is cleared: stop_sell, closed_to_arrival and closed_to_departure become false, both minimum stays become 1 and max_stay becomes 0 (no limit). price can't be null.

Field Type Description
start_datedate, requiredFirst date of the period (ISO 8601).
end_datedate, requiredLast date of the period, included. On or after start_date.
pricenumberNightly price in the rate plan's currency, in major units (e.g. 149.50). Positive; per_room plans only.
stop_sellboolean or nullCloses the date for sale.
closed_to_arrivalboolean or nullNo check-ins on the date.
closed_to_departureboolean or nullNo check-outs on the date.
min_stay_arrivalinteger or nullMinimum nights for stays arriving on the date (at least 1).
min_stay_throughinteger or nullMinimum nights for any stay including the date (at least 1).
max_stayinteger or nullMaximum nights; 0 means no limit.
  • Each period must set at least one field besides its dates. Unknown fields are rejected. Up to 1000 periods per request.
  • When periods overlap, the later one in the array wins, field by field.
  • Dates in the past, or more than 730 days ahead, are skipped.
  • Only dates whose current value differs are changed, so re-sending the same values changes nothing.
  • The latest change wins: a later edit by the property's staff overrides your value, and your next update overrides theirs.

Example request

curl -X PATCH -H "Authorization: Bearer sa_your_api_key" -H "Content-Type: application/json" \
  "https://stay.annwn.app/api/partner/v1/rate_plans/8d3e2b1a-6c4f-4e9a-b2d7-0f1e2d3c4b5a/rates_and_availability" \
  -d '[
    { "start_date": "2026-12-20", "end_date": "2027-01-03", "price": 189.00, "min_stay_arrival": 3 },
    { "start_date": "2026-12-24", "end_date": "2026-12-24", "closed_to_arrival": true },
    { "start_date": "2027-01-04", "end_date": "2027-01-31", "min_stay_arrival": null }
  ]'

Response

Updates are applied asynchronously. A valid request returns 202 Accepted with the update object (see Check an update) and a Location header pointing to it.

{
  "data": {
    "id": "3f9c7a2e-1b5d-4c8e-9a6f-2d4b6c8e0a1f",
    "rate_plan_id": "8d3e2b1a-6c4f-4e9a-b2d7-0f1e2d3c4b5a",
    "status": "pending",
    "submitted_at": "2026-10-08T15:04:05.000Z",
    "completed_at": null,
    "last_error": null
  }
}

Check an update permission: Manage Rates & Availability

GET /rate_updates/:id

Returns the update object for an update sent to this property, by the id the update request returned. Poll it until status is completed.

Field Description
idUUID of the update.
rate_plan_idThe rate plan it was sent to.
statuspending (queued), retrying (an attempt failed and is retried automatically), or completed.
submitted_at / completed_atWhen the update was accepted and when it finished; completed_at is null until then.
last_errorWhile retrying: { "code": "processing_failed", "at" }. Null otherwise.

completed means every price and restriction in the update has been saved and is used for new bookings. Pushing the new values to connected sales channels follows within a few minutes.

Example request

curl -H "Authorization: Bearer sa_your_api_key" \
  "https://stay.annwn.app/api/partner/v1/rate_updates/3f9c7a2e-1b5d-4c8e-9a6f-2d4b6c8e0a1f"

Errors

Errors share one JSON shape:

{ "error": { "code": "invalid_api_key", "message": "Missing, invalid, revoked, or expired API key" } }
HTTP status Code Meaning
401invalid_api_keyMissing, unknown, revoked, or expired API key.
403insufficient_permissionsThe key's roles, or the current permissions of the person who created it, don't allow this endpoint.
404not_foundThe stay, rate plan, or rate update does not exist or belongs to another property.
422invalid_parameterA date parameter is missing, not a valid ISO 8601 date or out of order, or a rates and availability body is malformed. The message names the parameter, or the period and field.
429—Rate limit exceeded; retry later.

Rate limits

Requests are limited to 120 per minute per API key. Exceeding the limit returns 429; wait and retry.