Skip to main content
PUT
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.

Path Parameters

provider_id
string
required

Your own identifier for the property. Unique to your workspace — it never collides with another workspace's.

Required string length: 1 - 128

Body

application/json

The whole property. Anything you omit is removed — this replaces rather than merges.

name
string
required

The property's title, as guests will see it.

Required string length: 1 - 120
property_type
string
required

A slug from GET /catalog/property-types. An unknown slug is a 400, never a silent default.

Maximum string length: 60
Pattern: ^[a-z0-9][a-z0-9_-]*$
Example:

"apartment"

address
Address · object
required

Where the property is, as your system pushes it. Delivery detail — building, floor, apartment number, directions — is not part of the push: write it with PUT /properties/{property_id}/address and read it back on PropertyReadAddress.

occupancy
Occupancy · object
required

How many people the property sleeps, and in what.

base_rate
Money · object
required

An amount and its currency, always together.

value is a decimal string, not a number. 1250.10 has no exact IEEE-754 representation, and a JSON parser that rounds it costs a cent on every booking. Parse it with a decimal type, not a float.

There is no bare amount anywhere in this API and no currency field sitting beside one. Two fields can be read separately; one object cannot.

description
string

Free text about the property. Never about a guest — see the data boundary guide.

Maximum string length: 4000
amenities
string[]

Amenity slugs from GET /catalog/amenities. Replaced whole on every push: a slug you omit is removed.

Maximum array length: 60
Maximum string length: 60
Pattern: ^[a-z0-9][a-z0-9_-]*$
photos
Photo · object[]

Photos by URL, in the order you want them shown. Replaced whole on every push.

Maximum array length: 30
check_in_time
string

Local time, 24-hour.

Pattern: ^([01]\d|2[0-3]):[0-5]\d$
Example:

"15:00"

check_out_time
string

Local check-out time, 24-hour HH:MM. Omitted means the platform default, 12:00.

Pattern: ^([01]\d|2[0-3]):[0-5]\d$
Example:

"12:00"

status
enum<string>

Whether you want the property sellable on Maat right now.

See ListingStatus.

Available options:
listed,
unlisted

Response

The property was created or replaced. Sandbox only — a production token receives 501.

provider_id
string
required

The identifier you addressed, echoed back so a queued write can be matched to its response.

result
enum<string>
required

Whether this push created the property or replaced one you had already pushed. See WriteResult.

Available options:
created,
updated
property_id
string<uuid>
required

Maat's own identifier for the property. Always present, unlike listing_id: the row exists from the moment this push commits, whether or not the property is publishable.

It is the handle every sub-resource write takes — PUT /properties/{property_id}/address and …/amenities — so a caller who created a property by their own reference never has to go and look ours up before editing it.

unit_id
string<uuid>
required

Maat's identifier for the physical unit this push created or replaced. Always present, for the same reason.

A property push writes exactly one unit today, and this is the handle PATCH /units/{unit_id} and PUT /units/{unit_id}/house-rules take.

listing_id
string<uuid> | null
required

Maat's listing identifier, once the property is complete enough to have one. null while it is not — never a placeholder.

Deliberately NOT unconditional like the two ids above, even though the row exists just as early: a listing addresses something a guest can reach, and handing it back before the property is publishable would read as "we are live". The other two address our own inventory and carry no such claim.

status
enum<string>
required

Whether the listing is on sale right now. A push that is missing something publication needs comes back unlisted. See ListingStatus.

Available options:
listed,
unlisted
publishable
boolean
required

Whether the property can be shown to guests as it stands.

missing
enum<string>[]
required

Machine-readable reasons publishable is false. Empty when it is true.

photos_processing is not a deficiency — your images are being fetched and processed, and it clears on its own. It is separate from photos precisely so you can tell "wait" from "send us more".

unlisted restates your own status, and it is reported alongside any real deficiency rather than instead of it, so you know whether the property is otherwise complete before you re-list it.

See MissingReason.

Why a listing is not publishable. A closed vocabulary.

Available options:
price,
amenities,
cover_photo,
photos,
photos_processing,
unlisted
Example:
unknown_amenities
string[]
required

Amenity slugs we do not recognise, echoed back rather than silently dropped. They are not linked to the property. One unmapped slug does not fail the push — an otherwise-good property should not be blocked while you map your vocabulary onto ours.

Example:
photos_rejected
PhotoRejection · object[]
required

Photo URLs that did not become photos. Empty when every one did.

missing: ["photos"] on its own is not actionable across a thirty-photo push, so each failure names your own URL and a reason. The reason vocabulary is deliberately coarse: a refused URL never says what the name resolved to.