Skip to main content
POST
cURL

Authorizations

Authorization
string
header
required

A short-lived partner token, obtained by exchanging your API key at POST /token. Send it as Authorization: Bearer <access_token>.

Tokens live for fifteen minutes by default and never for more than an hour. Mint one per batch of work, not one per request.

The token carries the workspace and the environment its key was issued for. You cannot change either by asking: the exchange ignores everything in the request body for exactly that reason.

It also carries the key's scopes (scope, a list). Every operation names a scope as x-maat-scope, and a token whose key lacks it is refused with 403 insufficient_scope, naming the scope. Scopes are fixed when a key is issued and are re-read from the key on every request, so a key narrowed today is narrowed on its next call — the token's scope is what the key held at exchange time, for your own reading, never what authorizes a request.

x-maat-scope is the floor, and on the quick-action operations it is not the whole requirement. PUT and DELETE /listings/{listing_id}/quick-actions/{template_key} carry rules:write, and additionally require pricing:write when template_key begins pricing. — the capability depends on the template in the path, because a cleaning fee changes what a guest pays and a minimum stay does not. The refusal is the same 403 insufficient_scope, naming pricing:write. The two READS apply the same requirement without erroring: GET /listings/{listing_id}/quick-actions still lists the pricing.* rows but withholds their applied state, and GET /listings/{listing_id}/rules omits the pricing rules entirely and reports pricing_withheld in warnings. These four are the only operations in this document whose requirement is not exactly their x-maat-scope, and each of them says so machine-readably: x-maat-scope-additional lists the second scope it can require and x-maat-scope-additional-when says what happens without it.

Never send it as a cookie or in a query string, and never store it — store the API key, mint tokens from it.

Body

application/json

What to mint. There is no guest field, no amount and no currency, and none of them is coming: the guest is synthetic and minted by us, and the amount comes from your listing's own price for those dates — which is what makes the figures on the resulting webhook the shape a real booking's are.

listing_id
string<uuid>
required

The listing to book. Must be in a sandbox workspace, and must be a stay — an experience or a service is a 409 inventory_not_supported.

check_in
string<date>
required

First night of the stay.

check_out
string<date>
required

Departure day, EXCLUSIVE — the guest is gone by then. At most 30 nights after check_in, because the booking holds those nights on a real sandbox calendar.

guests
integer
required

The party SIZE. Never a per-guest anything.

Required range: 1 <= x <= 50
status
enum<string>
required

Which half of the lifecycle to watch. See SandboxBookingStatus.

Available options:
pending,
confirmed

Response

The booking, in exactly the shape GET /v1/bookings/{booking_reference} reads it back. Its events are already on their way.

One booking, as your workspace reads it.

There is NO guest identity here, anywhere: no name, no initial, no email, no phone, no reference you could join against another read. Every field is a fact about a STAY, not a fact about a person. console_url is the honest pointer to where a person IS seen — the host console, where an authenticated human sees who is arriving. That is deliberately slower than a field on this wire, and it is the point.

There is no cancellation over this API. Cancelling a confirmed stay creates refunds and penalties and belongs to a person in the app.

booking_reference
string<uuid>
required

Maat's own booking id, and the correlation key for every later event. Treat it as OPAQUE: store it and echo it back, but do not parse it or derive anything from its shape. It is a UUID today and the contract does not promise it always will be.

property
DeveloperBookingProperty · object
required

Which of your inventory was booked. One object rather than four ids at the root, so that the same accessor reads a booking, a booking event and a publication event.

inventory_kind
enum<string>
required

What was sold. This contract manages unit stays; a write against the other two is refused with inventory_not_supported. See InventoryKind.

Available options:
unit,
service,
experience
status
enum<string>
required

A closed vocabulary, not the platform's internal state. pending is the only state confirm and reject act on.

See BookingStatus.

Available options:
pending,
confirmed,
checked_in,
completed,
cancelled,
rejected
stay
DeveloperBookingStay · object
required

The stay itself — when, and how many people. Never who.

The two door times are the UNIT's own, for a booking whose inventory_kind is unit. A service or an experience hangs off no unit and has no door: those bookings carry the platform defaults (15:00 / 12:00) so the field is always present and always a real time, and an integration that staffs a door should branch on inventory_kind rather than treat them as the operator's own answer.

amount_paid
Money · object | null
required

What the guest paid, as one object. null when it cannot be stated — a legacy row with no recorded amount resolves to null, never to "0.00": zero would read as free, and null means "we do not know".

instant_booking
boolean
required

True when the listing takes bookings without the host approving each one.

cancellation
DeveloperBookingCancellation · object | null
required

Present only on a cancelled booking.

created_at
string<date-time>
required

When the booking was created in Maat.

console_url
string<uri>
required

The host console's page for this booking — where a person is seen.