> ## 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.

# Scopes

> The whole vocabulary, what each one reaches, how to ask for the least, and the four operations whose requirement is not their scope.

A key is issued with a fixed list of **scopes**, and every operation's page in the API
reference names the one it requires as `x-maat-scope` — see
[Create or replace a property](/api-reference/properties/create-or-replace-a-property) for
what that looks like on a page. A token minted from a key without it is refused with
`403 insufficient_scope`, which — unlike the `401` — **names the scope you lack**, because
that is a fact about your own credential rather than about anybody else's data.

Scopes are fixed **when the key is issued**. They cannot be widened later; a key that needs
another one is a new key — see [Managing your keys](/guides/managing-keys).

## The vocabulary

Seventeen scopes. Fifteen of them authorize contract operations; the other two authorize a
route with the key itself, and reach no workspace data at all.

### Contract scopes

| Scope | What it reaches |
| :- | :- |
| `catalog:read` | Properties, units, listings, a property's photos, and the three reference catalogues. |
| `catalog:write` | The property push, the address and amenity replacements, the unit edits, and the listing patch. |
| `media:write` | Presigning photo uploads, reordering photos, deleting one. |
| `calendar:read` | `GET /v1/blocks` and a listing's availability. |
| `calendar:write` | Pushing and releasing your own blocks, and the host calendar blocks. |
| `pricing:write` | `PUT /v1/listings/{listing_id}/pricing` — the nightly rate and the cleaning fee on a listing. |
| `rules:write` | The quick actions (list, apply, remove) and the rules read. |
| `rates:read` | Your own rate calendar, and the pricing-mode read. |
| `rates:write` | Supplying nightly rates, and taking or releasing the pricing mode. |
| `bookings:read` | `GET /v1/bookings` and one booking by reference. |
| `bookings:manage` | Confirming or rejecting a pending request-to-book. |
| `reviews:read` | A listing's reviews, in their public form. |
| `reviews:reply` | Replying to a review, as the workspace. |
| `webhooks:manage` | Registering, reading, rotating and deleting your endpoint; the delivery log; a redrive. |
| `finance:read` | The six [finance](/guides/finance) reads: your balance, the earning lines behind it, a bucketed summary, your payout history and what is upcoming, and what you owe. Read-only. |

### The two that authorize a route directly

| Scope | What it reaches |
| :- | :- |
| `partner:token.issue` | The [token exchange](/guides/authentication), and nothing else. Every key holds it; on its own it reaches no operation. |
| `partner:mcp.read` | The documentation MCP server at `POST /v1/mcp`, read with the **key** rather than a token — [Connect your AI assistant](/guides/connect-your-ai-assistant). It reads the published specification and guides off our own disk. No workspace, no booking, no calendar, no guest. |

`partner:mcp.read` is separate from `partner:token.issue` on purpose. Reading our
documentation and pushing properties into a workspace are different jobs, and the credential
for the first has to survive a **static config file** — `claude mcp add --header` writes a
header once and never refreshes it, so a fifteen-minute token there breaks within the hour.
Ask for a docs key rather than pointing an assistant at your integration's key; if you point
it at the wrong one you get a `403`, which is the system working.

## Ask for the least, by job

Pick the row that describes what you are building. Every one of these also carries
`partner:token.issue`.

| The job | Ask for |
| :- | :- |
| Push a catalogue and hold nights (the quickstart) | `catalog:write`, `calendar:write` |
| The same, plus reading it back for reconciliation | add `catalog:read`, `calendar:read` |
| Receive booking notifications | add `webhooks:manage` |
| Price a listing yourself (the rate calendar) | add `rates:write`, `rates:read` |
| Set a listing's own nightly rate and cleaning fee | add `pricing:write` |
| Run the quick actions and read a listing's rules | add `rules:write` — and `pricing:write` for the `pricing.*` half |
| Upload photo bytes rather than pushing URLs | add `media:write` |
| Read bookings and answer requests-to-book | add `bookings:read`, `bookings:manage` |
| Read reviews, and reply as the workspace | add `reviews:read`, `reviews:reply` |
| Track earnings and payouts | add `finance:read` |
| Point an AI assistant at the documentation | a **separate** key whose only contract scope is `partner:mcp.read` |

A scope you do not ask for is one you cannot be tricked into using, and it is free to ask
for a second key later. The reverse is not free: widening is a re-issue.

## The issuance ceiling

Nobody grants authority they do not hold. When a **workspace** issues its own key, each
scope is checked against the issuing member's own permissions on that workspace, before
anything is minted — the Issue key form disables a scope the member cannot grant and says
why.

| Scope | The permission the issuer must hold |
| :- | :- |
| `catalog:read` | View Property |
| `catalog:write` | Edit Property **and** Create Property **and** Publish Property |
| `media:write` | Edit Property |
| `calendar:read` | View Calendar |
| `calendar:write` | Manage Calendar |
| `pricing:write`, `rates:write` | Manage Pricing Rules |
| `rates:read` | View Pricing Rules |
| `rules:write` | Manage Booking Rules **and** Manage Pricing Rules |
| `bookings:read` | View Bookings |
| `bookings:manage` | Approve Booking **and** Reject Booking **and** Cancel Booking |
| `reviews:read` | View Bookings — a review is written against a stay |
| `reviews:reply` | Edit Property — a reply publishes prose under the workspace's name |
| `webhooks:manage` | Manage Developer Access — the same gate the console's own Webhooks page uses |
| `finance:read` | View Payouts **and** View Transactions |
| `partner:token.issue`, `partner:mcp.read` | None. Neither reaches anything about the workspace. |

`catalog:write` needs all three because it is **one** scope over creating, replacing,
editing and publishing — a member who could mint it holding only Edit Property would be
handing a machine two capabilities they do not have themselves.

## The four operations whose requirement is not their scope

`x-maat-scope` is the **floor**. On four operations there is a second requirement, and it is
the only place in this API where what a call needs depends on the path rather than on the
operation.

| Operation | `x-maat-scope` | Also needs |
| :- | :- | :- |
| `PUT /v1/listings/{listing_id}/quick-actions/{template_key}` | `rules:write` | `pricing:write` when `template_key` begins `pricing.` |
| `DELETE /v1/listings/{listing_id}/quick-actions/{template_key}` | `rules:write` | `pricing:write` when `template_key` begins `pricing.` |
| `GET /v1/listings/{listing_id}/quick-actions` | `rules:write` | `pricing:write` to see `applied` on the `pricing.*` rows |
| `GET /v1/listings/{listing_id}/rules` | `rules:write` | `pricing:write` to see the pricing rules at all |

The reason the two writes split is that they are not the same act:
`pricing.cleaning_fee` changes what a guest pays and `booking.min_stay` changes who may
book. A key issued to manage availability should not thereby be able to move money.

**On the two reads nothing errors.** The quick-action list still returns all eight catalogue
entries with `rules:write` alone — the identifiers, kinds, bounds and defaults are the same
for every listing on Maat, so there is nothing there to withhold. What is withheld is
`applied` on the `pricing.*` rows, which come back `applied: null`, `available: false`,
`unavailable_reason: "insufficient_scope"`. On the rules read the pricing rows are withheld
**entirely** and `warnings` carries `pricing_withheld`: a rule is a host-written title and
whether it is live, which between them say what a business charges and when.

### Why two `GET`s require a write scope

There is no read-only scope for rules, and that is a decision rather than an oversight.
Seeing which rules price a listing is part of managing them, and a published scope is
permanent — a `rules:read` added now would be in the vocabulary forever, splitting one
capability into two for every key ever issued. If your integration only wants to read them,
ask for `rules:write` and do not call the writes.

## When you are refused

`403 insufficient_scope` names the missing scope in `detail`. Nothing was written, and
retrying unchanged can never succeed — scopes are fixed at issue, so the fix is a new key.

One refusal near it that is **not** a scope problem:

* **`403 workspace_scope_mismatch`** — a sandbox token against a production workspace, or
  the reverse. Check which key you exchanged.

Both are `403`, so **branch on `code`**, never on the status line.

One `403` is shaped differently, and it is the one you are most likely to meet first. `POST
/v1/mcp` is authenticated by the **key** rather than by a token, so a key without
`partner:mcp.read` is refused by the gateway before the contract's problem layer runs: the
body is `{ "message": "Insufficient scope" }` — no `code`, no `type`, no `instance`. Handle
that one on the status and the path; everything else on this API answers with a problem
document.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.