Skip to main content
A sandbox workspace is not a mock server. It is a real Maat tenancy — same endpoints, same schemas, same validation, real create-then-read loops — with one property: nothing in it can reach the public. Push a property and it is really created; block four nights and a real calendar really holds them; read your blocks back and you see exactly what we hold. That realism is the point. The integration you prove in the sandbox is the integration that will run in production, byte for byte — including the failure paths. A 409 conflict, a validation_failed naming the field you got wrong, a photo URL we refuse: all of it behaves in the sandbox exactly as this documentation says.

The environment is not a parameter

Every API key is issued for exactly one workspace, and that workspace is either a sandbox or a production one. Your token carries which kind it was issued for, we compare it against the workspace on every request, and a disagreement in either direction is refused with workspace_scope_mismatch. There is no header, no query parameter and no request field that switches between them. To move from sandbox to production you exchange a production key — your code otherwise does not change.

What is isolated

Everything a sandbox workspace contains is excluded from every public surface:
  • sandbox properties never appear in search, on the map, or in the guest feed;
  • a sandbox listing has no public listing page;
  • no notification of any kind is sent for sandbox activity;
  • a sandbox booking can never end in a real charge — sandbox workspaces do not reach a payment provider at all, and anything that would lead to a charge is refused with a real error rather than faked. A booking can still be MADE, with the fixture below.
The exclusion is unconditional on every public listing surface — search, the map, the guest feed, a listing page and the public availability calendar. There is no way, for you or for us, to ask one of those to include sandbox rows; the one thing outside that wall is the public price quote, which answers for a sandbox listing id — an id nobody but you holds — and returns your own synthetic prices to whoever already has it.

Treat sandbox content as disposable

A nightly reset exists and is not switched on yet. It is scheduled for 03:30 UTC and wipes sandbox content — properties, units, listings, calendars, blocks and photo records — leaving everything your integration depends on: the workspace itself, your API key and your webhook registration. Until it is switched on, sandbox content simply accumulates; the changelog will say when that changes. Treat sandbox data as disposable either way. Build your sandbox setup as something you can re-run — the property and block contracts are idempotent by construction, so re-pushing your catalogue is one loop — rather than as something you would have to rebuild by hand.

Making a booking happen

A sandbox workspace cannot reach a payment provider, so no guest can ever check out on a sandbox listing. Without something to stand in for that, GET /v1/bookings would answer an empty page forever, confirm and reject would have nothing to act on, and the booking webhooks — the larger half of what you build against — could not be seen at all until real money was involved. So there is a fixture: POST /v1/sandbox/bookings. It creates a booking on one of your sandbox listings the way a real one is made — the same availability check, the same price quote, the same booking record, the same events — and delivers the resulting booking.* webhooks to your endpoint in the same envelope with the same signature. It is not a rehearsal of a booking. It is a booking, on inventory that only exists in your sandbox.
Pick which half of the lifecycle you want to watch. "status": "pending" mints a booking waiting on a host decision and sends booking.requested; drive it on with POST /v1/bookings/{booking_reference}/confirm or /reject and watch the next event arrive. "status": "confirmed" mints one already confirmed and sends booking.confirmed straight away. There is no guest field, and there will not be one. The guest is synthetic, minted by us, and the same one every time for your workspace. No money moves, no payment provider is contacted, and nobody is emailed or notified. The amount comes from your listing’s own price for those dates, so the figures on the webhook have the shape a real booking’s do. The booking holds real nights on your sandbox calendar, exactly as a real one would, so a second fixture over the same dates is refused with 409 conflict — and conflicts lists every night that is closed, the same member and the same shape PUT /v1/blocks/{external_uid} answers a collision with, so a handler you already wrote for one works for the other. Move the window, or free the nights from your Maat console. A stay may cover at most 30 nights. Two refusals share 409 here, so branch on code. conflict means the nights are taken and a different window will work. sandbox_only means you named the listing in your own live workspace — the fixture only ever books sandbox inventory, and retrying cannot change that. A listing that is an experience or a service answers 409 inventory_not_supported; the fixture creates stays only. Everything else is one 404 listing_not_found: an id that names nothing, and an id that names a listing outside your own live/sandbox pair. They are deliberately the same answer, so the refusal cannot be used to find out what exists in another workspace.
One more thing has to be true before a delivery leaves the platform: outbound webhook delivery is switched on per environment by a Maat operator, and it is not on by default while the platform is pre-launch. If your endpoint is registered and verified and a fixture booking produced no delivery, that is the reason. Developer access covers how that is asked for today. Nothing else about your integration changes when it is switched on; the deliveries simply start arriving. GET /v1/webhooks/deliveries shows what we hold either way.

What the sandbox still cannot prove

A charge. Nothing in a sandbox reaches a payment provider — not a test gateway, not test credentials, not a fabricated confirmation — so the money half of a booking is not exercisable there, and a fixture booking carries no payment behind its amount. Anything that would lead to a charge is refused with a real error rather than faked. This limitation is stated here rather than papered over; see the changelog for when it changes.

Getting a sandbox key

Your workspace issues its own, from your own Maat account — the steps, and what is not published yet, are on Get access.