diff --git a/components/schemas/AvailabilityRequest.yaml b/components/schemas/AvailabilityRequest.yaml index f6509e9..cc9fc6a 100644 --- a/components/schemas/AvailabilityRequest.yaml +++ b/components/schemas/AvailabilityRequest.yaml @@ -5,11 +5,19 @@ properties: productId: { type: string, format: uuid } dateRange: type: object + description: | + Both bounds are inclusive, and the range may span at most 92 days + counting both endpoints. `from` must not be after `to`. Violating + either rule returns `400`. required: [from, to] properties: - from: { type: string, format: date, example: "2026-07-15" } + from: + type: string + format: date + description: First day to report on, inclusive. + example: "2026-07-15" to: type: string format: date - description: Range length is capped server-side (see operation description). + description: Last day to report on, inclusive. At most 91 days after `from`. example: "2026-07-21" diff --git a/components/schemas/CalendarDay.yaml b/components/schemas/CalendarDay.yaml index f233dc8..6701477 100644 --- a/components/schemas/CalendarDay.yaml +++ b/components/schemas/CalendarDay.yaml @@ -7,23 +7,48 @@ properties: type: string enum: [AVAILABLE, SOLD_OUT, CLOSED] description: | - - `AVAILABLE` — at least one start time has capacity. - - `SOLD_OUT` — start times exist but none has capacity. - - `CLOSED` — no start times configured for this day. + Whether anything can be booked on this day. + + - `AVAILABLE` — at least one slot is free. `vacancies` is above `0`. + - `SOLD_OUT` — the product runs this day, but nothing is free: every + host is booked, away, or past their lead-time cutoff. `vacancies` is + `0` and `startingTimes` is empty. + - `CLOSED` — the product does not run this day at all: it has no start + times configured, or the whole day is blocked at product level. Hosts + being unavailable gives `SOLD_OUT`, never `CLOSED`. vacancies: type: integer minimum: 0 description: | - Total remaining bookable spots across all start times on this day. + How many slots are free, where one slot is one host at one start time. + + **This is not a seat count.** A slot is exclusive to a single party and + holds up to `Product.options[].restrictions.maxGuests`, so one vacancy + serves a party of 1 or a party of 8 alike. Never compare `vacancies` + against your party size — check `minGuests`/`maxGuests` for that. + + It is also not the length of `startingTimes`: two hosts free at `10:00` + give `vacancies: 2` with `startingTimes: ["10:00"]`. + `0` when `status` is `SOLD_OUT` or `CLOSED`. - example: 20 + example: 2 startingTimes: type: array items: type: string pattern: "^[0-2][0-9]:[0-5][0-9]$" description: | - Local-time start times configured for this day (`HH:MM`, 24h). - Empty when `status` is `CLOSED`. Populated for `AVAILABLE` and - `SOLD_OUT` — distinguishing "no times at all" from "times exist but full". + The start times you can actually book on this day. Local time to the + product's timezone, `HH:MM` 24-hour, sorted ascending, each time listed + once no matter how many hosts offer it. + + Safe to render straight into a time picker - every entry is bookable. + + **This is not the product's configured schedule.** A product offering a + start every 30 minutes can return a single entry here once host + availability, existing bookings, and lead-time rules are applied. The + advertised schedule is `Product.options[].availabilityLocalStartTimes`; + this field is always a subset of it. + + Empty when `status` is `SOLD_OUT` or `CLOSED`. example: ["10:00", "14:00"] diff --git a/examples/availability-calendar.yaml b/examples/availability-calendar.yaml new file mode 100644 index 0000000..d583bbb --- /dev/null +++ b/examples/availability-calendar.yaml @@ -0,0 +1,37 @@ +summary: 7-day window, mixed statuses +description: | + Note `2026-07-15`: three vacancies but only two starting times. Two hosts are + free at `10:00`, which is two free slots but one entry in `startingTimes`. + And `2026-07-16` shows a day where the product advertises a full schedule yet + a single time is bookable - `startingTimes` reports only what can actually be + booked, never the configured schedule. +value: + availability: + - date: "2026-07-15" + status: AVAILABLE + vacancies: 3 + startingTimes: ["10:00", "14:00"] + - date: "2026-07-16" + status: AVAILABLE + vacancies: 1 + startingTimes: ["10:00"] + - date: "2026-07-17" + status: SOLD_OUT + vacancies: 0 + startingTimes: [] + - date: "2026-07-18" + status: SOLD_OUT + vacancies: 0 + startingTimes: [] + - date: "2026-07-19" + status: CLOSED + vacancies: 0 + startingTimes: [] + - date: "2026-07-20" + status: AVAILABLE + vacancies: 4 + startingTimes: ["10:00", "10:30", "11:00", "15:00"] + - date: "2026-07-21" + status: AVAILABLE + vacancies: 2 + startingTimes: ["16:00", "16:30"] diff --git a/paths/availability_calendar.yaml b/paths/availability_calendar.yaml index 3573add..7a5f2f8 100644 --- a/paths/availability_calendar.yaml +++ b/paths/availability_calendar.yaml @@ -3,13 +3,27 @@ post: operationId: availabilityCalendar summary: Get availability calendar description: | - Returns a single object per day across the requested date range. - Each entry carries a coarse `AVAILABLE` / `SOLD_OUT` / `CLOSED` status, - a remaining vacancy count, and the list of configured start times — - enough to render a month grid with tooltips without a follow-up call. + Day-by-day availability for one product. - Possible errors: `BAD_REQUEST`, `UNAUTHORIZED`, `NOT_FOUND` (product - unknown or not bookable). + Returns one entry per day, inclusive of both `from` and `to`, in ascending + date order. Each entry answers three questions: can anything be booked + (`status`), how much is left (`vacancies`), and at which times + (`startingTimes`) - enough to render a month grid without a follow-up call + per day. + + `startingTimes` contains only times that can actually be booked, so it is + safe to drive a time picker directly from it. It is empty on `SOLD_OUT` + and `CLOSED` days. + + ### Range limits + + The range may span at most **92 days**, counting both endpoints. Past + dates are accepted and come back with no availability. + + Returns `400` if the range exceeds 92 days, or if `from` is after `to`. + + Possible errors: `BAD_REQUEST` (range invalid or too long), `UNAUTHORIZED`, + `NOT_FOUND` (product unknown or not bookable). requestBody: required: true content: @@ -24,6 +38,9 @@ post: content: application/json: schema: { $ref: "../components/schemas/AvailabilityCalendar.yaml" } + examples: + golden: + $ref: "../examples/availability-calendar.yaml" "400": { $ref: "../components/responses/BadRequest.yaml" } "401": { $ref: "../components/responses/Unauthorized.yaml" } "404": { $ref: "../components/responses/NotFound.yaml" }