Skip to main content
A calendar block is one message: these nights are gone. When somebody books a property on your platform, you push a block so Maat stops selling those nights; when that reservation ends early, you release it. Three operations cover the whole lifecycle: external_uid is your identifier — typically your reservation id — unique to you. That makes every operation idempotent: re-sending a block unchanged is a no-op, re-sending it with different dates moves it (old nights released, new nights held, in one step), and releasing a block that is already gone is a 204 with nothing to retry.

Dates: check_out is exclusive

check_out is the departure day, not the last night held. A block from 2026-09-01 to 2026-09-05 holds four nights, and the departure day itself stays sellable for same-day turnover. The response restates nights so you can check the arithmetic. A single block can hold up to 730 nights — two years, enough for any legitimate long-term closure.

No guest data. Ever. This is a rule you are bound by

Maat has no relationship with somebody who booked on your platform, and therefore no lawful basis to hold anything about them. So the block schema has no field for a guest’s name, a booking reference, an amount, or a note — and no free-text field of any kind, because the obvious thing anyone writes in one is a guest’s name. This is enforced, not requested: an unrecognised field is a validation_failed naming the field, never a silent drop. If your integration framework “helpfully” attaches reservation details to outgoing payloads, you will find out on your first sandbox call — which is exactly when you want to find out. The only descriptive field is reason, a closed set — reserved, maintenance, owner_stay, other — enough to tell “somebody is staying” from “the boiler is broken” in both our audit trails, and structurally incapable of carrying more.

Conflicts: the whole range or none of it

If any night in your range already carries a Maat booking or a live hold, the push answers 409 and nothing is written. We never apply the free part of a range, because a partial apply you were told succeeded is the one failure you could not detect — you would stop holding those nights yourself, and the same room would be sold twice. The problem document names every colliding date in a machine-readable conflicts[] array (complete, unlike the human-readable sentence next to it). Act on the array: re-push a range that excludes those dates, or leave the block off if the stay cannot be honoured. Note that the conflict scan runs on every push, including an identical retry. If your nights were taken by a Maat booking after your original push, a re-push tells you so with a 409 — which is the fact you most need, not an error to suppress.

Release frees only what the block holds

DELETE removes the block and frees the nights it is still holding. A night that is unavailable for another reason — the property’s owner closed it, or a Maat booking has since taken it — stays unavailable. Releasing a block is “I no longer need these nights held”, never “declare these dates open”. There is no partial release: shorten a stay by re-pushing the shorter range with PUT.

Reading your blocks back

GET /v1/blocks lists the blocks you pushed into the workspace your key addresses — after a timeout or a 500, this is how you find out whether a push landed rather than guessing. Two boundaries are fixed and are not parameters: the blocks are yours (another partner syncing the same property never sees them, and you never see theirs), and the workspace is the one your key was issued for. Filter by date window with from/to, and page with page/page_size — see Pagination.

Addressing the property

Identify the property with either your own provider_id or Maat’s listing_id — and send exactly one. Today, use listing_id (it is returned when you push a property, and in every booking notification): resolving a block by provider_id is not enabled yet and answers 501 naming the working alternative. The reference marks the current status of each path.

Availability

Live in the sandbox — pushes, releases, reads and real 409 conflicts. Against a production workspace every request is authenticated and validated in full, then answers 501: keep holding those nights on your own platform until the production contract is enabled.