---
title: "Add Stay Room"
method: POST
path: "/trips/{trip_id}/itinerary/stays/{stay_id}/rooms/add"
tags: ["itinerary"]
---

# Add Stay Room

`POST /trips/{trip_id}/itinerary/stays/{stay_id}/rooms/add`

## Request

- Base URL: `https://stardrift.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://stardrift.ai/trips/{trip_id}/itinerary/stays/{stay_id}/rooms/add`
- Auth: none declared

Put a room the traveller chose onto a stay.

## Path parameters

- `trip_id` string, required
- `stay_id` string, required

## Headers

- `x-client-id` string, nullable

## Request body

- AddStayRoomRequest: Choose a room on a stay. Idempotent on the room's IDENTITY, not on `room.id`: the same room on the same terms for the same party is one selection, and re-sending it re-prices that selection instead of stacking a duplicate. Two of the SAME room is expressed by `occupancy`, never by sending it twice — LiteAPI prices N rooms as N occupancies on a single offer.
  - `room` StayRoom, required: One room the traveller chose on a stay. **`StayRoom` is to `Stay` what `Stay` is to `canonical_places`** — the traveller's instance of a room, not the room itself. See docs/HOTEL_ROOM_MODEL_ASSESSMENT.md §5. **One StayRoom is exactly one LiteAPI offer**, and therefore one prebook and one booking: an offer is one room type priced for N occupancies, so N rooms of the SAME type are N entries in `occupancies`, never N StayRooms. Two DIFFERENT room types cannot share an offer, so those are two StayRooms and two bookings. Identity is `hotel_id` + `mapped_room_id` and nothing else. Everything else — how many travellers and their ages, board, refundability, the price — is a choice about that room and may change without making it a different room.
    - `id` string, required
    - `source` 'liteapi'
    - `hotel_id` string, required
    - `mapped_room_id` integer, nullable
    - `canonical_room_id` string, nullable
    - `room_name` string, required
    - `occupancies` RoomOccupancy[]
      - `adults` integer, required
      - `children` integer[]
    - `board_type` string, nullable
    - `prefer_refundable` boolean, nullable
    - `chosen_rate` SeenRate: A price we OBSERVED for a room, and when. Never a held price. LiteAPI publishes no TTL on an offer — expiry surfaces only as error 4040 at prebook — so the honest shape is an observation with a timestamp rather than a quote that pretends to still be live. `offer_id` lives HERE rather than on `StayRoom` deliberately: an offer id is part of *a price seen at a time*, not part of *which room this is*. Pairing them means a stale rate is self-describing, and a surface can date the number instead of presenting it as current. Also the seed for price-change notification on a saved room: two `SeenRate`s for one `StayRoom` are a price movement.
      - `offer_id` string, required
      - `all_in_total` number, required
      - `price_per_night` number, nullable
      - `currency` string
      - `board_type` string, nullable
      - `board_name` string, nullable
      - `refundable` boolean
      - `cancel_until` string, nullable
      - `seen_at` string, date-time, required
    - `latest_seen_rate` SeenRate: A price we OBSERVED for a room, and when. Never a held price. LiteAPI publishes no TTL on an offer — expiry surfaces only as error 4040 at prebook — so the honest shape is an observation with a timestamp rather than a quote that pretends to still be live. `offer_id` lives HERE rather than on `StayRoom` deliberately: an offer id is part of *a price seen at a time*, not part of *which room this is*. Pairing them means a stale rate is self-describing, and a surface can date the number instead of presenting it as current. Also the seed for price-change notification on a saved room: two `SeenRate`s for one `StayRoom` are a price movement.
      - `offer_id` string, required
      - `all_in_total` number, required
      - `price_per_night` number, nullable
      - `currency` string
      - `board_type` string, nullable
      - `board_name` string, nullable
      - `refundable` boolean
      - `cancel_until` string, nullable
      - `seen_at` string, date-time, required
    - `booking_id` string, uuid, nullable

## Response `200`

Successful Response

- StayRoomResponse
  - `itinerary` object, required
  - `rooms` StayRoom[], required
    - `id` string, required
    - `source` 'liteapi'
    - `hotel_id` string, required
    - `mapped_room_id` integer, nullable
    - `canonical_room_id` string, nullable
    - `room_name` string, required
    - `occupancies` RoomOccupancy[]
      - `adults` integer, required
      - `children` integer[]
    - `board_type` string, nullable
    - `prefer_refundable` boolean, nullable
    - `chosen_rate` SeenRate: A price we OBSERVED for a room, and when. Never a held price. LiteAPI publishes no TTL on an offer — expiry surfaces only as error 4040 at prebook — so the honest shape is an observation with a timestamp rather than a quote that pretends to still be live. `offer_id` lives HERE rather than on `StayRoom` deliberately: an offer id is part of *a price seen at a time*, not part of *which room this is*. Pairing them means a stale rate is self-describing, and a surface can date the number instead of presenting it as current. Also the seed for price-change notification on a saved room: two `SeenRate`s for one `StayRoom` are a price movement.
      - `offer_id` string, required
      - `all_in_total` number, required
      - `price_per_night` number, nullable
      - `currency` string
      - `board_type` string, nullable
      - `board_name` string, nullable
      - `refundable` boolean
      - `cancel_until` string, nullable
      - `seen_at` string, date-time, required
    - `latest_seen_rate` SeenRate: A price we OBSERVED for a room, and when. Never a held price. LiteAPI publishes no TTL on an offer — expiry surfaces only as error 4040 at prebook — so the honest shape is an observation with a timestamp rather than a quote that pretends to still be live. `offer_id` lives HERE rather than on `StayRoom` deliberately: an offer id is part of *a price seen at a time*, not part of *which room this is*. Pairing them means a stale rate is self-describing, and a surface can date the number instead of presenting it as current. Also the seed for price-change notification on a saved room: two `SeenRate`s for one `StayRoom` are a price movement.
      - `offer_id` string, required
      - `all_in_total` number, required
      - `price_per_night` number, nullable
      - `currency` string
      - `board_type` string, nullable
      - `board_name` string, nullable
      - `refundable` boolean
      - `cancel_until` string, nullable
      - `seen_at` string, date-time, required
    - `booking_id` string, uuid, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/stardrift/apis/fastapi.md) - [All operations](https://skmtc.dev/stardrift/apis/fastapi/llms.txt) - [OpenAPI document](https://skmtc.dev/stardrift/apis/fastapi/revisions/be9ed1e6c38a?raw)
