> ## Documentation Index
> Fetch the complete documentation index at: https://developer.maatstays.wtf/llms.txt
> Use this file to discover all available pages before exploring further.

# Properties

> Push the properties you manage, keyed by your own identifier. Replace, not merge.

`PUT /v1/properties/{provider_id}` pushes one property into Maat. The `provider_id` in
the path is **your** identifier — unique within your integration, never across the
platform — which makes the operation idempotent by construction: retrying a timed-out
push is always safe, and there is no idempotency header to remember.

## Replace, not merge

The payload you send is the whole property. An amenity you omit is removed; a photo you
omit is dropped. This mirrors how listings are edited everywhere on Maat, and it is the
only version of "update" in which *remove this amenity* is expressible at all. The
practical consequence: build your push from your own catalogue every time, never from a
diff.

## A property can be accepted and not yet sellable

A property with no price, no photos or no amenities is accepted and held in an
incomplete state rather than published at a placeholder price. The response tells you
exactly where it stands:

| Field               | What it tells you                                                                                                            |
| :------------------ | :--------------------------------------------------------------------------------------------------------------------------- |
| `publishable`       | Whether the property can be shown to guests as it stands.                                                                    |
| `missing[]`         | Machine-readable reasons it cannot — `price`, `amenities`, `cover_photo`, `photos`, `photos_processing`, `unlisted`.         |
| `listing_id`        | Maat's identifier, once the property is complete enough to have one. `null` until then — never a placeholder.                |
| `unknown_amenities` | Amenity slugs we did not recognise, echoed back rather than silently dropped. One unmapped slug never blocks the whole push. |
| `photos_rejected`   | Photo URLs that did not become photos, each with a reason.                                                                   |

Branch on `missing`, and treat its two photo values differently — they ask for opposite
things:

* **`photos_processing` means wait.** Photos are fetched asynchronously, so the *first*
  push of a complete property legitimately comes back `publishable: false`. There is
  nothing to do; we fetch, process and publish on our own.
* **`photos` means send more.** The property does not have enough usable images.

## Photos are URLs, and unchanged URLs are free

You send photo URLs; Maat fetches each one and runs it through the same processing
pipeline a host upload goes through. Three rules follow:

1. **URLs must be `https`, on a public DNS name, with no redirects.** Anything else
   comes back in `photos_rejected` as `url_refused` — and the reason never says more
   than that, by design.
2. **An unchanged URL is never re-fetched**, so a nightly catalogue sync costs nothing
   for the photos that did not change.
3. **Changed image bytes need a new URL.** We treat the URL as the identity of the
   photo; publish edited images at a fresh path.

`position: 0` is the cover photo. Per-photo failures are named individually in
`photos_rejected` (`url_refused`, `fetch_failed`, `unsupported_type`, `too_large`,
`processing_failed`), so one dead CDN path in a thirty-photo push is findable.

## The address is where the property is — not how to get in

Send only the address fields in the schema. Delivery detail — building, floor,
apartment number, directions to the key box — is deliberately not part of this contract
in either direction, because it is private to the host and the confirmed guest. A
payload carrying such a field is refused with `validation_failed`, not silently
trimmed.

## Taking a property off sale

`status: "unlisted"` makes the property unsellable on Maat without deleting anything.
The response reports `unlisted` in `missing[]` *alongside* any real deficiency, so you
always know whether the property is otherwise complete before you re-list it. There is
no property deletion in the current contract.

## Availability

Live in the sandbox: a sandbox token creates real properties in your sandbox workspace.
Against a production workspace the request is authenticated and validated in full, then
answers `501` — see [the introduction](/#what-works-today-honestly).
