Developers

Public API reference

Connect your CRM, invoicing system, or other tools to your Book2dream data — bookings, guests, programs, rooms, availability, and fares.

1Getting started

The Book2dream Public API lets a hotel connect its own CRM, invoicing system, or other external tools to its Book2dream data — bookings, guests, programs, rooms, availability, and fares.

It is a paid add-on on top of your plan, enabled per property by Book2dream — so it is switched on for one hotel at a time, not for a whole group at once. If Settings → API Keys shows it as not yet included, contact us and we will talk you through pricing and switch it on for the properties you want.

Every request goes over HTTPS to https://api.book2dream.com/public/v1, and both requests and responses are JSON.

The API is scoped to a single hotel: a key you create belongs to exactly one property, and every response it returns is limited to that property's data.

2
POST
Authentication

Authentication is a two-step exchange. Generate a key from your Book2dream dashboard under Settings → API Keys — the full secret is shown once, at creation, so store it somewhere safe. Then exchange that key for a short-lived access token, and send the token on every other endpoint. Creating a key needs the add-on to be active on that property; until it is, the screen shows what the Public API does rather than a key form.

The API key is accepted at this one endpoint and nowhere else. Sending it to a data endpoint returns a 401 telling you to exchange it first. The point of the exchange is that your long-lived secret crosses the network roughly four times an hour instead of on every single request, so it stops accumulating in proxy logs, APM traces and error reports.

Tokens last 15 minutes by default; the response tells you exactly how long in `expires_in`. Cache the token and reuse it until it is close to expiring, then exchange again — this endpoint is rate limited far more tightly than the rest of the API precisely because a correct client calls it rarely. A token also never outlives its key, so if the key has an expiry date closer than 15 minutes, `expires_in` is shortened to match.

This API is server-to-server only. It deliberately sends no CORS headers, so a call from browser JavaScript will be blocked — which is intentional, because reaching the API from a browser would mean shipping your secret key to everyone who loads the page. Keep the key on your own server and call the API from there.

Example request

bash
curl -X POST https://api.book2dream.com/public/v1/auth/token \
  -H "Authorization: Bearer sk_live_..."

Example response

json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "canGetBookingsList canGetRooms canGetAvailability"
}

3
GET
GET /auth/introspect

Describes the key behind your current access token — which hotel it belongs to, what it may call, and whether it has an expiry date. Useful as a connection test when setting an integration up. Takes an access token like every other endpoint, and needs no permission of its own.

Example request

bash
curl https://api.book2dream.com/public/v1/auth/introspect \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example response

json
{
  "hotelId": 1034,
  "hotelName": "Hotel Example",
  "keyName": "Zapier integration",
  "permissions": {
    "canGetBookingsList": true,
    "canGetBooking": true,
    "canGetGuestsList": false,
    "canGetGuest": false,
    "canGetPrograms": true,
    "canGetRooms": true,
    "canGetAvailability": true,
    "canGetFares": true,
    "canPostFare": false
  },
  "expiresAt": null
}

4Keeping a key secure

Your API key is the credential that matters: anyone holding it can mint tokens for your hotel's data with whatever permissions it carries. Three settings in the dashboard limit what a leaked key is worth, and all three are worth using.

Restrict the key to the addresses it will actually be used from. Under Settings → API Keys, Allowed IP addresses accepts single addresses (203.0.113.7) or an IPv4 range (203.0.113.0/29), and once set, requests from anywhere else are refused even with a valid key. Leave it empty and the key works from any address, which is the default. The restriction is enforced on the exchange and on every request the resulting token makes, so a stolen token is no more useful off-network than a stolen key.

Grant only the endpoints the integration needs, and revoke a key the moment it is no longer in use. Revoking takes effect immediately and also kills every access token already issued from that key — it does not wait for them to expire. A revoked key can never be reactivated. Editing a key's permissions or its allowed addresses works the same way: the change applies at once rather than when the current token runs out.

A key can also be given an expiry date. It works through the end of the day you choose and stops afterward, so a key issued for a trial, a seasonal integration, or a contractor's access winds itself down instead of relying on somebody remembering to revoke it. By default a key has no expiry and works until revoked.

  • A key restricted by IP returns the same 401 as an invalid key when called from an unlisted address. This is deliberate — it avoids confirming to a stranger that the key itself is valid — so if a working integration starts returning 401 after a network or hosting change, check its allowed addresses first.
  • There is no way to retrieve an existing secret. If a key may have leaked, create a replacement, move the integration over, then revoke the old one.
  • List the public address your server sends requests from, not its private or internal address. If your host uses a pool of outbound addresses, list the whole range as a CIDR block — or leave the restriction off rather than have requests fail intermittently when the host picks a different address.

5Permissions model

Keys are scoped per endpoint, not just per resource: when you create a key, you choose exactly which of the endpoints below it may call. There's no implicit read/write split — Get Bookings and Get Booking, for example, are two separate permissions, so a key can list bookings without ever seeing an individual booking's guest details.

A request to an endpoint your token isn't permitted for returns a 403 with a machine-readable error body (see below). Your token carries the permissions its key had at the moment it was issued, and the `scope` field in the exchange response lists them. Authentication itself isn't a permission — any valid token can call the introspection endpoint above.

  • Every error on this API — including permission and rate-limit errors — follows the same shape: { "error": { "code", "message", "statusCode" } }.
  • Requests are rate-limited per key — 60 per minute by default, counted in one-minute windows. Going over returns a 429 with a Retry-After header; wait that many seconds and retry. If your integration needs a higher limit, ask us rather than working around it with parallel keys.
  • Not every 403 is a permission problem. A 403 from the token exchange naming the add-on means the Public API is not active on that property — no change to the key's permissions will fix it, so stop retrying and contact us instead.
  • Treat a 401 on a data endpoint as "my token is stale": exchange the API key for a fresh one and retry the request once. That covers an expired token and a key whose permissions were just edited. If the retry also returns 401, the key itself is the problem — revoked, expired, or calling from an address it isn't allowed from — so stop rather than looping on the exchange endpoint, which is rate limited separately and tightly.

6
GET
GET /bookings

Returns a paginated list of this hotel's bookings.

Permission required: Get Bookings (list)

NameTypeRequiredDescription
pageNumberintegerOptional1-indexed page number. Defaults to 1.
pageSizeintegerOptionalResults per page, up to 50. Defaults to 30.

Example request

bash
curl "https://api.book2dream.com/public/v1/bookings?pageSize=30" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example response

json
{
  "data": [
    {
      "id": 58231,
      "reservationCode": "B2D-58231",
      "date": "2026-08-12",
      "checkIn": "2026-09-01",
      "checkOut": "2026-09-05",
      "bookingStatus": "CONFIRMED",
      "guestBookingStatus": "PENDING_CHECK_IN",
      "adults": 2,
      "children": 0,
      "infants": 0,
      "numberOfRooms": 1,
      "numberOfnights": 4,
      "subtotal": "620.00",
      "taxes": "0.00",
      "finalPrice": "620.00",
      "bookingChannel": "DIRECT"
    }
  ],
  "metadata": { "count": 214, "pageSize": 30, "pageNumber": 1, "totalPages": 8 }
}

Personal data: This endpoint does not include guest personal data — see GET Booking for the single-resource form, which does.

7
GET
GET /bookings/{id}

Returns one booking, including the full guest record attached to it.

Permission required: Get Booking (single)

Example request

bash
curl https://api.book2dream.com/public/v1/bookings/58231 \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example response

json
{
  "id": 58231,
  "reservationCode": "B2D-58231",
  "checkIn": "2026-09-01",
  "checkOut": "2026-09-05",
  "bookingStatus": "CONFIRMED",
  "finalPrice": "620.00",
  "especialRequest": "Late check-in, arriving around 11pm",
  "guest": {
    "id": 9021,
    "firstName": "Jane",
    "lastName": "Doe",
    "email": "jane@example.com",
    "phone": "+1 555 0100",
    "country": "US",
    "status": "CONFIRMED"
  }
}

Personal data: Includes the guest's name, contact details, and other personal data. Requires the Get Booking permission specifically — separate from Get Bookings (list).

8
GET
GET /guests

Returns a paginated list of guests who have booked at this hotel.

Permission required: Get Guests (list)

NameTypeRequiredDescription
pageNumberintegerOptional1-indexed page number. Defaults to 1.
pageSizeintegerOptionalResults per page, up to 50. Defaults to 30.

Example request

bash
curl "https://api.book2dream.com/public/v1/guests?pageSize=30" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example response

json
{
  "data": [
    { "id": 9021, "status": "CONFIRMED" }
  ],
  "metadata": { "count": 512, "pageSize": 30, "pageNumber": 1, "totalPages": 18 }
}

Personal data: The list form omits personal data entirely — no name, email, or phone. See GET Guest for the single-resource form, which includes it.

9
GET
GET /guests/{id}

Returns one guest's full record.

Permission required: Get Guest (single)

Example request

bash
curl https://api.book2dream.com/public/v1/guests/9021 \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example response

json
{
  "id": 9021,
  "firstName": "Jane",
  "lastName": "Doe",
  "email": "jane@example.com",
  "phone": "+1 555 0100",
  "fiscalNumber": "",
  "city": "Austin",
  "country": "US",
  "state": "TX",
  "postalCode": "78701",
  "status": "CONFIRMED"
}

Personal data: Includes the guest's personal data. Requires the Get Guest permission specifically — separate from Get Guests (list).

10
GET
GET /programs

Returns every rate program available at this hotel.

Permission required: Get Programs

Example request

bash
curl https://api.book2dream.com/public/v1/programs \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example response

json
[
  {
    "id": 595,
    "name": "Room Only",
    "description": "Non-refundable, room only",
    "minNightsStay": 2,
    "minBookingAnticipation": 0,
    "externalCode": "NRF-RO",
    "channexMealType": "NONE"
  }
]

11
GET
GET /rooms

Returns every room type configured at this hotel.

Permission required: Get Rooms

Example request

bash
curl https://api.book2dream.com/public/v1/rooms \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example response

json
[
  {
    "id": 586,
    "name": "Single Room",
    "isActive": true,
    "quantity": 6,
    "bedroomsQuantity": 1,
    "bathroomsQuantity": 1,
    "maximumGuests": 2,
    "limitOnChildren": 1,
    "limitOnAdults": 2,
    "externalCode": "SGL"
  }
]

12
GET
GET /availability

Returns per-day stock for one or more rooms over a date range. A date with no explicit stock row is still bookable — it falls back to the room's total quantity — so `availableStock` always reflects what's actually left to sell, not just what's been explicitly configured.

Permission required: Get Availability

NameTypeRequiredDescription
roomIdsinteger[]RequiredComma-separated room ids, all must belong to your hotel.
dateFromdateRequiredInclusive start date, YYYY-MM-DD.
dateTodateRequiredExclusive end date, YYYY-MM-DD.

Example request

bash
curl "https://api.book2dream.com/public/v1/availability?roomIds=586&dateFrom=2026-09-01&dateTo=2026-09-05" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example response

json
[
  { "roomId": 586, "date": "2026-09-01", "roomQuantity": 6, "fixedStock": 6, "bookingCount": 2, "availableStock": 4 },
  { "roomId": 586, "date": "2026-09-02", "roomQuantity": 6, "fixedStock": 6, "bookingCount": 3, "availableStock": 3 }
]

13
GET
GET /fares

Returns every fare configured at this hotel, across all rooms and programs.

Permission required: Get Fares (list)

Example request

bash
curl https://api.book2dream.com/public/v1/fares \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Example response

json
[
  {
    "id": 4410,
    "name": "Standard Rate",
    "adults": 2,
    "children": 0,
    "infants": 0,
    "validFrom": "2026-01-01",
    "validTo": "2026-12-31",
    "checkInFrom": "2026-01-01",
    "checkInTo": "2026-12-31",
    "isActive": true,
    "fixedPrice": false,
    "mondayPrice": "120.00",
    "roomId": 586,
    "segmentId": 12,
    "programId": 595,
    "paymentPolicyId": 3,
    "cancellationPolicyId": 2
  }
]

14
POST
POST /fares

Creates a fare — the only mutating endpoint in v1. It has the same field surface as the internal admin's fare creation, including day-of-week pricing and the linked room, program, segment, cancellation policy, and payment policy: every id you send must belong to (or be usable by) your hotel, or the request is rejected with a 400.

A successful call creates a real, versioned fare — the same immutable-snapshot mechanism the internal admin panel relies on for rate history — so treat it with the same care you would a change made directly in the dashboard.

Permission required: Post Fare (create)

NameTypeRequiredDescription
roomIdintegerRequiredMust belong to your hotel.
segmentIdintegerRequiredMust belong to your hotel.
programIdintegerRequiredMust be assigned to roomId.
paymentPolicyIdintegerRequiredMust be available for segmentId.
cancellationPolicyIdintegerRequiredMust be available for segmentId.
namestringRequiredFare name.
adults / children / infantsintegerRequiredParty size this fare is priced for.
fixedPricebooleanOptionalWhen true, price is per room regardless of party size.
validFrom / validTodateRequiredWhen the fare can be booked.
checkInFrom / checkInTodateRequiredWhich check-in dates this fare covers.
mondayPrice … sundayPricedecimalOptionalPer-day-of-week base price.

Example request

bash
curl -X POST https://api.book2dream.com/public/v1/fares \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Standard Rate",
    "description": "Standard flexible rate",
    "adults": 2,
    "children": 0,
    "infants": 0,
    "fixedPrice": false,
    "validFrom": "2026-01-01",
    "validTo": "2026-12-31",
    "checkInFrom": "2026-01-01",
    "checkInTo": "2026-12-31",
    "mondayPrice": 120,
    "tuesdayPrice": 120,
    "wednesdayPrice": 120,
    "thursdayPrice": 120,
    "fridayPrice": 150,
    "saturdayPrice": 150,
    "sundayPrice": 130,
    "roomId": 586,
    "segmentId": 12,
    "programId": 595,
    "paymentPolicyId": 3,
    "cancellationPolicyId": 2,
    "seasonFaresId": []
  }'

Example response

json
{
  "id": 4489,
  "name": "Standard Rate",
  "isActive": true,
  "fixedPrice": false,
  "roomId": 586,
  "segmentId": 12,
  "programId": 595,
  "paymentPolicyId": 3,
  "cancellationPolicyId": 2
}