Salta ai contenuti

Reservations and physical inventory

Questi contenuti non sono ancora disponibili nella tua lingua.

The reservation and inventory API is the persisted workflow for local front-desk work. Planning, reservations and rooms have operator screens in the web app. Authentication and GuestCraft-Workspace-Id work as described in the API guide.

Under /api/v1/properties/{property_id}:

Resource Operations
/room-types GET list, POST create
/room-types/{id} GET detail, PUT replace
/room-types/{id}/archive, /unarchive POST archive or restore
/units GET list (room_type_id, housekeeping, include_archived, paired free_from/free_to), POST create
/units/{id} GET detail, PUT replace
/units/{id}/archive, /unarchive POST archive or restore
/housekeeping PUT {unit_ids: [...], status}; atomic, up to 100 distinct units
/units/{id}/housekeeping PUT {status: dirty|clean|inspected, expected_housekeeping_version?, expected_updated_at?}; with the housekeeping_version or the housekeeping_updated_at the client read (or both), the unit already in that status answers unchanged and a status changed since answers 409 unit.status_conflict (API 4.6.0, for offline replays)
/reservations GET paginated search, POST create
/reservations/{id} GET full detail, PATCH details
/reservations/{id}/rooms/{room_id} PUT dates/type/occupancy, optional replacement price and nullable unit_id
/reservations/{id}/rooms/{room_id}/assignment PUT {unit_id}; null unassigns
/reservations/{id}/rooms/{room_id}/check_in POST {override_reason?}
/reservations/{id}/rooms/{room_id}/check_out POST
/reservations/{id}/rooms/{room_id}/undo_check_in, /undo_check_out POST same-property-day correction
/reservations/{id}/cancel POST {reason, simulated_amount?, override_amount?, override_reason?}; cancels every room still expected and posts the fee atomically (policies)
/availability?from=&to= GET capacity/sold/available by type and night
/calendar?from=&to= GET types, units, stays and availability in one consistent read
/front-desk?date= GET arrivals/departures/in-house, counts and occupancy; property today by default
/blocks GET dated unit blocks, POST create; optional from/to list window
/blocks/{id} PUT edit or DELETE future block with strong If-Match
/blocks/{id}/end POST {on} with strong If-Match to shorten a block
/maintenance_tickets GET list, POST report a fault for a unit, with an optional block; see housekeeping
/maintenance_tickets/{id} GET, and PATCH its details with strong If-Match; its status is PUT …/status

At workspace scope, /api/v1/guests supports POST create and GET paginated search (q on full name, email and phone). /guests/{id} supports GET and PUT. Contacts are not unique. Search ignores case and accents, treats % and _ literally, and also matches phone digits without punctuation. Guest duplicate review, merge, consents and ownership reassignment are described in the guest workflow. Guest PUT requires If-Match; GET/create/update return the guest ETag. /guests/{id}/reservations returns paginated summary history across workspace properties.

A member with inventory.configure creates a type with name, unique code, max_adults, max_children, max_total and default_occupancy, then creates units with room_type_id and a unique active name. A new unit starts dirty. With the built-in roles, every role may read; administrators, front desk and revenue managers manage guests and reservations (guests.manage, reservations.create, reservations.modify); administrators and front desk check in/out (stays.check_in, stays.check_out) and cancel (reservations.cancel); housekeeping may also set cleanliness (housekeeping.update); only the administrator configures inventory (inventory.configure) or overrides a dirty-room check-in with a reason (stays.override_readiness). A custom role holds exactly the codes it grants. Organization owners resolve to workspace admin.

Create a reservation with guest (an existing gst_… or inline GuestInput), source, optional confirmation/notes, and 1–20 rooms. Each room has its room_type_id, arrival, departure, adults, children, optional unit_id, and price: {nightly_amount: "120.00"} or {total_amount: "360.00"}. Money is an exact decimal string in the property’s currency; response totals include that currency. A stay total distributes remainder minor units to the earliest nights. Prices are frozen one row per night. A stay lasts at most 366 half-open nights. Date/type edits require complete replacement pricing; occupancy-only edits may retain the saved nightly prices. Existing assignment remains unless explicitly unassigned: send unit_id: null, or supply a matching new unit, in the same type-change PUT.

Creation returns 201 with Location. Reservation detail and mutations return ETag: "1" (the current integer version). Every later reservation mutation requires that exact strong tag in If-Match; a stale value returns 412 with the current ETag. An empty PATCH does not change the version or append audit/events. Missing, weak or malformed preconditions return field-linked 400. PATCH changes guest ID, source, notes or tentative→confirmed, retaining omitted fields; null clears notes.

const created = await gc.reservations.create({
path: { property_id },
body: {
guest: { full_name: "Synthetic Example" },
source: "direct",
rooms: [{ room_type_id, arrival: "2026-12-01", departure: "2026-12-03",
adults: 2, price: { total_amount: "240.00" } }],
},
});
await gc.reservationRooms.assign({
path: { property_id, id: created.id, room_id: created.rooms[0].id },
headers: { "if-match": `"${created.version}"` },
body: { unit_id },
});

The MIT SDK exposes corresponding namespaces (roomTypes, units, guests, reservations, reservationRooms, calendar, frontDesk, availability), with Hey API options (path, body, query, headers, signal). Generated query factories are exported by @guestcraft/react/query. Use a caller/workspace-specific transport; operational mutation cache invalidation remains the screen’s responsibility.

Tentative and confirmed reservations sell capacity. Check-in needs confirmed status, an assignment, arrival ≤ property today < departure, and a ready room: clean or inspected, or only inspected when the property’s housekeeping settings require inspection (or a reason recorded with stays.override_readiness). In-house type/arrival edits and cancellation are refused; an in-house room move uses the move and split actions below. Check-out marks the room departed, deactivates its segments and makes the unit of the last night dirty. Early check-out releases today and later sold nights, retaining prior sold nights and all frozen prices. Cancellation requires a reason and expected rooms; it releases all capacity and assignments. Cancelled/departed records remain readable.

Availability is active units minus sold minus out-of-order blocks. Out-of-service blocks leave sellable availability unchanged, but physical assignment and check-in avoid blocked units. OOO changes enqueue ARI availability rows in the same transaction; an OOO can make already sold, unassigned nights negative and returns overbooked_dates. Out-of-order blocks need blocks.manage_ooo, out-of-service blocks blocks.manage_oos and tickets maintenance.manage; with the built-in roles, administrators and front desk hold all three and housekeeping the last two. Closing a ticket leaves the room status and block state unchanged; it creates the clean or inspection task it names (housekeeping). See ADR 0033. The same ledger/read serves booking and reporting; concurrent last-room requests cannot both succeed. 409 inventory.unavailable includes full_nights: [{room_type_id,date,available}]. 409 assignment.conflict includes reservation_code. The assignment endpoint needs a unit of the booked type (assignment.room_type_mismatch); upgrades and mid-stay moves use the move and split actions below. Archives and unit type changes refuse future assignments, in-house stays and reductions below sold capacity, including unassigned bookings in distant future years. Property currency changes are refused once a reservation exists.

Reservation lists return ReservationSummary, with first arrival, last departure, room count, booker ID/name, per-room IDs/type and unit names/status/dates/totals, total and version. Nights and guest contacts are detail-only. Detail also includes room_type_name and segment unit_name, so it can render a direct reload without separate setup requests. Filters: arrival_from/to, departure_from/to (inclusive), in_house_on (non-cancelled sold stay night), confirmation, stay_status, room_type_id, q (code/guest name), guest_id, unit_id (active assignment), source, created_from/to (RFC 3339 instants, inclusive), include_cancelled. Cancelled reservations are excluded unless opted in or explicitly filtered. Room predicates match the same room. sort=arrival:asc|arrival:desc|departure:asc|departure:desc|created:asc|created:desc|code:asc|code:desc, with ID tie-breaking; default created:asc. Pages default to 20, maximum 100, with has_more and no full-table total_count. Opaque cursors bind workspace, parent, filters and sort. Changing those is cursor.invalid; editing sort keys can move rows between live pages. Use detail to reload one resource after a stale update.

Calendar/availability windows contain at most 62 nights, with exclusive to. Calendar excludes cancelled and archived data unless include_cancelled=true / include_archived=true. Current unit_id/unit_name only identify an active assignment: the unit of the first requested night the stay occupies, while segments lists every room it uses (see below). Reservation summary rooms (property lists and guest history) expose the same nullable stayed_unit_id and stayed_unit_name as CalendarStay; the list and detail show the historical room after departure without presenting it as a current assignment. Departed stays instead carry stayed_unit_id/stayed_unit_name from their inactive segment for the nights actually stayed. Cancelled stays and same-day departures with no occupied nights have null historical fields. effective_departure truncates an early checkout, while checked_out_on retains the actual action day. The historical interval is [arrival, effective_departure); it does not hold the unit or prevent a subsequent reservation from using it. Each stay includes guest ID, type/unit names, notes presence and a frozen total. Front desk returns overdue in-house departures and counts, pending/completed arrival/departure counts, housekeeping counts and not_ready_arrivals. occupied_units counts physical in-house units (including overdue), sold_rooms is the ledger for the selected night; future KPIs use the latter. Front-desk dates span 0001-01-01 through 9999-12-30. Setup lists default to active entries; include_archived=true opts in. Unit order is sort position, numeric name order, ID (2 < 11 < 101), using an explicit stable en comparison rather than the request locale.

Use units?free_from=2026-12-01&free_to=2026-12-03&room_type_id=... for assignment choices. The paired half-open window permits at most 366 nights; active overlaps are excluded and adjacent stays are allowed. Results retain housekeeping, housekeeping_updated_at and housekeeping_version.

A unit’s housekeeping_version starts at 0 and every change of its status advances it by one, whoever makes it: the single and bulk status changes, and the check-out or same-night room move that leaves the unit dirty; editing the unit keeps it. housekeeping_updated_at also moves forward by at least a millisecond on each change. Send the version you read as expected_housekeeping_version to make a change only if nothing changed the status since; expected_updated_at remains for clients that read no version. Type codes remain reserved after archive: restore with unarchive. Archived unit names may be reused; restoring a duplicate name returns 409. Restore a unit’s type first.

POST reservations?validate_only=true and PUT .../rooms/{room_id}?validate_only=true return 200 with proposed detail (including nightly split/total), validate_only: true and post-change availability. A conflict returns the usual 409. Preview persists nothing, including inline guests, audit or inventory. Transient IDs/version are proposals. Create preview sends neither Location nor ETag; room preview sends the current ETag. Submit again without the flag using the saved resource’s current If-Match; availability is rechecked. The preview does not reserve capacity or implement persistent quotes.

Undo of check-in or check-out needs stays.check_in or stays.check_out and works only on the recorded action’s property day. Undo checkout rechecks availability and overlapping assignments, then returns to in house; cleanliness stays dirty until housekeeping updates it. Failed recovery is atomic. Past arrivals, including room date edits, need inventory.configure (built-in: administrators); other members receive 422 at the arrival field. Historical entries follow the usual audit/event transaction.

All writes, thin events and identifier-only audit records commit together. Reasons stay in tenant records, outside audit/event payloads. Writes have no automatic retries or idempotency: after a lost response, inspect persisted state before retrying.

Groups and allotments, country tax rules, cancellation reinstatement and a housekeeping-specific contact-free detail projection remain planned; all built-in roles have the full guest read grant. Rates, policies, folios, room blocks and the night audit have their own workflows (rates, policies, folios, night audit). The supported browser workflow is documented in front desk. ADR 0024 records the locking, permission and representation decisions for review.

ADR 0042 gives a booked room one or more consecutive segments, each with unit_id, room_type_id, from, to and active. Each sold night consumes the capacity of its unit’s type, or of the booked type while unassigned: a unit of another type is an upgrade that keeps the booked type, plan, nightly prices and policy terms, moves the ledger between the two types and enqueues ARI availability for both. A same-type room change leaves the ledger and ARI untouched.

POST .../reservations/{id}/rooms/{room_id}/move takes unit_id (any active unit, or null for unassigned), an optional arrival keeping the stay’s length, and pricing. keep (the default) keeps the nightly amounts in order on new dates; keeping a rate-plan price there needs reservations.override_price. New dates must remain bookable under the room’s own plan, even when prices are kept. quote re-quotes the room’s own plan: preview with validate_only=true, then save with the preview’s nightly amounts as quoted_amounts and its policy_snapshot as quoted_policy_id/quoted_policy_version (quote.changed otherwise). Expected stays move whole into one segment; in-house stays move from tonight (the business date, or the local date while the business date lags; nights before it have been stayed), need stays.check_in, refuse dirty targets (assignment.unit_not_ready) and leave the room of tonight dirty. A stay with no night left before its departure is reservation.invalid_state; moves never need the night audit. Channel stays may change unit but not dates (reservation.channel_managed).

POST .../split takes date and unit_id: the nights from date to the end of the segment covering it go to the unit, so a date at a segment boundary reassigns that segment and a move back merges the segments. The room must be assigned (assignment.required); date must be inside the stay, not before tonight for an in-house stay and not before the business date for an expected one unless the member holds inventory.configure.

Both actions need reservations.modify and a strong If-Match, return the reservation with its new ETag, and record reservation.room_moved@1 or reservation.room_split@1 with identifiers and safe flags. Unchanged requests answer the stay at its current version without a write; their validate_only previews still carry validate_only and availability. Targets are checked for stays, blocks, occupancy (assignment.occupancy) and, for type or date changes, availability; the database exclusion constraints remain the last guard. A live segment always uses its unit’s current type, also in the database; undoing a departure or a no-show onto a unit whose type changed since answers assignment.room_type_mismatch.

const preview = await gc.reservationRooms.move({
path: { property_id, id, room_id },
headers: { "if-match": `"${version}"` },
query: { validate_only: "true" },
body: { unit_id: suiteUnitId },
});
await gc.reservationRooms.split({
path: { property_id, id, room_id },
headers: { "if-match": `"${version}"` },
body: { date: "2026-12-03", unit_id: otherUnitId },
});

Calendar and front-desk stays list the segments Planning draws: the live assignment, or the rooms a departed stay occupied clipped to its effective departure. Reservation summaries show the unit of the property’s local date, clamped to the stay. Room edits keep segments when dates and booked type do not change; an in-house departure change moves the end of the last segment. Folios follow frozen nights and are unchanged by room moves; reports keep the booked type.

POST /api/v1/guests requires origin_property_id alongside the contact fields. The chosen property must be visible in the selected workspace and the caller must have guests.manage there. Missing/invalid identifiers return field-linked 400 errors; an inaccessible property returns 403 for a scoped caller or 404 when not found. No guest, audit or event is written on failure. The workspace Guests screen offers New guest and a required owning-property selector. Inline new guests in reservation creation use the reservation property.

Reads expose nullable origin_property_id (optional for older response consumers). Editing contact details preserves ownership. Unrestricted members with guest-read permission see standalone guests; selected members see a guest owned by an allowed property or linked to a reservation there. Legacy unowned standalone guests remain visible only to unrestricted members until linked to an allowed reservation. No ownership reassignment or legacy bulk backfill is added. This authorized pre-release correction makes the create field mandatory on the existing /api/v1 resource, so the document revision increments to 3.0.0; existing clients must regenerate and supply it.

New OTA-imported guests use the booking property as their owner, as inline reservation guests do. Updates preserve an existing owner, including a legacy null. An unrestricted administrator with guests.reassign may correct ownership using a versioned, audited write. See guest identity.

POST .../rooms/{room_id}/resize takes arrival, departure, pricing (keep or quote) and the move workflow’s quote confirmation fields. Preview with validate_only=true; then confirm the returned nights/terms under the original strong If-Match. The existing room date-change rules, ledger, assignment checks and folio reconciliation apply: retained nights keep frozen prices; shortening needs no quote or override; added nights default to the own-plan quote (manual stays use edge prices). Explicit edge pricing for added planned nights requires reservations.override_price. Added planned nights must remain bookable regardless of price choice; a refused preview includes reasons. The new window must overlap the stay. Expected direct stays can change either outer edge. In-house stays keep arrival and nights already stayed; only departure changes. OTA dates remain channel managed. No unit is selected by resize.

PUT .../rooms/{room_id}/do_not_move takes { "enabled": true } or false, requires reservations.set_do_not_move at the property, and returns the versioned reservation. The flag is do_not_move on the reservation room and calendar/front-desk stay. A flagged stay’s move, split, assignment or date edit must explicitly send confirm_do_not_move: true; absent confirmation is refused, including outside Planning. Setting/clearing is audited.

Successful planning changes return planning_change_id. POST .../rooms/{room_id}/undo takes that identifier and the original saved response’s ETag in If-Match. The server retains the prior room state for the originating actor and restores it through the existing room write workflow: every segment, date, frozen night and quote/policy state. The client cannot supply replacement prices or segments. A receipt is usable once, is bound to the actor, reservation and room, and expires logically after ten minutes. Expired rows are removed in property-scoped batches on later receipt writes; an idle property has no physical-deletion deadline. A concurrent edit is never overwritten; reload and review it. The web exposes the inverse for ten seconds, with the toast’s accessible hover/focus pauses, and Ctrl/⌘ Z in Planning runs the same action outside text fields/modals. No write is automatically retried.

The flag, resize and inverse all use the normal tenant transaction, inventory gates and atomic audit/events. Housekeeping reschedules its tasks from the same room changes, in the same transaction: a resize or an Undo moves or cancels the departure clean and the clean of a room a move vacates, also on a future move day (housekeeping). Groups/allotments and booking-engine sales remain outside planning.