DailyStay API

Take reservations on your own website and keep your systems in step with DailyStay.

Overview

DailyStay has two HTTP APIs. Both speak JSON and share one base URL.

Base URL
https://new.yourdailystay.com/api
APIUse it forAuthCall it from
Public Booking API, /public/booking and /public/paymentsA hotel website: rooms, availability, new reservations, depositsNoneBrowser or server
Partner API, /partnerYour own systems: keeping a website or channel manager in sync (rooms, classes, prices, photos, calendar, bookings), reporting, accountingAPI key in X-Api-Key, scopes per capabilityServer only

Both APIs come with the Max plan. On a lower plan the public booking endpoints answer as if the page did not exist, and API keys are rejected with 401.

Conventions

  • Request and response bodies are JSON. Send Content-Type: application/json.
  • Field names are camelCase. Webhook data is the one exception (see Webhooks).
  • Stay dates are YYYY-MM-DD. Timestamps are ISO 8601 in UTC.
  • Money is a decimal in the property's currency, given as currency (for example EUR).
  • Every response uses the same envelope: check success, then read data or error.
Success
{
  "success": true,
  "data": {
    "...": "..."
  },
  "error": null,
  "pagination": null
}
Error
{
  "success": false,
  "data": null,
  "error": {
    "code": "NOT_FOUND",
    "message": "No such booking.",
    "details": []
  },
  "pagination": null
}

Hotel website quickstart

Building a website for a hotel that runs on DailyStay? This is the whole flow. Reservations made on your site are saved straight into DailyStay, as pending requests the property confirms. No API key is needed for any of it.

  1. The owner switches on the booking page: Property Settings, Features tab, Your own booking page. Until then every public endpoint answers 404.
  2. Find the property id. It is the number at the end of the booking page link on that card (/book/1042). GET /partner/whoami also returns it for any API key.
  3. Load the property and its rooms with GET /public/booking/{propertyId}.
  4. Search a stay with GET /public/booking/{propertyId}/availability.
  5. Book it with POST /public/booking/{propertyId}. The reservation appears in DailyStay as pending and the property is notified.
  6. Optional: take the deposit online with the payment endpoints.
  7. Optional: from your server, keep your own records in step with the Partner API and webhooks.
Browser example
const BASE = 'https://new.yourdailystay.com/api'
const PROPERTY_ID = '1042'

async function call(path, options = {}) {
  const response = await fetch(BASE + path, {
    ...options,
    headers: { 'Content-Type': 'application/json', ...options.headers },
  })
  const body = await response.json()
  if (!body.success) throw new Error(body.error?.message ?? 'HTTP ' + response.status)
  return body.data
}

// 1. What can be booked for these dates, priced
const search = await call(
  '/public/booking/' + PROPERTY_ID + '/availability?checkIn=2026-11-14&checkOut=2026-11-17&guests=2',
)
if (search.rooms.length === 0) console.log(search.notes, search.suggestions)

// 2. Book the first free room
const booking = await call('/public/booking/' + PROPERTY_ID, {
  method: 'POST',
  body: JSON.stringify({
    roomId: search.rooms[0].roomId,
    checkIn: '2026-11-14',
    checkOut: '2026-11-17',
    guests: 2,
    guestName: 'Elira Hoxha',
    guestEmail: 'guest@example.com',
    consentGiven: true,
  }),
})

console.log(booking.reference, booking.status) // "482913" "pending"

Never put an API key in browser code. The public booking endpoints need no key, and the Partner API is for your server only.

Generate an API key

  1. Sign in to DailyStay as the property owner. Managers and staff do not see this panel.
  2. Open Property Settings, Features tab, API and webhooks.
  3. Under API keys choose New key. Name it after what will use it, for example "Website sync".
  4. Tick the scopes it needs (see Scopes). With none ticked it gets bookings:read.
  5. Choose Create key. The full key, ds_ followed by 43 characters, is shown once. Copy it into your server's secret store straight away.

DailyStay keeps only a SHA-256 hash of the key, so nobody can show it to you again. If it is lost, create a new key and revoke the old one. The list shows each key's first 12 characters, scopes, creation date and last use.

A key belongs to one property. The property comes from the key itself, never from the URL, so a key cannot reach another property's data.

Authenticate requests

Send the key in the X-Api-Key header on every Partner API request. Not in Authorization, and never in the query string.

Request
curl "https://new.yourdailystay.com/api/partner/whoami" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Response
{
  "success": true,
  "data": {
    "propertyId": "1042",
    "scopes": [
      "bookings:read"
    ]
  },
  "error": null,
  "pagination": null
}
StatusMeaning
200Key valid and allowed.
401Header missing, key unknown or revoked, or the property is no longer on the Max plan. Empty body.
403Key valid but missing the scope this endpoint needs. Empty body.
Server helper (Node 18+)
const BASE = 'https://new.yourdailystay.com/api'

export async function dailyStay(path) {
  const response = await fetch(BASE + path, {
    headers: { 'X-Api-Key': process.env.DAILYSTAY_API_KEY },
  })
  if (response.status === 401) throw new Error('DailyStay rejected the key. Is it revoked?')
  if (response.status === 403) throw new Error('The key is missing a scope for ' + path)
  const body = await response.json()
  if (!body.success) throw new Error(body.error.message)
  return body.data
}

Key lifetime and token refresh

An API key is not an access token. There is no login call, no expiry and nothing to refresh: a key works until the owner revokes it, or until the property leaves the Max plan.

Rotate a key without downtime

  1. Create a second key with the same scopes.
  2. Deploy it to your server.
  3. Watch the old key's last use stop moving in the keys list.
  4. Revoke the old key. It stops working at once and stays listed, marked revoked, for the audit trail.

Which credential refreshes

CredentialExpiresRefresh
API key, ds_...Never, until revokedNot needed. Rotate by creating a new key.
AI assistant connector (MCP, OAuth)Access token expiresYour AI client refreshes it on its own with grant_type=refresh_token at /mcp/oauth/token.
DailyStay app sign-inYesInternal to DailyStay's own apps. Not supported for integrations: use an API key.

Scopes

ScopeGrantsUsed by
bookings:readRead the property's bookings, guest contact details included.GET /partner/bookings, GET /partner/bookings/{bookingId}
bookings:writeCreate bookings, set their price and change their status.POST /partner/bookings, PUT /partner/bookings/{bookingId}/price, PATCH /partner/bookings/{bookingId}/status
rooms:readRead rooms, room classes, amenities and photos.GET /partner/rooms, GET /partner/rooms/{roomId}, GET /partner/room-classes
rooms:writeChange a room's name, description, capacity, amenities and photos. Not its price.PUT /partner/rooms/{roomId}, PUT /partner/rooms/{roomId}/images
pricing:readRead base prices, the weekend setting and rate rules.GET /partner/pricing
pricing:writeSet a room's base price.PUT /partner/rooms/{roomId}/price
calendar:readRead closed dates, restrictions, occupied nights (no guest details) and the calendar feeds.GET /partner/calendar, GET /partner/ical
calendar:writeClose dates and open them again.POST /partner/calendar/blocks, DELETE /partner/calendar/blocks/{ruleId}
guests:readReserved. Grants nothing today.None yet. Guest details come with each booking.

GET /partner/whoami needs no scope. Give each key the least it needs: a reporting tool only ever needs bookings:read. A website that mirrors the property needs every scope above except guests:read: choose **Full website sync** when you create the key. Call GET /partner/whoami to see which scopes a key carries and switch features off, rather than fail, when one is missing.

Public Booking API

For the hotel's own website. No key, open to any origin (CORS), limited to 60 requests a minute per IP address. Nothing here reveals another guest or another booking.

GET/public/booking/{propertyId}

Property and rooms

Everything a booking page shows before dates are picked: contact details, check-in times, deposit rule, cancellation terms and every room that is not in maintenance. Prices here are the rooms' base rates; real prices come from availability.

Auth: None

https://new.yourdailystay.com/api/public/booking/{propertyId}

Parameters
propertyIdrequired
path, stringThe DailyStay property id.
ref
query, stringA tracked booking link code, counted as a visit for that link.
Example
curl "https://new.yourdailystay.com/api/public/booking/1042"
Errors
  • 404 NOT_FOUND The booking page is switched off, the id is unknown, or the property is not on the Max plan.
GET/public/booking/by-slug/{slug}

Property by booking address

The same response, found by the booking address name the owner chose in Property Settings (the villa-drita in villa-drita.yourdailystay.com). Read propertyId from the answer and use it for every other call.

Auth: None

https://new.yourdailystay.com/api/public/booking/by-slug/{slug}

Parameters
slugrequired
path, stringThe booking address name.
ref
query, stringA tracked booking link code.
Example
curl "https://new.yourdailystay.com/api/public/booking/by-slug/villa-drita"
Errors
  • 404 NOT_FOUND No published page with that name.
GET/public/booking/{propertyId}/availability

Search availability

Rooms free for the whole stay that fit the party, priced with the property's rates, minimum stays and closed dates applied. When nothing fits, rooms is empty, notes says why in plain words, and suggestions offers up to three nearby windows that do have rooms.

Auth: None

https://new.yourdailystay.com/api/public/booking/{propertyId}/availability

Parameters
propertyIdrequired
path, stringThe property id.
checkInrequired
query, dateArrival, YYYY-MM-DD. Not in the past, at most 730 days ahead.
checkOutrequired
query, dateDeparture, YYYY-MM-DD. 1 to 60 nights after arrival.
guestsrequired
query, integerPeople staying. Values below 1 are read as 1.
Example
curl "https://new.yourdailystay.com/api/public/booking/1042/availability?checkIn=2026-11-14&checkOut=2026-11-17&guests=2"
Errors
  • 404 NOT_FOUND No published booking page for this property.
POST/public/booking/{propertyId}

Create a reservation

Saves the reservation in DailyStay as pending with reservation source Direct, and notifies the property, who confirms it in the app. Availability, price, minimum stay and capacity are all checked again on the server, so a stale search cannot double-book a room.

Auth: None

https://new.yourdailystay.com/api/public/booking/{propertyId}

Parameters
propertyIdrequired
path, stringThe property id.
roomIdrequired
body, stringA roomId from availability.
checkInrequired
body, dateYYYY-MM-DD.
checkOutrequired
body, dateYYYY-MM-DD.
guestsrequired
body, integer1 to 20, and no more than the room sleeps.
guestNamerequired
body, stringUp to 100 characters.
guestEmailrequired
body, stringA valid email, up to 100 characters.
guestPhone
body, stringUp to 20 characters.
guestCountry
body, stringUp to 50 characters.
notes
body, stringMessage for the property, up to 1000 characters.
consentGivenrequired
body, booleanMust be true: the guest accepted the data-processing notice.
payAtProperty
body, booleanGuest wants to pay on arrival. Honoured only when payAtPropertyAllowed is true.
whatsAppOptIn
body, booleanGuest agreed to WhatsApp messages about this booking.
ref
body, stringTracked booking link code, credits the booking to that link.
Example
curl -X POST "https://new.yourdailystay.com/api/public/booking/1042" \
  -H "Content-Type: application/json" \
  -d '{"roomId":"8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30","checkIn":"2026-11-14","checkOut":"2026-11-17","guests":2,"guestName":"Elira Hoxha","guestEmail":"guest@example.com","consentGiven":true}'
Errors
  • 400 VALIDATION_ERROR A rule was broken, for example consent missing, dates in the past, or more guests than the room sleeps. message says which.
  • 400 Field validation failed (problem details body, see Errors).
  • 409 UNAVAILABLE The room was taken meanwhile, a closed date or minimum stay applies, or the page is not live.

Calling these from your server instead of the browser? Every guest then shares your server's IP address and its 60 requests a minute. Cache the property response and keep availability calls to real searches.

Online payments

Optional. Lets a guest pay the deposit, or the whole stay, right after booking. Needs Stripe or PayPal connected in Property Settings, Payments tab.

  1. Read the providers. Empty means no online payment.
  2. Start a payment for the bookingId you got back from the reservation.
  3. Finish it in the browser with Stripe.js (clientToken is the client secret) or the PayPal buttons (clientToken is the order id).
  4. Confirm the payment so DailyStay records it against the booking.
GET/public/payments/{propertyId}/providers

Payment providers

Which ways this property can be paid online. An empty list means no online payment: show the booking as pending and let the property collect.

Auth: None

https://new.yourdailystay.com/api/public/payments/{propertyId}/providers

Parameters
propertyIdrequired
path, stringThe property id.
Example
curl "https://new.yourdailystay.com/api/public/payments/1042/providers"
POST/public/payments/{propertyId}/booking/{bookingId}

Start a payment

Opens a payment for a booking and returns what the browser needs to finish it. The amount is worked out on the server: the deposit percentage when the property takes deposits, otherwise the whole stay. For Stripe, clientToken is the PaymentIntent client secret and publishableKey the Stripe publishable key. For PayPal, clientToken is the order id and publishableKey the PayPal client id.

Auth: None

https://new.yourdailystay.com/api/public/payments/{propertyId}/booking/{bookingId}

Parameters
propertyIdrequired
path, stringThe property id.
bookingIdrequired
path, stringbookingId from the reservation response.
providerrequired
body, stringstripe or paypal.
Example
curl -X POST "https://new.yourdailystay.com/api/public/payments/1042/booking/482913" \
  -H "Content-Type: application/json" \
  -d '{"provider":"stripe"}'
Errors
  • 400 PAYMENT_ERROR Online payments are off, the booking is cancelled, or there is nothing to pay.
POST/public/payments/confirm/{paymentId}

Confirm a payment

Call when the guest comes back from paying. DailyStay asks the provider directly whether the money arrived, so this call cannot mark a booking paid by itself. Stripe payments are also confirmed by Stripe's own webhook if the guest closes the tab.

Auth: None

https://new.yourdailystay.com/api/public/payments/confirm/{paymentId}

Parameters
paymentIdrequired
path, stringpaymentId from Start a payment.
Example
curl -X POST "https://new.yourdailystay.com/api/public/payments/confirm/c41d7e02-93b5-4f6a-8e1c-5a9b2f0d7e64"
Errors
  • 404 NOT_FOUND No such payment.
  • 400 PAYMENT_ERROR The provider refused or could not be reached.

Partner API

Everything your server needs to run against one property: read and change its rooms, prices, calendar and bookings. The key fixes the property, so no id appears in the URL, and each endpoint asks for the one scope it needs.

GET/partner/whoami

Check a key

Returns the property this key belongs to and the scopes it carries. Call it once at start-up to fail fast on a wrong or revoked key.

Auth: API key, no scope needed

https://new.yourdailystay.com/api/partner/whoami

Parameters
X-Api-Keyrequired
header, stringYour key, ds_....
Example
curl "https://new.yourdailystay.com/api/partner/whoami" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Errors
  • 401 Missing, unknown or revoked key, or the property is no longer on the Max plan.
GET/partner/bookings

List bookings

Bookings of the key's property, newest created first. pagination is null: keep raising offset until a page comes back shorter than limit.

Auth: API key, scope bookings:read

https://new.yourdailystay.com/api/partner/bookings

Parameters
X-Api-Keyrequired
header, stringYour key.
status
query, stringOnly bookings in this status: pending, confirmed, checked_in, checked_out, cancelled or rejected.
limit
query, integerPage size. Default 100, clamped to 1-200.
offset
query, integerHow many to skip. Default 0.
Example
curl "https://new.yourdailystay.com/api/partner/bookings?status=confirmed&limit=50&offset=0" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Errors
  • 401 Key rejected.
  • 403 Key lacks bookings:read.
GET/partner/bookings/{bookingId}

Get one booking

A single booking by id. Use it after a webhook arrives to read the current state.

Auth: API key, scope bookings:read

https://new.yourdailystay.com/api/partner/bookings/{bookingId}

Parameters
X-Api-Keyrequired
header, stringYour key.
bookingIdrequired
path, stringThe booking id, for example 482913.
Example
curl "https://new.yourdailystay.com/api/partner/bookings/482913" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Errors
  • 401 Key rejected.
  • 403 Key lacks bookings:read.
  • 404 NOT_FOUND No such booking on this property.
POST/partner/bookings

Create a booking

Adds a booking to the calendar, for a reservation your system took itself (a direct sale, a phone call). Send totalAmount to book at an agreed price (a quote you gave the guest); leave it out and DailyStay prices the stay from the room's rates and rate rules. The deposit follows the property's deposit setting. The same checks apply as in the dashboard: dates already taken, closed ranges and minimum stays are refused. Guests are not emailed unless you set sendEmails. For a reservation made by a visitor on a hotel website, use the Public Booking API instead: it starts as pending and is accepted by the property.

Auth: API key, scope bookings:write

https://new.yourdailystay.com/api/partner/bookings

Parameters
X-Api-Keyrequired
header, stringYour key.
roomIdrequired
body, stringThe room to book.
guestNamerequired
body, stringUp to 200 characters.
guestEmail
body, stringWithout one a placeholder is stored and no email can be sent.
guestPhone
body, stringInternational format.
guestCountry
body, stringCountry name.
checkInrequired
body, dateArrival, YYYY-MM-DD.
checkOutrequired
body, dateDeparture, YYYY-MM-DD.
guests
body, integer1-20. Default 1.
notes
body, stringFree text.
source
body, stringWhere it came from, shown as the booking source. Default Partner API.
status
body, stringOne of the booking statuses. Default confirmed.
totalAmount
body, numberAgreed total for the whole stay. Without it the stay is priced from the rates.
sendEmails
body, booleanLet DailyStay email the guest. Default false.
Example
curl -X POST "https://new.yourdailystay.com/api/partner/bookings" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"roomId":"8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30","guestName":"Elira Hoxha","checkIn":"2026-11-14","checkOut":"2026-11-17","guests":2}'
Errors
  • 400 VALIDATION_ERROR Unknown status or an invalid field.
  • 403 Key lacks bookings:write.
  • 409 BOOKING_ERROR Dates taken, a closed range, a stay below the minimum, or an unknown room. message says which.
PATCH/partner/bookings/{bookingId}/status

Change a booking's status

Moves a booking to a new status: confirm a pending one, cancel or reject one (the nights become free again), or mark it checked in or out. Returns the booking.

Auth: API key, scope bookings:write

https://new.yourdailystay.com/api/partner/bookings/{bookingId}/status

Parameters
X-Api-Keyrequired
header, stringYour key.
bookingIdrequired
path, stringThe booking id, for example 482913.
statusrequired
body, stringpending, confirmed, checked_in, checked_out, cancelled or rejected.
Example
curl -X PATCH "https://new.yourdailystay.com/api/partner/bookings/482913/status" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"confirmed"}'
Errors
  • 400 VALIDATION_ERROR Unknown status.
  • 403 Key lacks bookings:write.
  • 404 NOT_FOUND No such booking.
PUT/partner/bookings/{bookingId}/price

Set a booking's price

Sets the agreed total of a booking, for example after the guest accepted a price you quoted. A deposit already paid is kept; otherwise the deposit follows the property's deposit setting. Combine it with Change a booking's status to confirm the stay at that price.

Auth: API key, scope bookings:write

https://new.yourdailystay.com/api/partner/bookings/{bookingId}/price

Parameters
X-Api-Keyrequired
header, stringYour key.
bookingIdrequired
path, stringThe booking id.
totalAmountrequired
body, numberThe agreed total for the whole stay.
Example
curl -X PUT "https://new.yourdailystay.com/api/partner/bookings/482913/price" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"totalAmount":420}'
Errors
  • 403 Key lacks bookings:write.
  • 404 NOT_FOUND No such booking.

Rooms and photos

GET/partner/rooms

List rooms

Every room of the key's property with what its room class adds (guest limits, class title), its base price, amenities and photos. name is shared by rooms of the same kind, so show name with number ("Deluxe Apartment 101") to tell them apart. type names the room class; it matches type on GET /partner/room-classes. Read it to build or refresh your own room list, then match rooms by id, which never changes.

Auth: API key, scope rooms:read

https://new.yourdailystay.com/api/partner/rooms

Parameters
X-Api-Keyrequired
header, stringYour key.
Example
curl "https://new.yourdailystay.com/api/partner/rooms" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Errors
  • 401 Key rejected.
  • 403 Key lacks rooms:read.
GET/partner/rooms/{roomId}

Get one room

A single room, in the same shape as the list.

Auth: API key, scope rooms:read

https://new.yourdailystay.com/api/partner/rooms/{roomId}

Parameters
X-Api-Keyrequired
header, stringYour key.
roomIdrequired
path, stringThe room id, for example 8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30.
Example
curl "https://new.yourdailystay.com/api/partner/rooms/8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Errors
  • 401 Key rejected.
  • 403 Key lacks rooms:read.
  • 404 NOT_FOUND No such room on this property.
GET/partner/room-classes

List room classes

The classes (types) rooms belong to, with their guest limits, layout (sizeSqm in m², beds as guests read it, bathrooms, sofaBeds; each null until the property fills it in), selling copy, amenities and photos (images, the first is the cover; imageUrl repeats the cover). Use it to group rooms the way the property does, and show the class photos where a room has none of its own.

Auth: API key, scope rooms:read

https://new.yourdailystay.com/api/partner/room-classes

Parameters
X-Api-Keyrequired
header, stringYour key.
Example
curl "https://new.yourdailystay.com/api/partner/room-classes" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Errors
  • 401 Key rejected.
  • 403 Key lacks rooms:read.
PUT/partner/rooms/{roomId}

Update a room

Changes a room's name, description, capacity, amenities or photos. Send only what changes; anything left out is kept. Prices are separate (see Set a room's price) so a key that may edit text cannot reprice the property. Returns the room as it is now.

Auth: API key, scope rooms:write

https://new.yourdailystay.com/api/partner/rooms/{roomId}

Parameters
X-Api-Keyrequired
header, stringYour key.
roomIdrequired
path, stringThe room id, for example 8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30.
name
body, stringUp to 100 characters. Two rooms cannot share a name.
description
body, stringUp to 1000 characters.
capacity
body, integer1-20.
amenities
body, string[]Replaces the amenity list.
images
body, string[]Replaces the photos. Public https addresses only, at most 30. See Replace photos.
Example
curl -X PUT "https://new.yourdailystay.com/api/partner/rooms/8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"description":"Corner apartment with a balcony over the harbour."}'
Errors
  • 400 VALIDATION_ERROR A photo address is not a public https URL, or there are more than 30.
  • 400 UPDATE_ROOM_ERROR The change was refused, for example a duplicate room name.
  • 403 Key lacks rooms:write.
  • 404 NOT_FOUND No such room.
PUT/partner/rooms/{roomId}/images

Replace photos

Replaces the room's photos with these addresses, in this order; the first one leads on the booking page. DailyStay stores the addresses and shows each photo from where it is hosted, so host the files yourself on a public https address and keep them there. Send an empty list to remove every photo. Duplicates are dropped.

Auth: API key, scope rooms:write

https://new.yourdailystay.com/api/partner/rooms/{roomId}/images

Parameters
X-Api-Keyrequired
header, stringYour key.
roomIdrequired
path, stringThe room id, for example 8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30.
imagesrequired
body, string[]Absolute https:// URLs without a user name or password, each up to 2048 characters, at most 30.
Example
curl -X PUT "https://new.yourdailystay.com/api/partner/rooms/8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30/images" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"images":["https://example.com/media/deluxe-1.jpg"]}'
Errors
  • 400 VALIDATION_ERROR An address is not public https (data:, http: and javascript: are refused), or there are more than 30.
  • 403 Key lacks rooms:write.
  • 404 NOT_FOUND No such room.

Prices

GET/partner/pricing

Read prices

Base price per room, the weekend setting, and the rate rules. A rule applies to one room class between two dates and multiplies the base price: weekdayMultiplier on weekdays and weekendMultiplier on the days listed in configuration.weekendDays (0 is Sunday). Where rules overlap the highest priority wins. To get a total for exact dates, ask GET /public/booking/{propertyId}/availability: it applies every rule for you.

Auth: API key, scope pricing:read

https://new.yourdailystay.com/api/partner/pricing

Parameters
X-Api-Keyrequired
header, stringYour key.
Example
curl "https://new.yourdailystay.com/api/partner/pricing" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Errors
  • 403 Key lacks pricing:read.
PUT/partner/rooms/{roomId}/price

Set a room's price

Sets the room's base price per night. Seasonal and weekend rates are rules applied on top of this base (see Read prices), so changing the base moves every rate that is a multiple of it.

Auth: API key, scope pricing:write

https://new.yourdailystay.com/api/partner/rooms/{roomId}/price

Parameters
X-Api-Keyrequired
header, stringYour key.
roomIdrequired
path, stringThe room id, for example 8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30.
pricePerNightrequired
body, numberMore than 0, in the property's currency.
Example
curl -X PUT "https://new.yourdailystay.com/api/partner/rooms/8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30/price" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pricePerNight":160}'
Errors
  • 403 Key lacks pricing:write.
  • 404 NOT_FOUND No such room.

Calendar

GET/partner/calendar

Read the calendar

What is closed and what is taken in a window. rules are closed dates (kind: "block") and stay restrictions (kind: "restriction": minimum nights, closed to arrival or departure). bookings are the nights held by stays that are not cancelled or rejected, with no guest details; use bookings:read for those. A rule with roomId: null applies to every room. endDate of a rule is its last night, inclusive; a booking's checkOut is the morning the guest leaves.

Auth: API key, scope calendar:read

https://new.yourdailystay.com/api/partner/calendar

Parameters
X-Api-Keyrequired
header, stringYour key.
from
query, dateFirst day, YYYY-MM-DD. Default today.
to
query, dateLast day, YYYY-MM-DD. Default a year after from; windows are cut at two years.
Example
curl "https://new.yourdailystay.com/api/partner/calendar?from=2026-11-01&to=2027-10-31" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Errors
  • 400 VALIDATION_ERROR to is before from.
  • 403 Key lacks calendar:read.
POST/partner/calendar/blocks

Close dates

Closes a range of nights for one room, or for every room when roomId is empty. The change goes to the connected booking channels like a closure made in the dashboard. Nights that are already sold cannot be closed.

Auth: API key, scope calendar:write

https://new.yourdailystay.com/api/partner/calendar/blocks

Parameters
X-Api-Keyrequired
header, stringYour key.
roomId
body, stringThe room to close. Empty closes every room.
startDaterequired
body, dateFirst night closed, YYYY-MM-DD.
endDaterequired
body, dateLast night closed, inclusive.
note
body, stringUp to 200 characters, shown on the calendar.
Example
curl -X POST "https://new.yourdailystay.com/api/partner/calendar/blocks" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"roomId":"8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30","startDate":"2026-12-01","endDate":"2026-12-05","note":"Painting"}'
Errors
  • 400 VALIDATION_ERROR The range is not valid.
  • 403 Key lacks calendar:write.
  • 409 DATES_BOOKED Some of those nights already have a stay. message says which.
DELETE/partner/calendar/blocks/{ruleId}

Open dates again

Removes a closed range, using the id from the calendar or from Close dates. A closure that comes from a synced calendar feed cannot be removed here: the next import would put it back.

Auth: API key, scope calendar:write

https://new.yourdailystay.com/api/partner/calendar/blocks/{ruleId}

Parameters
X-Api-Keyrequired
header, stringYour key.
ruleIdrequired
path, stringThe id of the closed range.
Example
curl -X DELETE "https://new.yourdailystay.com/api/partner/calendar/blocks/f7b2d4a6-1c83-49e5-b0d7-6a8c2e4f1b93" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Errors
  • 403 Key lacks calendar:write.
  • 404 NOT_FOUND No such closed range.
  • 409 SYNCED_BLOCK The closure belongs to a synced calendar feed.
GET/partner/ical

List calendar feeds

The iCal feeds the property has set up per room. direction: "export" feeds carry url, the address to give another system (Booking.com, Airbnb, your own site) so it can read this room's availability. direction: "import" feeds are the channel calendars DailyStay reads; their address is never returned, because it usually holds a private token. Use lastImportedAt and lastError to show whether a channel is healthy.

Auth: API key, scope calendar:read

https://new.yourdailystay.com/api/partner/ical

Parameters
X-Api-Keyrequired
header, stringYour key.
Example
curl "https://new.yourdailystay.com/api/partner/ical" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
Errors
  • 403 Key lacks calendar:read.

Booking statuses

StatusMeaning
pendingRequest received, not yet accepted by the property. Every public booking starts here.
confirmedAccepted by the property. The room is held.
checked_inThe guest has arrived.
checked_outThe stay is over.
cancelledCancelled. The nights are free again.
rejectedDeclined by the property. The nights are free again.
Read every booking, 200 at a time
const all = []
for (let offset = 0; ; offset += 200) {
  const page = await dailyStay('/partner/bookings?limit=200&offset=' + offset)
  all.push(...page)
  if (page.length < 200) break
}

Keep a website in sync

The same steps work for any property and any system: a hotel website, a channel manager, a booking app. DailyStay stays the source of truth for rooms, prices, the calendar and bookings; your system keeps its own presentation (display names, copy, layout) and sends back what only it has (photos, reservations it takes itself).

  1. Create a key with **Full website sync** (or only the scopes you need) and call GET /partner/whoami. Turn off whatever a missing scope would need instead of failing later.
  2. Read the catalogue: GET /partner/rooms and GET /partner/room-classes. Store each room's id; it never changes, and it is how you match a room to your own record.
  3. Match rooms. Pair rooms you already have with DailyStay rooms by id (suggest pairs whose names match), let the owner confirm, and offer to create a record for any room that is new. Run this step again whenever a refresh brings a room you have not seen.
  4. Keep your own display name per room if you want one. Treat name as DailyStay's name and never write it back unless the owner asks.
  5. Read prices (GET /partner/pricing) for a "from" price, and ask GET /public/booking/{propertyId}/availability for the real total of a stay: it applies every rule.
  6. Mirror the calendar: GET /partner/calendar for closed dates and occupied nights, or the iCal feeds from GET /partner/ical for systems that only speak iCal. Refresh on a timer and after each webhook.
  7. Push what you own: photos with PUT /partner/rooms/{roomId}/images (public https addresses you host), closures with POST /partner/calendar/blocks, and reservations your system took with POST /partner/bookings.
  8. Import history once with GET /partner/bookings?limit=200&offset=... (past stays included), then follow booking.created, booking.updated and booking.cancelled webhooks. Treat a webhook as a nudge and read the booking again.
Start-up check and catalogue refresh
const { scopes } = await dailyStay('/partner/whoami')
const can = (scope) => scopes.includes(scope)

const rooms = can('rooms:read') ? await dailyStay('/partner/rooms') : []
const unseen = rooms.filter((room) => !myRooms.has(room.id))
if (unseen.length) askOwnerToMatch(unseen)       // show your "match rooms" step

// Push photos you host. Public https addresses only.
if (can('rooms:write')) {
  await dailyStay('/partner/rooms/' + roomId + '/images', {
    method: 'PUT',
    body: { images: photoUrls },
  })
}

Photos are stored as addresses, not files. Keep the files on your own public https address for as long as they should show on the booking page, and send the full list again whenever it changes.

Errors

HTTPerror.codeWhen
400VALIDATION_ERRORThe request broke a booking rule. message says which, in words you can show a guest.
400none (problem details)A body field failed validation, for example a malformed email. See below.
400PAYMENT_ERRORA payment could not be started or confirmed.
401none (empty body)API key missing, unknown or revoked.
403none (empty body)API key lacks the scope.
404NOT_FOUNDUnknown booking or payment, or the booking page is off.
409UNAVAILABLEThe room is no longer free, or a closed date or minimum stay applies.
409BOOKING_ERROR, DATES_BOOKED, SYNCED_BLOCKA Partner API write was refused: the dates are taken, nights are already sold, or the closure belongs to a synced calendar feed. message says which.
429RATE_LIMITEDToo many requests. Wait for the Retry-After seconds.
5xxvariesSomething failed on our side. Retry with backoff.
400 problem details
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "GuestEmail": [
      "The GuestEmail field is not a valid e-mail address."
    ]
  }
}

Rate limits

EndpointsLimit
/public/booking/...60 requests a minute per IP address
/public/payments/... and /partner/...300 requests a minute per IP address

Over the limit you get 429 with error.code RATE_LIMITED and a Retry-After header in seconds. Back off when you see it: an address that keeps going after 429s is blocked for a while.

Webhooks

Instead of polling, let DailyStay call your server when a booking changes. Add a subscription in Property Settings, Features tab, API and webhooks: an https URL and the events you want (none ticked means all). Copy the signing secret shown with it.

EventSent when
booking.createdThe property creates a booking in the DailyStay app.
booking.updatedA booking's status changes, for example a website request is confirmed, or a guest checks in.
booking.cancelledA booking is cancelled or rejected.

Reservations made through the Public Booking API do not send booking.created. You hear about them as booking.updated when the property confirms (or booking.cancelled when it rejects). Edits to dates or guest details do not send an event.

Delivery

  • POST to your URL with a JSON body and the header Daily-Stay-Signature: t=<unix seconds>,v1=<hex HMAC>.
  • Any 2xx within 8 seconds counts as delivered. Answer fast and do the work afterwards.
  • A failed delivery is not retried. After 20 failures in a row the subscription stops being called; it stays listed with its last error. Fix the endpoint, then delete the subscription and add it again.
  • Treat an event as a nudge: read the booking again with GET /partner/bookings/{bookingId}, and run a periodic sync to catch anything missed.
Payload
{
  "event": "booking.updated",
  "propertyId": "1042",
  "sentAt": "2026-10-21T09:30:02.1184310Z",
  "data": {
    "Id": "482913",
    "DisplayId": null,
    "BookingGroupId": null,
    "RoomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "PropertyId": "1042",
    "ReservationSourceId": null,
    "ReservationSource": null,
    "EffectivePricePerNight": null,
    "AppliedRateRuleId": null,
    "AppliedRateRuleName": null,
    "PricingMode": null,
    "EmailDisabled": false,
    "GuestName": "Elira Hoxha",
    "GuestEmail": "guest@example.com",
    "GuestPhone": "+355694821937",
    "GuestCountry": "Albania",
    "GuestPassportId": null,
    "CheckIn": "2026-11-14T00:00:00",
    "CheckOut": "2026-11-17T00:00:00",
    "Guests": 2,
    "TotalAmount": 235,
    "DepositAmount": 0,
    "RemainingAmount": 235,
    "Status": "confirmed",
    "PaymentStatus": "pending",
    "DepositSent": false,
    "DepositDate": null,
    "DepositPaymentMethod": null,
    "Notes": "Arriving late, around 22:00",
    "ExtraOptions": [],
    "EmailStatuses": [],
    "CreatedAt": "2026-10-21T09:12:44.517",
    "UpdatedAt": "2026-10-21T09:12:44.517"
  }
}

Inside data the field names are PascalCase (GuestName, CheckIn), unlike the API's camelCase. ReservationSource is always null in webhooks; read the booking from the API if you need it.

Verify the signature

Compute HMAC-SHA256, hex encoded, of <t>.<raw request body> with the subscription secret and compare it with v1. Use the raw bytes as received: parsing and re-serialising the JSON changes them. Reject timestamps older than five minutes to stop replays.

Node (Express)
import crypto from 'node:crypto'
import express from 'express'

const app = express()

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries((header ?? '').split(',').map((part) => part.split('=')))
  const timestamp = Number(parts.t)
  if (!timestamp || !parts.v1) return false
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false

  const expected = crypto.createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex')
  return expected.length === parts.v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}

app.post('/webhooks/dailystay', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8')
  if (!verify(raw, req.get('Daily-Stay-Signature'), process.env.DAILYSTAY_WEBHOOK_SECRET)) {
    return res.sendStatus(401)
  }
  res.sendStatus(200)

  const event = JSON.parse(raw)
  console.log(event.event, event.data.Id, event.data.Status)
})

AI usage

Everything on this page, as Markdown

2,053 lines, ready to paste into any AI chat.

Preview the Markdown
dailystay-api.md
# DailyStay API documentation

Base URL: `https://new.yourdailystay.com/api`

## Contents

- Overview
- Hotel website quickstart
- Generate an API key
- Authenticate requests
- Key lifetime and token refresh
- Scopes
- Public Booking API
- Online payments
- Partner API
- Keep a website in sync
- Errors
- Rate limits
- Webhooks
- AI usage

## Overview

DailyStay has two HTTP APIs. Both speak JSON and share one base URL.

Base URL:

```text
https://new.yourdailystay.com/api
```

| API | Use it for | Auth | Call it from |
| --- | --- | --- | --- |
| Public Booking API, `/public/booking` and `/public/payments` | A hotel website: rooms, availability, new reservations, deposits | None | Browser or server |
| Partner API, `/partner` | Your own systems: keeping a website or channel manager in sync (rooms, classes, prices, photos, calendar, bookings), reporting, accounting | API key in `X-Api-Key`, scopes per capability | Server only |

> **Note:** Both APIs come with the Max plan. On a lower plan the public booking endpoints answer as if the page did not exist, and API keys are rejected with `401`.

### Conventions

- Request and response bodies are JSON. Send `Content-Type: application/json`.
- Field names are camelCase. Webhook `data` is the one exception (see Webhooks).
- Stay dates are `YYYY-MM-DD`. Timestamps are ISO 8601 in UTC.
- Money is a decimal in the property's currency, given as `currency` (for example `EUR`).
- Every response uses the same envelope: check `success`, then read `data` or `error`.

Success:

```json
{
  "success": true,
  "data": {
    "...": "..."
  },
  "error": null,
  "pagination": null
}
```

Error:

```json
{
  "success": false,
  "data": null,
  "error": {
    "code": "NOT_FOUND",
    "message": "No such booking.",
    "details": []
  },
  "pagination": null
}
```

## Hotel website quickstart

Building a website for a hotel that runs on DailyStay? This is the whole flow. Reservations made on your site are saved straight into DailyStay, as pending requests the property confirms. No API key is needed for any of it.

1. The owner switches on the booking page: Property Settings, Features tab, Your own booking page. Until then every public endpoint answers `404`.
2. Find the property id. It is the number at the end of the booking page link on that card (`/book/1042`). `GET /partner/whoami` also returns it for any API key.
3. Load the property and its rooms with `GET /public/booking/{propertyId}`.
4. Search a stay with `GET /public/booking/{propertyId}/availability`.
5. Book it with `POST /public/booking/{propertyId}`. The reservation appears in DailyStay as `pending` and the property is notified.
6. Optional: take the deposit online with the payment endpoints.
7. Optional: from your server, keep your own records in step with the Partner API and webhooks.

Browser example:

```js
const BASE = 'https://new.yourdailystay.com/api'
const PROPERTY_ID = '1042'

async function call(path, options = {}) {
  const response = await fetch(BASE + path, {
    ...options,
    headers: { 'Content-Type': 'application/json', ...options.headers },
  })
  const body = await response.json()
  if (!body.success) throw new Error(body.error?.message ?? 'HTTP ' + response.status)
  return body.data
}

// 1. What can be booked for these dates, priced
const search = await call(
  '/public/booking/' + PROPERTY_ID + '/availability?checkIn=2026-11-14&checkOut=2026-11-17&guests=2',
)
if (search.rooms.length === 0) console.log(search.notes, search.suggestions)

// 2. Book the first free room
const booking = await call('/public/booking/' + PROPERTY_ID, {
  method: 'POST',
  body: JSON.stringify({
    roomId: search.rooms[0].roomId,
    checkIn: '2026-11-14',
    checkOut: '2026-11-17',
    guests: 2,
    guestName: 'Elira Hoxha',
    guestEmail: 'guest@example.com',
    consentGiven: true,
  }),
})

console.log(booking.reference, booking.status) // "482913" "pending"
```

> **Important:** Never put an API key in browser code. The public booking endpoints need no key, and the Partner API is for your server only.

## Generate an API key

1. Sign in to DailyStay as the property owner. Managers and staff do not see this panel.
2. Open Property Settings, Features tab, API and webhooks.
3. Under API keys choose New key. Name it after what will use it, for example "Website sync".
4. Tick the scopes it needs (see Scopes). With none ticked it gets `bookings:read`.
5. Choose Create key. The full key, `ds_` followed by 43 characters, is shown once. Copy it into your server's secret store straight away.

DailyStay keeps only a SHA-256 hash of the key, so nobody can show it to you again. If it is lost, create a new key and revoke the old one. The list shows each key's first 12 characters, scopes, creation date and last use.

> **Note:** A key belongs to one property. The property comes from the key itself, never from the URL, so a key cannot reach another property's data.

## Authenticate requests

Send the key in the `X-Api-Key` header on every Partner API request. Not in `Authorization`, and never in the query string.

Request:

```bash
curl "https://new.yourdailystay.com/api/partner/whoami" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

Response:

```json
{
  "success": true,
  "data": {
    "propertyId": "1042",
    "scopes": [
      "bookings:read"
    ]
  },
  "error": null,
  "pagination": null
}
```

| Status | Meaning |
| --- | --- |
| `200` | Key valid and allowed. |
| `401` | Header missing, key unknown or revoked, or the property is no longer on the Max plan. Empty body. |
| `403` | Key valid but missing the scope this endpoint needs. Empty body. |

Server helper (Node 18+):

```js
const BASE = 'https://new.yourdailystay.com/api'

export async function dailyStay(path) {
  const response = await fetch(BASE + path, {
    headers: { 'X-Api-Key': process.env.DAILYSTAY_API_KEY },
  })
  if (response.status === 401) throw new Error('DailyStay rejected the key. Is it revoked?')
  if (response.status === 403) throw new Error('The key is missing a scope for ' + path)
  const body = await response.json()
  if (!body.success) throw new Error(body.error.message)
  return body.data
}
```

## Key lifetime and token refresh

An API key is not an access token. There is no login call, no expiry and nothing to refresh: a key works until the owner revokes it, or until the property leaves the Max plan.

### Rotate a key without downtime

1. Create a second key with the same scopes.
2. Deploy it to your server.
3. Watch the old key's last use stop moving in the keys list.
4. Revoke the old key. It stops working at once and stays listed, marked revoked, for the audit trail.

### Which credential refreshes

| Credential | Expires | Refresh |
| --- | --- | --- |
| API key, `ds_...` | Never, until revoked | Not needed. Rotate by creating a new key. |
| AI assistant connector (MCP, OAuth) | Access token expires | Your AI client refreshes it on its own with `grant_type=refresh_token` at `/mcp/oauth/token`. |
| DailyStay app sign-in | Yes | Internal to DailyStay's own apps. Not supported for integrations: use an API key. |

## Scopes

| Scope | Grants | Used by |
| --- | --- | --- |
| `bookings:read` | Read the property's bookings, guest contact details included. | `GET /partner/bookings`, `GET /partner/bookings/{bookingId}` |
| `bookings:write` | Create bookings, set their price and change their status. | `POST /partner/bookings`, `PUT /partner/bookings/{bookingId}/price`, `PATCH /partner/bookings/{bookingId}/status` |
| `rooms:read` | Read rooms, room classes, amenities and photos. | `GET /partner/rooms`, `GET /partner/rooms/{roomId}`, `GET /partner/room-classes` |
| `rooms:write` | Change a room's name, description, capacity, amenities and photos. Not its price. | `PUT /partner/rooms/{roomId}`, `PUT /partner/rooms/{roomId}/images` |
| `pricing:read` | Read base prices, the weekend setting and rate rules. | `GET /partner/pricing` |
| `pricing:write` | Set a room's base price. | `PUT /partner/rooms/{roomId}/price` |
| `calendar:read` | Read closed dates, restrictions, occupied nights (no guest details) and the calendar feeds. | `GET /partner/calendar`, `GET /partner/ical` |
| `calendar:write` | Close dates and open them again. | `POST /partner/calendar/blocks`, `DELETE /partner/calendar/blocks/{ruleId}` |
| `guests:read` | Reserved. Grants nothing today. | None yet. Guest details come with each booking. |

`GET /partner/whoami` needs no scope. Give each key the least it needs: a reporting tool only ever needs `bookings:read`. A website that mirrors the property needs every scope above except `guests:read`: choose **Full website sync** when you create the key. Call `GET /partner/whoami` to see which scopes a key carries and switch features off, rather than fail, when one is missing.

## Public Booking API

For the hotel's own website. No key, open to any origin (CORS), limited to 60 requests a minute per IP address. Nothing here reveals another guest or another booking.

### GET /public/booking/{propertyId}

**Property and rooms.** Everything a booking page shows before dates are picked: contact details, check-in times, deposit rule, cancellation terms and every room that is not in maintenance. Prices here are the rooms' base rates; real prices come from availability.

- URL: `GET https://new.yourdailystay.com/api/public/booking/{propertyId}`
- Auth: None

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `propertyId` | path | string | yes | The DailyStay property id. |
| `ref` | query | string | no | A tracked booking link code, counted as a visit for that link. |

Response `200`:

```json
{
  "success": true,
  "data": {
    "propertyId": "1042",
    "name": "Villa Drita",
    "intro": "Five minutes from the beach, with parking.",
    "city": "Sarandë",
    "country": "Albania",
    "address": "Rruga Skënderbeu 14",
    "phone": "+355694821937",
    "email": "stay@example.com",
    "currency": "EUR",
    "checkInTime": "14:00",
    "checkOutTime": "11:00",
    "depositEnabled": true,
    "depositPercentage": 30,
    "rooms": [
      {
        "id": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
        "name": "Sea View Double",
        "type": "double",
        "capacity": 2,
        "pricePerNight": 85,
        "description": "Balcony over the bay.",
        "images": [
          "https://example.com/rooms/sea-view-1.jpg"
        ],
        "amenities": [
          "Wi-Fi",
          "Air conditioning",
          "Balcony"
        ],
        "classTitle": "Double room with sea view",
        "maxAdults": 2,
        "maxGuests": 2
      }
    ],
    "coordinates": "39.8756,20.0053",
    "websiteUrl": "https://example.com",
    "facilities": [
      "Air conditioning",
      "Balcony",
      "Wi-Fi"
    ],
    "gallery": [
      "https://example.com/rooms/sea-view-1.jpg"
    ],
    "freeCancellationDays": 3,
    "payAtPropertyAllowed": true
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `404 NOT_FOUND` The booking page is switched off, the id is unknown, or the property is not on the Max plan.

Example:

```bash
curl "https://new.yourdailystay.com/api/public/booking/1042"
```

### GET /public/booking/by-slug/{slug}

**Property by booking address.** The same response, found by the booking address name the owner chose in Property Settings (the `villa-drita` in `villa-drita.yourdailystay.com`). Read `propertyId` from the answer and use it for every other call.

- URL: `GET https://new.yourdailystay.com/api/public/booking/by-slug/{slug}`
- Auth: None

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `slug` | path | string | yes | The booking address name. |
| `ref` | query | string | no | A tracked booking link code. |

Response `200`:

```json
{
  "success": true,
  "data": {
    "propertyId": "1042",
    "name": "Villa Drita",
    "intro": "Five minutes from the beach, with parking.",
    "city": "Sarandë",
    "country": "Albania",
    "address": "Rruga Skënderbeu 14",
    "phone": "+355694821937",
    "email": "stay@example.com",
    "currency": "EUR",
    "checkInTime": "14:00",
    "checkOutTime": "11:00",
    "depositEnabled": true,
    "depositPercentage": 30,
    "rooms": [
      "..."
    ],
    "coordinates": "39.8756,20.0053",
    "websiteUrl": "https://example.com",
    "facilities": [
      "..."
    ],
    "gallery": [
      "..."
    ],
    "freeCancellationDays": 3,
    "payAtPropertyAllowed": true
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `404 NOT_FOUND` No published page with that name.

Example:

```bash
curl "https://new.yourdailystay.com/api/public/booking/by-slug/villa-drita"
```

### GET /public/booking/{propertyId}/availability

**Search availability.** Rooms free for the whole stay that fit the party, priced with the property's rates, minimum stays and closed dates applied. When nothing fits, `rooms` is empty, `notes` says why in plain words, and `suggestions` offers up to three nearby windows that do have rooms.

- URL: `GET https://new.yourdailystay.com/api/public/booking/{propertyId}/availability`
- Auth: None

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `propertyId` | path | string | yes | The property id. |
| `checkIn` | query | date | yes | Arrival, `YYYY-MM-DD`. Not in the past, at most 730 days ahead. |
| `checkOut` | query | date | yes | Departure, `YYYY-MM-DD`. 1 to 60 nights after arrival. |
| `guests` | query | integer | yes | People staying. Values below 1 are read as 1. |

Response `200`:

```json
{
  "success": true,
  "data": {
    "checkIn": "2026-11-14",
    "checkOut": "2026-11-17",
    "nights": 3,
    "guests": 2,
    "currency": "EUR",
    "rooms": [
      {
        "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
        "name": "Sea View Double",
        "type": "double",
        "capacity": 2,
        "description": "Balcony over the bay.",
        "total": 235,
        "averagePerNight": 78.33,
        "deposit": 70.5,
        "images": [
          "https://example.com/rooms/sea-view-1.jpg"
        ],
        "amenities": [
          "Wi-Fi",
          "Air conditioning",
          "Balcony"
        ],
        "classTitle": "Double room with sea view",
        "maxGuests": 2,
        "standardPerNight": 85
      }
    ],
    "notes": [],
    "suggestions": []
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `404 NOT_FOUND` No published booking page for this property.

Example:

```bash
curl "https://new.yourdailystay.com/api/public/booking/1042/availability?checkIn=2026-11-14&checkOut=2026-11-17&guests=2"
```

### POST /public/booking/{propertyId}

**Create a reservation.** Saves the reservation in DailyStay as `pending` with reservation source Direct, and notifies the property, who confirms it in the app. Availability, price, minimum stay and capacity are all checked again on the server, so a stale search cannot double-book a room.

- URL: `POST https://new.yourdailystay.com/api/public/booking/{propertyId}`
- Auth: None

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `propertyId` | path | string | yes | The property id. |
| `roomId` | body | string | yes | A `roomId` from availability. |
| `checkIn` | body | date | yes | `YYYY-MM-DD`. |
| `checkOut` | body | date | yes | `YYYY-MM-DD`. |
| `guests` | body | integer | yes | 1 to 20, and no more than the room sleeps. |
| `guestName` | body | string | yes | Up to 100 characters. |
| `guestEmail` | body | string | yes | A valid email, up to 100 characters. |
| `guestPhone` | body | string | no | Up to 20 characters. |
| `guestCountry` | body | string | no | Up to 50 characters. |
| `notes` | body | string | no | Message for the property, up to 1000 characters. |
| `consentGiven` | body | boolean | yes | Must be `true`: the guest accepted the data-processing notice. |
| `payAtProperty` | body | boolean | no | Guest wants to pay on arrival. Honoured only when `payAtPropertyAllowed` is true. |
| `whatsAppOptIn` | body | boolean | no | Guest agreed to WhatsApp messages about this booking. |
| `ref` | body | string | no | Tracked booking link code, credits the booking to that link. |

Request body:

```json
{
  "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
  "checkIn": "2026-11-14",
  "checkOut": "2026-11-17",
  "guests": 2,
  "guestName": "Elira Hoxha",
  "guestEmail": "guest@example.com",
  "guestPhone": "+355694821937",
  "guestCountry": "Albania",
  "notes": "Arriving late, around 22:00",
  "consentGiven": true,
  "payAtProperty": false,
  "whatsAppOptIn": false
}
```

Response `200`:

```json
{
  "success": true,
  "data": {
    "bookingId": "482913",
    "reference": "482913",
    "roomName": "Sea View Double",
    "checkIn": "2026-11-14",
    "checkOut": "2026-11-17",
    "nights": 3,
    "guests": 2,
    "total": 235,
    "deposit": 70.5,
    "currency": "EUR",
    "status": "pending",
    "payAtProperty": false,
    "freeCancellationUntil": "2026-11-11"
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `400 VALIDATION_ERROR` A rule was broken, for example consent missing, dates in the past, or more guests than the room sleeps. `message` says which.
- `400` Field validation failed (problem details body, see Errors).
- `409 UNAVAILABLE` The room was taken meanwhile, a closed date or minimum stay applies, or the page is not live.

Example:

```bash
curl -X POST "https://new.yourdailystay.com/api/public/booking/1042" \
  -H "Content-Type: application/json" \
  -d '{"roomId":"8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30","checkIn":"2026-11-14","checkOut":"2026-11-17","guests":2,"guestName":"Elira Hoxha","guestEmail":"guest@example.com","consentGiven":true}'
```

> **Note:** Calling these from your server instead of the browser? Every guest then shares your server's IP address and its 60 requests a minute. Cache the property response and keep availability calls to real searches.

## Online payments

Optional. Lets a guest pay the deposit, or the whole stay, right after booking. Needs Stripe or PayPal connected in Property Settings, Payments tab.

1. Read the providers. Empty means no online payment.
2. Start a payment for the `bookingId` you got back from the reservation.
3. Finish it in the browser with Stripe.js (`clientToken` is the client secret) or the PayPal buttons (`clientToken` is the order id).
4. Confirm the payment so DailyStay records it against the booking.

### GET /public/payments/{propertyId}/providers

**Payment providers.** Which ways this property can be paid online. An empty list means no online payment: show the booking as pending and let the property collect.

- URL: `GET https://new.yourdailystay.com/api/public/payments/{propertyId}/providers`
- Auth: None

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `propertyId` | path | string | yes | The property id. |

Response `200`:

```json
{
  "success": true,
  "data": [
    "stripe",
    "paypal"
  ],
  "error": null,
  "pagination": null
}
```

Example:

```bash
curl "https://new.yourdailystay.com/api/public/payments/1042/providers"
```

### POST /public/payments/{propertyId}/booking/{bookingId}

**Start a payment.** Opens a payment for a booking and returns what the browser needs to finish it. The amount is worked out on the server: the deposit percentage when the property takes deposits, otherwise the whole stay. For Stripe, `clientToken` is the PaymentIntent client secret and `publishableKey` the Stripe publishable key. For PayPal, `clientToken` is the order id and `publishableKey` the PayPal client id.

- URL: `POST https://new.yourdailystay.com/api/public/payments/{propertyId}/booking/{bookingId}`
- Auth: None

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `propertyId` | path | string | yes | The property id. |
| `bookingId` | path | string | yes | `bookingId` from the reservation response. |
| `provider` | body | string | yes | `stripe` or `paypal`. |

Request body:

```json
{
  "provider": "stripe"
}
```

Response `200`:

```json
{
  "success": true,
  "data": {
    "paymentId": "c41d7e02-93b5-4f6a-8e1c-5a9b2f0d7e64",
    "provider": "stripe",
    "clientToken": "pi_3Q8...secret_...",
    "publishableKey": "pk_live_...",
    "amount": 70.5,
    "currency": "EUR",
    "isLive": true
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `400 PAYMENT_ERROR` Online payments are off, the booking is cancelled, or there is nothing to pay.

Example:

```bash
curl -X POST "https://new.yourdailystay.com/api/public/payments/1042/booking/482913" \
  -H "Content-Type: application/json" \
  -d '{"provider":"stripe"}'
```

### POST /public/payments/confirm/{paymentId}

**Confirm a payment.** Call when the guest comes back from paying. DailyStay asks the provider directly whether the money arrived, so this call cannot mark a booking paid by itself. Stripe payments are also confirmed by Stripe's own webhook if the guest closes the tab.

- URL: `POST https://new.yourdailystay.com/api/public/payments/confirm/{paymentId}`
- Auth: None

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `paymentId` | path | string | yes | `paymentId` from Start a payment. |

Response `200`:

```json
{
  "success": true,
  "data": {
    "id": "c41d7e02-93b5-4f6a-8e1c-5a9b2f0d7e64",
    "bookingId": "482913",
    "provider": "stripe",
    "amount": 70.5,
    "refundedAmount": 0,
    "currency": "EUR",
    "status": "paid",
    "kind": "deposit",
    "failureReason": null,
    "createdAt": "2026-10-21T09:13:02.104",
    "capturedAt": "2026-10-21T09:13:40.881",
    "refundedAt": null
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `404 NOT_FOUND` No such payment.
- `400 PAYMENT_ERROR` The provider refused or could not be reached.

Example:

```bash
curl -X POST "https://new.yourdailystay.com/api/public/payments/confirm/c41d7e02-93b5-4f6a-8e1c-5a9b2f0d7e64"
```

## Partner API

Everything your server needs to run against one property: read and change its rooms, prices, calendar and bookings. The key fixes the property, so no id appears in the URL, and each endpoint asks for the one scope it needs.

### GET /partner/whoami

**Check a key.** Returns the property this key belongs to and the scopes it carries. Call it once at start-up to fail fast on a wrong or revoked key.

- URL: `GET https://new.yourdailystay.com/api/partner/whoami`
- Auth: API key, no scope needed

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key, `ds_...`. |

Response `200`:

```json
{
  "success": true,
  "data": {
    "propertyId": "1042",
    "scopes": [
      "bookings:read"
    ]
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `401` Missing, unknown or revoked key, or the property is no longer on the Max plan.

Example:

```bash
curl "https://new.yourdailystay.com/api/partner/whoami" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

### GET /partner/bookings

**List bookings.** Bookings of the key's property, newest created first. `pagination` is `null`: keep raising `offset` until a page comes back shorter than `limit`.

- URL: `GET https://new.yourdailystay.com/api/partner/bookings`
- Auth: API key, scope `bookings:read`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `status` | query | string | no | Only bookings in this status: `pending`, `confirmed`, `checked_in`, `checked_out`, `cancelled` or `rejected`. |
| `limit` | query | integer | no | Page size. Default `100`, clamped to 1-200. |
| `offset` | query | integer | no | How many to skip. Default `0`. |

Response `200`:

```json
{
  "success": true,
  "data": [
    {
      "id": "482913",
      "displayId": null,
      "bookingGroupId": null,
      "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
      "propertyId": "1042",
      "reservationSourceId": null,
      "reservationSource": "Direct",
      "effectivePricePerNight": 78.33,
      "appliedRateRuleId": null,
      "appliedRateRuleName": null,
      "pricingMode": null,
      "emailDisabled": false,
      "guestName": "Elira Hoxha",
      "guestEmail": "guest@example.com",
      "guestPhone": "+355694821937",
      "guestCountry": "Albania",
      "guestPassportId": null,
      "checkIn": "2026-11-14T00:00:00",
      "checkOut": "2026-11-17T00:00:00",
      "guests": 2,
      "totalAmount": 235,
      "depositAmount": 0,
      "remainingAmount": 235,
      "status": "pending",
      "paymentStatus": "pending",
      "depositSent": false,
      "depositDate": null,
      "depositPaymentMethod": null,
      "notes": "Arriving late, around 22:00",
      "extraOptions": [],
      "emailStatuses": [],
      "createdAt": "2026-10-21T09:12:44.517",
      "updatedAt": "2026-10-21T09:12:44.517"
    }
  ],
  "error": null,
  "pagination": null
}
```

Errors:

- `401` Key rejected.
- `403` Key lacks `bookings:read`.

Example:

```bash
curl "https://new.yourdailystay.com/api/partner/bookings?status=confirmed&limit=50&offset=0" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

### GET /partner/bookings/{bookingId}

**Get one booking.** A single booking by id. Use it after a webhook arrives to read the current state.

- URL: `GET https://new.yourdailystay.com/api/partner/bookings/{bookingId}`
- Auth: API key, scope `bookings:read`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `bookingId` | path | string | yes | The booking id, for example `482913`. |

Response `200`:

```json
{
  "success": true,
  "data": {
    "id": "482913",
    "displayId": null,
    "bookingGroupId": null,
    "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "propertyId": "1042",
    "reservationSourceId": null,
    "reservationSource": "Direct",
    "effectivePricePerNight": 78.33,
    "appliedRateRuleId": null,
    "appliedRateRuleName": null,
    "pricingMode": null,
    "emailDisabled": false,
    "guestName": "Elira Hoxha",
    "guestEmail": "guest@example.com",
    "guestPhone": "+355694821937",
    "guestCountry": "Albania",
    "guestPassportId": null,
    "checkIn": "2026-11-14T00:00:00",
    "checkOut": "2026-11-17T00:00:00",
    "guests": 2,
    "totalAmount": 235,
    "depositAmount": 0,
    "remainingAmount": 235,
    "status": "confirmed",
    "paymentStatus": "pending",
    "depositSent": false,
    "depositDate": null,
    "depositPaymentMethod": null,
    "notes": "Arriving late, around 22:00",
    "extraOptions": [],
    "emailStatuses": [],
    "createdAt": "2026-10-21T09:12:44.517",
    "updatedAt": "2026-10-21T09:12:44.517"
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `401` Key rejected.
- `403` Key lacks `bookings:read`.
- `404 NOT_FOUND` No such booking on this property.

Example:

```bash
curl "https://new.yourdailystay.com/api/partner/bookings/482913" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

### POST /partner/bookings

**Create a booking.** Adds a booking to the calendar, for a reservation your system took itself (a direct sale, a phone call). Send `totalAmount` to book at an agreed price (a quote you gave the guest); leave it out and DailyStay prices the stay from the room's rates and rate rules. The deposit follows the property's deposit setting. The same checks apply as in the dashboard: dates already taken, closed ranges and minimum stays are refused. Guests are not emailed unless you set `sendEmails`. For a reservation made by a visitor on a hotel website, use the Public Booking API instead: it starts as `pending` and is accepted by the property.

- URL: `POST https://new.yourdailystay.com/api/partner/bookings`
- Auth: API key, scope `bookings:write`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `roomId` | body | string | yes | The room to book. |
| `guestName` | body | string | yes | Up to 200 characters. |
| `guestEmail` | body | string | no | Without one a placeholder is stored and no email can be sent. |
| `guestPhone` | body | string | no | International format. |
| `guestCountry` | body | string | no | Country name. |
| `checkIn` | body | date | yes | Arrival, `YYYY-MM-DD`. |
| `checkOut` | body | date | yes | Departure, `YYYY-MM-DD`. |
| `guests` | body | integer | no | 1-20. Default 1. |
| `notes` | body | string | no | Free text. |
| `source` | body | string | no | Where it came from, shown as the booking source. Default `Partner API`. |
| `status` | body | string | no | One of the booking statuses. Default `confirmed`. |
| `totalAmount` | body | number | no | Agreed total for the whole stay. Without it the stay is priced from the rates. |
| `sendEmails` | body | boolean | no | Let DailyStay email the guest. Default `false`. |

Request body:

```json
{
  "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
  "guestName": "Elira Hoxha",
  "guestEmail": "guest@example.com",
  "checkIn": "2026-11-14",
  "checkOut": "2026-11-17",
  "guests": 2,
  "source": "Joy Apartments website",
  "status": "confirmed"
}
```

Response `200`:

```json
{
  "success": true,
  "data": {
    "id": "482913",
    "displayId": null,
    "bookingGroupId": null,
    "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "propertyId": "1042",
    "reservationSourceId": null,
    "reservationSource": "Joy Apartments website",
    "effectivePricePerNight": 78.33,
    "appliedRateRuleId": null,
    "appliedRateRuleName": null,
    "pricingMode": null,
    "emailDisabled": false,
    "guestName": "Elira Hoxha",
    "guestEmail": "guest@example.com",
    "guestPhone": "+355694821937",
    "guestCountry": "Albania",
    "guestPassportId": null,
    "checkIn": "2026-11-14T00:00:00",
    "checkOut": "2026-11-17T00:00:00",
    "guests": 2,
    "totalAmount": 235,
    "depositAmount": 0,
    "remainingAmount": 235,
    "status": "confirmed",
    "paymentStatus": "pending",
    "depositSent": false,
    "depositDate": null,
    "depositPaymentMethod": null,
    "notes": "Arriving late, around 22:00",
    "extraOptions": [],
    "emailStatuses": [],
    "createdAt": "2026-10-21T09:12:44.517",
    "updatedAt": "2026-10-21T09:12:44.517"
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `400 VALIDATION_ERROR` Unknown status or an invalid field.
- `403` Key lacks `bookings:write`.
- `409 BOOKING_ERROR` Dates taken, a closed range, a stay below the minimum, or an unknown room. `message` says which.

Example:

```bash
curl -X POST "https://new.yourdailystay.com/api/partner/bookings" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"roomId":"8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30","guestName":"Elira Hoxha","checkIn":"2026-11-14","checkOut":"2026-11-17","guests":2}'
```

### PATCH /partner/bookings/{bookingId}/status

**Change a booking's status.** Moves a booking to a new status: confirm a pending one, cancel or reject one (the nights become free again), or mark it checked in or out. Returns the booking.

- URL: `PATCH https://new.yourdailystay.com/api/partner/bookings/{bookingId}/status`
- Auth: API key, scope `bookings:write`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `bookingId` | path | string | yes | The booking id, for example `482913`. |
| `status` | body | string | yes | `pending`, `confirmed`, `checked_in`, `checked_out`, `cancelled` or `rejected`. |

Request body:

```json
{
  "status": "confirmed"
}
```

Response `200`:

```json
{
  "success": true,
  "data": {
    "id": "482913",
    "displayId": null,
    "bookingGroupId": null,
    "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "propertyId": "1042",
    "reservationSourceId": null,
    "reservationSource": "Direct",
    "effectivePricePerNight": 78.33,
    "appliedRateRuleId": null,
    "appliedRateRuleName": null,
    "pricingMode": null,
    "emailDisabled": false,
    "guestName": "Elira Hoxha",
    "guestEmail": "guest@example.com",
    "guestPhone": "+355694821937",
    "guestCountry": "Albania",
    "guestPassportId": null,
    "checkIn": "2026-11-14T00:00:00",
    "checkOut": "2026-11-17T00:00:00",
    "guests": 2,
    "totalAmount": 235,
    "depositAmount": 0,
    "remainingAmount": 235,
    "status": "confirmed",
    "paymentStatus": "pending",
    "depositSent": false,
    "depositDate": null,
    "depositPaymentMethod": null,
    "notes": "Arriving late, around 22:00",
    "extraOptions": [],
    "emailStatuses": [],
    "createdAt": "2026-10-21T09:12:44.517",
    "updatedAt": "2026-10-21T09:12:44.517"
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `400 VALIDATION_ERROR` Unknown status.
- `403` Key lacks `bookings:write`.
- `404 NOT_FOUND` No such booking.

Example:

```bash
curl -X PATCH "https://new.yourdailystay.com/api/partner/bookings/482913/status" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"confirmed"}'
```

### PUT /partner/bookings/{bookingId}/price

**Set a booking's price.** Sets the agreed total of a booking, for example after the guest accepted a price you quoted. A deposit already paid is kept; otherwise the deposit follows the property's deposit setting. Combine it with Change a booking's status to confirm the stay at that price.

- URL: `PUT https://new.yourdailystay.com/api/partner/bookings/{bookingId}/price`
- Auth: API key, scope `bookings:write`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `bookingId` | path | string | yes | The booking id. |
| `totalAmount` | body | number | yes | The agreed total for the whole stay. |

Request body:

```json
{
  "totalAmount": 420
}
```

Response `200`:

```json
{
  "success": true,
  "data": {
    "id": "482913",
    "displayId": null,
    "bookingGroupId": null,
    "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "propertyId": "1042",
    "reservationSourceId": null,
    "reservationSource": "Direct",
    "effectivePricePerNight": 140,
    "appliedRateRuleId": null,
    "appliedRateRuleName": null,
    "pricingMode": "manual",
    "emailDisabled": false,
    "guestName": "Elira Hoxha",
    "guestEmail": "guest@example.com",
    "guestPhone": "+355694821937",
    "guestCountry": "Albania",
    "guestPassportId": null,
    "checkIn": "2026-11-14T00:00:00",
    "checkOut": "2026-11-17T00:00:00",
    "guests": 2,
    "totalAmount": 420,
    "depositAmount": 126,
    "remainingAmount": 294,
    "status": "pending",
    "paymentStatus": "pending",
    "depositSent": false,
    "depositDate": null,
    "depositPaymentMethod": null,
    "notes": "Arriving late, around 22:00",
    "extraOptions": [],
    "emailStatuses": [],
    "createdAt": "2026-10-21T09:12:44.517",
    "updatedAt": "2026-10-21T09:12:44.517"
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `403` Key lacks `bookings:write`.
- `404 NOT_FOUND` No such booking.

Example:

```bash
curl -X PUT "https://new.yourdailystay.com/api/partner/bookings/482913/price" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"totalAmount":420}'
```

### Rooms and photos

### GET /partner/rooms

**List rooms.** Every room of the key's property with what its room class adds (guest limits, class title), its base price, amenities and photos. `name` is shared by rooms of the same kind, so show `name` with `number` ("Deluxe Apartment 101") to tell them apart. `type` names the room class; it matches `type` on `GET /partner/room-classes`. Read it to build or refresh your own room list, then match rooms by `id`, which never changes.

- URL: `GET https://new.yourdailystay.com/api/partner/rooms`
- Auth: API key, scope `rooms:read`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |

Response `200`:

```json
{
  "success": true,
  "data": [
    {
      "id": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
      "name": "Deluxe Apartment",
      "number": "101",
      "floor": "1",
      "disabledAccess": false,
      "type": "deluxe",
      "classTitle": "Deluxe apartment with sea view",
      "capacity": 4,
      "maxGuests": 4,
      "maxAdults": 3,
      "pricePerNight": 150,
      "description": "Corner apartment with a balcony over the harbour.",
      "status": "available",
      "amenities": [
        "Wi-Fi",
        "Air conditioning",
        "Balcony"
      ],
      "images": [
        "https://example.com/media/deluxe-1.jpg",
        "https://example.com/media/deluxe-2.jpg"
      ],
      "updatedAt": "2026-10-21T09:12:44.517"
    }
  ],
  "error": null,
  "pagination": null
}
```

Errors:

- `401` Key rejected.
- `403` Key lacks `rooms:read`.

Example:

```bash
curl "https://new.yourdailystay.com/api/partner/rooms" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

### GET /partner/rooms/{roomId}

**Get one room.** A single room, in the same shape as the list.

- URL: `GET https://new.yourdailystay.com/api/partner/rooms/{roomId}`
- Auth: API key, scope `rooms:read`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `roomId` | path | string | yes | The room id, for example `8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30`. |

Response `200`:

```json
{
  "success": true,
  "data": {
    "id": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "name": "Deluxe Apartment",
    "number": "101",
    "floor": "1",
    "disabledAccess": false,
    "type": "deluxe",
    "classTitle": "Deluxe apartment with sea view",
    "capacity": 4,
    "maxGuests": 4,
    "maxAdults": 3,
    "pricePerNight": 150,
    "description": "Corner apartment with a balcony over the harbour.",
    "status": "available",
    "amenities": [
      "Wi-Fi",
      "Air conditioning",
      "Balcony"
    ],
    "images": [
      "https://example.com/media/deluxe-1.jpg",
      "https://example.com/media/deluxe-2.jpg"
    ],
    "updatedAt": "2026-10-21T09:12:44.517"
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `401` Key rejected.
- `403` Key lacks `rooms:read`.
- `404 NOT_FOUND` No such room on this property.

Example:

```bash
curl "https://new.yourdailystay.com/api/partner/rooms/8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

### GET /partner/room-classes

**List room classes.** The classes (types) rooms belong to, with their guest limits, layout (`sizeSqm` in m², `beds` as guests read it, `bathrooms`, `sofaBeds`; each `null` until the property fills it in), selling copy, amenities and photos (`images`, the first is the cover; `imageUrl` repeats the cover). Use it to group rooms the way the property does, and show the class photos where a room has none of its own.

- URL: `GET https://new.yourdailystay.com/api/partner/room-classes`
- Auth: API key, scope `rooms:read`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |

Response `200`:

```json
{
  "success": true,
  "data": [
    {
      "id": "b3d2a7c1-5e84-4f19-a6c0-9d1e2f3a4b50",
      "propertyId": "1042",
      "type": "deluxe",
      "generalType": "apartment",
      "title": "Deluxe apartment with sea view",
      "shortTitle": "Deluxe",
      "description": "Spacious apartment for up to four guests.",
      "minGuests": 1,
      "maxGuests": 4,
      "maxAdults": 3,
      "allowChildren": true,
      "sizeSqm": 32,
      "beds": "1 double bed, 2 sofa beds",
      "bathrooms": 1,
      "sofaBeds": 2,
      "amenities": [
        "Wi-Fi",
        "Air conditioning"
      ],
      "imageUrl": "https://example.com/media/deluxe-1.jpg",
      "images": [
        "https://example.com/media/deluxe-1.jpg",
        "https://example.com/media/deluxe-2.jpg"
      ],
      "sortOrder": 0,
      "createdAt": "2026-08-02T10:00:00",
      "updatedAt": "2026-10-01T10:00:00"
    }
  ],
  "error": null,
  "pagination": null
}
```

Errors:

- `401` Key rejected.
- `403` Key lacks `rooms:read`.

Example:

```bash
curl "https://new.yourdailystay.com/api/partner/room-classes" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

### PUT /partner/rooms/{roomId}

**Update a room.** Changes a room's name, description, capacity, amenities or photos. Send only what changes; anything left out is kept. Prices are separate (see Set a room's price) so a key that may edit text cannot reprice the property. Returns the room as it is now.

- URL: `PUT https://new.yourdailystay.com/api/partner/rooms/{roomId}`
- Auth: API key, scope `rooms:write`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `roomId` | path | string | yes | The room id, for example `8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30`. |
| `name` | body | string | no | Up to 100 characters. Two rooms cannot share a name. |
| `description` | body | string | no | Up to 1000 characters. |
| `capacity` | body | integer | no | 1-20. |
| `amenities` | body | string[] | no | Replaces the amenity list. |
| `images` | body | string[] | no | Replaces the photos. Public https addresses only, at most 30. See Replace photos. |

Request body:

```json
{
  "description": "Corner apartment with a balcony over the harbour.",
  "amenities": [
    "Wi-Fi",
    "Air conditioning",
    "Balcony"
  ]
}
```

Response `200`:

```json
{
  "success": true,
  "data": {
    "id": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "name": "Deluxe Apartment",
    "number": "101",
    "floor": "1",
    "disabledAccess": false,
    "type": "deluxe",
    "classTitle": "Deluxe apartment with sea view",
    "capacity": 4,
    "maxGuests": 4,
    "maxAdults": 3,
    "pricePerNight": 150,
    "description": "Corner apartment with a balcony over the harbour.",
    "status": "available",
    "amenities": [
      "Wi-Fi",
      "Air conditioning",
      "Balcony"
    ],
    "images": [
      "https://example.com/media/deluxe-1.jpg",
      "https://example.com/media/deluxe-2.jpg"
    ],
    "updatedAt": "2026-10-21T09:12:44.517"
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `400 VALIDATION_ERROR` A photo address is not a public https URL, or there are more than 30.
- `400 UPDATE_ROOM_ERROR` The change was refused, for example a duplicate room name.
- `403` Key lacks `rooms:write`.
- `404 NOT_FOUND` No such room.

Example:

```bash
curl -X PUT "https://new.yourdailystay.com/api/partner/rooms/8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"description":"Corner apartment with a balcony over the harbour."}'
```

### PUT /partner/rooms/{roomId}/images

**Replace photos.** Replaces the room's photos with these addresses, in this order; the first one leads on the booking page. DailyStay stores the addresses and shows each photo from where it is hosted, so host the files yourself on a public https address and keep them there. Send an empty list to remove every photo. Duplicates are dropped.

- URL: `PUT https://new.yourdailystay.com/api/partner/rooms/{roomId}/images`
- Auth: API key, scope `rooms:write`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `roomId` | path | string | yes | The room id, for example `8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30`. |
| `images` | body | string[] | yes | Absolute `https://` URLs without a user name or password, each up to 2048 characters, at most 30. |

Request body:

```json
{
  "images": [
    "https://example.com/media/deluxe-1.jpg",
    "https://example.com/media/deluxe-2.jpg"
  ]
}
```

Response `200`:

```json
{
  "success": true,
  "data": {
    "id": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "name": "Deluxe Apartment",
    "number": "101",
    "floor": "1",
    "disabledAccess": false,
    "type": "deluxe",
    "classTitle": "Deluxe apartment with sea view",
    "capacity": 4,
    "maxGuests": 4,
    "maxAdults": 3,
    "pricePerNight": 150,
    "description": "Corner apartment with a balcony over the harbour.",
    "status": "available",
    "amenities": [
      "Wi-Fi",
      "Air conditioning",
      "Balcony"
    ],
    "images": [
      "https://example.com/media/deluxe-1.jpg",
      "https://example.com/media/deluxe-2.jpg"
    ],
    "updatedAt": "2026-10-21T09:12:44.517"
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `400 VALIDATION_ERROR` An address is not public https (`data:`, `http:` and `javascript:` are refused), or there are more than 30.
- `403` Key lacks `rooms:write`.
- `404 NOT_FOUND` No such room.

Example:

```bash
curl -X PUT "https://new.yourdailystay.com/api/partner/rooms/8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30/images" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"images":["https://example.com/media/deluxe-1.jpg"]}'
```

### Prices

### GET /partner/pricing

**Read prices.** Base price per room, the weekend setting, and the rate rules. A rule applies to one room class between two dates and multiplies the base price: `weekdayMultiplier` on weekdays and `weekendMultiplier` on the days listed in `configuration.weekendDays` (0 is Sunday). Where rules overlap the highest `priority` wins. To get a total for exact dates, ask `GET /public/booking/{propertyId}/availability`: it applies every rule for you.

- URL: `GET https://new.yourdailystay.com/api/partner/pricing`
- Auth: API key, scope `pricing:read`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |

Response `200`:

```json
{
  "success": true,
  "data": {
    "configuration": {
      "propertyId": "1042",
      "autoApplyRates": true,
      "allowManualOverride": true,
      "roundingMode": "nearest_0_01",
      "weekendDays": [
        5,
        6
      ],
      "updatedAt": "2026-10-01T10:00:00"
    },
    "rules": [
      {
        "id": "e5a1c9d3-27b4-4c68-8f0a-3b6d9e1a2c47",
        "propertyId": "1042",
        "name": "High season",
        "roomClassType": "deluxe",
        "startDate": "2027-07-01T00:00:00",
        "endDate": "2027-08-31T00:00:00",
        "weekdayMultiplier": 1.4,
        "weekendMultiplier": 1.6,
        "reservationSourceId": null,
        "reservationSource": null,
        "sourceAdjustmentType": "none",
        "sourceAdjustmentValue": 0,
        "minStayNights": 3,
        "maxStayNights": null,
        "priority": 10,
        "isActive": true,
        "createdAt": "2026-10-01T10:00:00",
        "updatedAt": "2026-10-01T10:00:00"
      }
    ],
    "rooms": [
      {
        "id": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
        "name": "Deluxe Apartment",
        "type": "deluxe",
        "pricePerNight": 150
      }
    ]
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `403` Key lacks `pricing:read`.

Example:

```bash
curl "https://new.yourdailystay.com/api/partner/pricing" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

### PUT /partner/rooms/{roomId}/price

**Set a room's price.** Sets the room's base price per night. Seasonal and weekend rates are rules applied on top of this base (see Read prices), so changing the base moves every rate that is a multiple of it.

- URL: `PUT https://new.yourdailystay.com/api/partner/rooms/{roomId}/price`
- Auth: API key, scope `pricing:write`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `roomId` | path | string | yes | The room id, for example `8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30`. |
| `pricePerNight` | body | number | yes | More than 0, in the property's currency. |

Request body:

```json
{
  "pricePerNight": 160
}
```

Response `200`:

```json
{
  "success": true,
  "data": {
    "id": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "name": "Deluxe Apartment",
    "number": "101",
    "floor": "1",
    "disabledAccess": false,
    "type": "deluxe",
    "classTitle": "Deluxe apartment with sea view",
    "capacity": 4,
    "maxGuests": 4,
    "maxAdults": 3,
    "pricePerNight": 160,
    "description": "Corner apartment with a balcony over the harbour.",
    "status": "available",
    "amenities": [
      "Wi-Fi",
      "Air conditioning",
      "Balcony"
    ],
    "images": [
      "https://example.com/media/deluxe-1.jpg",
      "https://example.com/media/deluxe-2.jpg"
    ],
    "updatedAt": "2026-10-21T09:12:44.517"
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `403` Key lacks `pricing:write`.
- `404 NOT_FOUND` No such room.

Example:

```bash
curl -X PUT "https://new.yourdailystay.com/api/partner/rooms/8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30/price" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"pricePerNight":160}'
```

### Calendar

### GET /partner/calendar

**Read the calendar.** What is closed and what is taken in a window. `rules` are closed dates (`kind: "block"`) and stay restrictions (`kind: "restriction"`: minimum nights, closed to arrival or departure). `bookings` are the nights held by stays that are not cancelled or rejected, with no guest details; use `bookings:read` for those. A rule with `roomId: null` applies to every room. `endDate` of a rule is its last night, inclusive; a booking's `checkOut` is the morning the guest leaves.

- URL: `GET https://new.yourdailystay.com/api/partner/calendar`
- Auth: API key, scope `calendar:read`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `from` | query | date | no | First day, `YYYY-MM-DD`. Default today. |
| `to` | query | date | no | Last day, `YYYY-MM-DD`. Default a year after `from`; windows are cut at two years. |

Response `200`:

```json
{
  "success": true,
  "data": {
    "from": "2026-11-01",
    "to": "2027-10-31",
    "rules": [
      {
        "id": "f7b2d4a6-1c83-49e5-b0d7-6a8c2e4f1b93",
        "propertyId": "1042",
        "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
        "kind": "block",
        "startDate": "2026-12-01",
        "endDate": "2026-12-05",
        "note": "Painting",
        "minNights": null,
        "closedToArrival": false,
        "closedToDeparture": false,
        "createdAt": "2026-10-20T08:00:00"
      }
    ],
    "bookings": [
      {
        "id": "482913",
        "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
        "checkIn": "2026-11-14",
        "checkOut": "2026-11-17",
        "status": "confirmed"
      }
    ]
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `400 VALIDATION_ERROR` `to` is before `from`.
- `403` Key lacks `calendar:read`.

Example:

```bash
curl "https://new.yourdailystay.com/api/partner/calendar?from=2026-11-01&to=2027-10-31" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

### POST /partner/calendar/blocks

**Close dates.** Closes a range of nights for one room, or for every room when `roomId` is empty. The change goes to the connected booking channels like a closure made in the dashboard. Nights that are already sold cannot be closed.

- URL: `POST https://new.yourdailystay.com/api/partner/calendar/blocks`
- Auth: API key, scope `calendar:write`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `roomId` | body | string | no | The room to close. Empty closes every room. |
| `startDate` | body | date | yes | First night closed, `YYYY-MM-DD`. |
| `endDate` | body | date | yes | Last night closed, inclusive. |
| `note` | body | string | no | Up to 200 characters, shown on the calendar. |

Request body:

```json
{
  "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
  "startDate": "2026-12-01",
  "endDate": "2026-12-05",
  "note": "Painting"
}
```

Response `200`:

```json
{
  "success": true,
  "data": {
    "id": "f7b2d4a6-1c83-49e5-b0d7-6a8c2e4f1b93",
    "propertyId": "1042",
    "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "kind": "block",
    "startDate": "2026-12-01",
    "endDate": "2026-12-05",
    "note": "Painting",
    "minNights": null,
    "closedToArrival": false,
    "closedToDeparture": false,
    "createdAt": "2026-10-21T09:12:44.517"
  },
  "error": null,
  "pagination": null
}
```

Errors:

- `400 VALIDATION_ERROR` The range is not valid.
- `403` Key lacks `calendar:write`.
- `409 DATES_BOOKED` Some of those nights already have a stay. `message` says which.

Example:

```bash
curl -X POST "https://new.yourdailystay.com/api/partner/calendar/blocks" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"roomId":"8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30","startDate":"2026-12-01","endDate":"2026-12-05","note":"Painting"}'
```

### DELETE /partner/calendar/blocks/{ruleId}

**Open dates again.** Removes a closed range, using the `id` from the calendar or from Close dates. A closure that comes from a synced calendar feed cannot be removed here: the next import would put it back.

- URL: `DELETE https://new.yourdailystay.com/api/partner/calendar/blocks/{ruleId}`
- Auth: API key, scope `calendar:write`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |
| `ruleId` | path | string | yes | The id of the closed range. |

Response `200`:

```json
{
  "success": true,
  "data": true,
  "error": null,
  "pagination": null
}
```

Errors:

- `403` Key lacks `calendar:write`.
- `404 NOT_FOUND` No such closed range.
- `409 SYNCED_BLOCK` The closure belongs to a synced calendar feed.

Example:

```bash
curl -X DELETE "https://new.yourdailystay.com/api/partner/calendar/blocks/f7b2d4a6-1c83-49e5-b0d7-6a8c2e4f1b93" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

### GET /partner/ical

**List calendar feeds.** The iCal feeds the property has set up per room. `direction: "export"` feeds carry `url`, the address to give another system (Booking.com, Airbnb, your own site) so it can read this room's availability. `direction: "import"` feeds are the channel calendars DailyStay reads; their address is never returned, because it usually holds a private token. Use `lastImportedAt` and `lastError` to show whether a channel is healthy.

- URL: `GET https://new.yourdailystay.com/api/partner/ical`
- Auth: API key, scope `calendar:read`

| Parameter | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `X-Api-Key` | header | string | yes | Your key. |

Response `200`:

```json
{
  "success": true,
  "data": [
    {
      "id": "a9c3e5b7-4d12-4f68-90ab-1c2d3e4f5a67",
      "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
      "roomName": "Deluxe Apartment",
      "direction": "export",
      "label": "Availability for partners",
      "channel": "other",
      "url": "https://new.yourdailystay.com/api/ical/q7Zk2mXw9d.ics",
      "isActive": true,
      "lastImportedAt": null,
      "lastImportedCount": 0,
      "lastError": null
    },
    {
      "id": "b1d4f6a8-5e23-4a79-81bc-2d3e4f5a6b78",
      "roomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
      "roomName": "Deluxe Apartment",
      "direction": "import",
      "label": "Airbnb",
      "channel": "airbnb",
      "url": null,
      "isActive": true,
      "lastImportedAt": "2026-10-21T09:00:00",
      "lastImportedCount": 12,
      "lastError": null
    }
  ],
  "error": null,
  "pagination": null
}
```

Errors:

- `403` Key lacks `calendar:read`.

Example:

```bash
curl "https://new.yourdailystay.com/api/partner/ical" \
  -H "X-Api-Key: $DAILYSTAY_API_KEY"
```

### Booking statuses

| Status | Meaning |
| --- | --- |
| `pending` | Request received, not yet accepted by the property. Every public booking starts here. |
| `confirmed` | Accepted by the property. The room is held. |
| `checked_in` | The guest has arrived. |
| `checked_out` | The stay is over. |
| `cancelled` | Cancelled. The nights are free again. |
| `rejected` | Declined by the property. The nights are free again. |

Read every booking, 200 at a time:

```js
const all = []
for (let offset = 0; ; offset += 200) {
  const page = await dailyStay('/partner/bookings?limit=200&offset=' + offset)
  all.push(...page)
  if (page.length < 200) break
}
```

## Keep a website in sync

The same steps work for any property and any system: a hotel website, a channel manager, a booking app. DailyStay stays the source of truth for rooms, prices, the calendar and bookings; your system keeps its own presentation (display names, copy, layout) and sends back what only it has (photos, reservations it takes itself).

1. Create a key with **Full website sync** (or only the scopes you need) and call `GET /partner/whoami`. Turn off whatever a missing scope would need instead of failing later.
2. Read the catalogue: `GET /partner/rooms` and `GET /partner/room-classes`. Store each room's `id`; it never changes, and it is how you match a room to your own record.
3. Match rooms. Pair rooms you already have with DailyStay rooms by `id` (suggest pairs whose names match), let the owner confirm, and offer to create a record for any room that is new. Run this step again whenever a refresh brings a room you have not seen.
4. Keep your own display name per room if you want one. Treat `name` as DailyStay's name and never write it back unless the owner asks.
5. Read prices (`GET /partner/pricing`) for a "from" price, and ask `GET /public/booking/{propertyId}/availability` for the real total of a stay: it applies every rule.
6. Mirror the calendar: `GET /partner/calendar` for closed dates and occupied nights, or the iCal feeds from `GET /partner/ical` for systems that only speak iCal. Refresh on a timer and after each webhook.
7. Push what you own: photos with `PUT /partner/rooms/{roomId}/images` (public https addresses you host), closures with `POST /partner/calendar/blocks`, and reservations your system took with `POST /partner/bookings`.
8. Import history once with `GET /partner/bookings?limit=200&offset=...` (past stays included), then follow `booking.created`, `booking.updated` and `booking.cancelled` webhooks. Treat a webhook as a nudge and read the booking again.

Start-up check and catalogue refresh:

```js
const { scopes } = await dailyStay('/partner/whoami')
const can = (scope) => scopes.includes(scope)

const rooms = can('rooms:read') ? await dailyStay('/partner/rooms') : []
const unseen = rooms.filter((room) => !myRooms.has(room.id))
if (unseen.length) askOwnerToMatch(unseen)       // show your "match rooms" step

// Push photos you host. Public https addresses only.
if (can('rooms:write')) {
  await dailyStay('/partner/rooms/' + roomId + '/images', {
    method: 'PUT',
    body: { images: photoUrls },
  })
}
```

> **Note:** Photos are stored as addresses, not files. Keep the files on your own public https address for as long as they should show on the booking page, and send the full list again whenever it changes.

## Errors

| HTTP | error.code | When |
| --- | --- | --- |
| `400` | `VALIDATION_ERROR` | The request broke a booking rule. `message` says which, in words you can show a guest. |
| `400` | none (problem details) | A body field failed validation, for example a malformed email. See below. |
| `400` | `PAYMENT_ERROR` | A payment could not be started or confirmed. |
| `401` | none (empty body) | API key missing, unknown or revoked. |
| `403` | none (empty body) | API key lacks the scope. |
| `404` | `NOT_FOUND` | Unknown booking or payment, or the booking page is off. |
| `409` | `UNAVAILABLE` | The room is no longer free, or a closed date or minimum stay applies. |
| `409` | `BOOKING_ERROR`, `DATES_BOOKED`, `SYNCED_BLOCK` | A Partner API write was refused: the dates are taken, nights are already sold, or the closure belongs to a synced calendar feed. `message` says which. |
| `429` | `RATE_LIMITED` | Too many requests. Wait for the `Retry-After` seconds. |
| `5xx` | varies | Something failed on our side. Retry with backoff. |

400 problem details:

```json
{
  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "GuestEmail": [
      "The GuestEmail field is not a valid e-mail address."
    ]
  }
}
```

## Rate limits

| Endpoints | Limit |
| --- | --- |
| `/public/booking/...` | 60 requests a minute per IP address |
| `/public/payments/...` and `/partner/...` | 300 requests a minute per IP address |

Over the limit you get `429` with `error.code` `RATE_LIMITED` and a `Retry-After` header in seconds. Back off when you see it: an address that keeps going after `429`s is blocked for a while.

## Webhooks

Instead of polling, let DailyStay call your server when a booking changes. Add a subscription in Property Settings, Features tab, API and webhooks: an `https` URL and the events you want (none ticked means all). Copy the signing secret shown with it.

| Event | Sent when |
| --- | --- |
| `booking.created` | The property creates a booking in the DailyStay app. |
| `booking.updated` | A booking's status changes, for example a website request is confirmed, or a guest checks in. |
| `booking.cancelled` | A booking is cancelled or rejected. |

> **Note:** Reservations made through the Public Booking API do not send `booking.created`. You hear about them as `booking.updated` when the property confirms (or `booking.cancelled` when it rejects). Edits to dates or guest details do not send an event.

### Delivery

- `POST` to your URL with a JSON body and the header `Daily-Stay-Signature: t=<unix seconds>,v1=<hex HMAC>`.
- Any `2xx` within 8 seconds counts as delivered. Answer fast and do the work afterwards.
- A failed delivery is not retried. After 20 failures in a row the subscription stops being called; it stays listed with its last error. Fix the endpoint, then delete the subscription and add it again.
- Treat an event as a nudge: read the booking again with `GET /partner/bookings/{bookingId}`, and run a periodic sync to catch anything missed.

Payload:

```json
{
  "event": "booking.updated",
  "propertyId": "1042",
  "sentAt": "2026-10-21T09:30:02.1184310Z",
  "data": {
    "Id": "482913",
    "DisplayId": null,
    "BookingGroupId": null,
    "RoomId": "8f2c1a4e-6b0d-4c1e-9a57-2d4b9e1f7c30",
    "PropertyId": "1042",
    "ReservationSourceId": null,
    "ReservationSource": null,
    "EffectivePricePerNight": null,
    "AppliedRateRuleId": null,
    "AppliedRateRuleName": null,
    "PricingMode": null,
    "EmailDisabled": false,
    "GuestName": "Elira Hoxha",
    "GuestEmail": "guest@example.com",
    "GuestPhone": "+355694821937",
    "GuestCountry": "Albania",
    "GuestPassportId": null,
    "CheckIn": "2026-11-14T00:00:00",
    "CheckOut": "2026-11-17T00:00:00",
    "Guests": 2,
    "TotalAmount": 235,
    "DepositAmount": 0,
    "RemainingAmount": 235,
    "Status": "confirmed",
    "PaymentStatus": "pending",
    "DepositSent": false,
    "DepositDate": null,
    "DepositPaymentMethod": null,
    "Notes": "Arriving late, around 22:00",
    "ExtraOptions": [],
    "EmailStatuses": [],
    "CreatedAt": "2026-10-21T09:12:44.517",
    "UpdatedAt": "2026-10-21T09:12:44.517"
  }
}
```

> **Important:** Inside `data` the field names are PascalCase (`GuestName`, `CheckIn`), unlike the API's camelCase. `ReservationSource` is always `null` in webhooks; read the booking from the API if you need it.

### Verify the signature

Compute HMAC-SHA256, hex encoded, of `<t>.<raw request body>` with the subscription secret and compare it with `v1`. Use the raw bytes as received: parsing and re-serialising the JSON changes them. Reject timestamps older than five minutes to stop replays.

Node (Express):

```js
import crypto from 'node:crypto'
import express from 'express'

const app = express()

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries((header ?? '').split(',').map((part) => part.split('=')))
  const timestamp = Number(parts.t)
  if (!timestamp || !parts.v1) return false
  if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false

  const expected = crypto.createHmac('sha256', secret).update(timestamp + '.' + rawBody).digest('hex')
  return expected.length === parts.v1.length &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}

app.post('/webhooks/dailystay', express.raw({ type: 'application/json' }), (req, res) => {
  const raw = req.body.toString('utf8')
  if (!verify(raw, req.get('Daily-Stay-Signature'), process.env.DAILYSTAY_WEBHOOK_SECRET)) {
    return res.sendStatus(401)
  }
  res.sendStatus(200)

  const event = JSON.parse(raw)
  console.log(event.event, event.data.Id, event.data.Status)
})
```

## AI usage

Building with ChatGPT, Claude, Cursor or Copilot? Copy this whole documentation as one Markdown file and paste it into the chat with your task. Every endpoint, field and example on this page is in it.

Prompt to start with:

```text
You are helping me connect a hotel website to the DailyStay PMS.
Use only the endpoints and fields in the DailyStay API documentation below.
Do not invent endpoints, fields or statuses. Never put the API key in browser code.

Property id: 1042
What I am building: <describe your task>

<paste the DailyStay API documentation here>
```

Prompt for a full sync:

```text
You are building a two-way sync between my website and the DailyStay PMS.
Use only the endpoints, scopes and fields in the DailyStay API documentation below.
Never put the API key in browser code. Store the key on the server.

1. Call GET /partner/whoami and read the scopes. Disable any feature whose scope is missing.
2. Read rooms and room classes. Match them to my own rooms by DailyStay room id, suggest
   matches by name, and ask me before linking. List DailyStay rooms I have not matched.
3. Keep my own display name for a room if I set one. Do not write names back to DailyStay.
4. Mirror prices, the calendar and bookings from DailyStay. Import past bookings once, then
   follow webhooks and re-read the booking on each one.
5. Send DailyStay what only I have: photos (public https URLs I host), closed dates, and
   reservations my site took.

Property id: 1042
My stack: <describe it>

<paste the DailyStay API documentation here>
```

### Let an AI assistant work inside DailyStay

To have Claude or ChatGPT look up bookings and answer questions about the property directly, add `https://new.yourdailystay.com/mcp` as a custom connector. The assistant signs in through OAuth and refreshes its own tokens, so no API key is involved. Max plan.

Building with ChatGPT, Claude, Cursor or Copilot? Copy this whole documentation as one Markdown file and paste it into the chat with your task. Every endpoint, field and example on this page is in it.

Prompt to start with
You are helping me connect a hotel website to the DailyStay PMS.
Use only the endpoints and fields in the DailyStay API documentation below.
Do not invent endpoints, fields or statuses. Never put the API key in browser code.

Property id: 1042
What I am building: <describe your task>

<paste the DailyStay API documentation here>
Prompt for a full sync
You are building a two-way sync between my website and the DailyStay PMS.
Use only the endpoints, scopes and fields in the DailyStay API documentation below.
Never put the API key in browser code. Store the key on the server.

1. Call GET /partner/whoami and read the scopes. Disable any feature whose scope is missing.
2. Read rooms and room classes. Match them to my own rooms by DailyStay room id, suggest
   matches by name, and ask me before linking. List DailyStay rooms I have not matched.
3. Keep my own display name for a room if I set one. Do not write names back to DailyStay.
4. Mirror prices, the calendar and bookings from DailyStay. Import past bookings once, then
   follow webhooks and re-read the booking on each one.
5. Send DailyStay what only I have: photos (public https URLs I host), closed dates, and
   reservations my site took.

Property id: 1042
My stack: <describe it>

<paste the DailyStay API documentation here>

Let an AI assistant work inside DailyStay

To have Claude or ChatGPT look up bookings and answer questions about the property directly, add https://new.yourdailystay.com/mcp as a custom connector. The assistant signs in through OAuth and refreshes its own tokens, so no API key is involved. Max plan.