Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions components/schemas/AvailabilityRequest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
41 changes: 33 additions & 8 deletions components/schemas/CalendarDay.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
37 changes: 37 additions & 0 deletions examples/availability-calendar.yaml
Original file line number Diff line number Diff line change
@@ -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"]
29 changes: 23 additions & 6 deletions paths/availability_calendar.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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" }
Loading