Skip to main content
The contract endpoints share one quota: 600 requests an hour per API key by default, across all of them together. It is sized for a nightly catalogue sync plus a live calendar feed for a mid-size portfolio. The ceiling is a property of your key, and it can be raised for yours alone. 600 is the default a key is issued with; your Maat contact can set a different figure on an individual key, and it takes effect on your very next request rather than at some scheduled time. Ask for one when your integration’s shape genuinely needs it — and read RateLimit-Limit rather than assuming 600, because it always reports the figure that applies to the key you are calling with. Every response announces where you stand: Going over answers 429 with code: "rate_limited" and a Retry-After header giving the wait in seconds. Honour it — the window does not reset faster because you keep asking. All four, plus Retry-After and X-Correlation-Id, are exposed to browser JavaScript, so a page calling this API can read them and not only a server can. One honest exception, and it is narrower than it sounds: a refusal that lands before your key has been identified carries no RateLimit-*, because there is no bucket to report yet. On the gateway that is the 401 and the 403 for a token used against the wrong kind of workspace. A 403 for a missing scope is counted first and does carry them, and so does the token exchange’s own 401 — its per-IP ceiling runs ahead of the credential check.

Budgeting a sync

Three properties of the API keep a well-shaped integration comfortably inside the quota:
  • Unchanged pushes are cheap for you to skip. Both inbound contracts are idempotent PUTs keyed by your own identifier, so you can safely push only what changed since your last sync — and re-push everything after any doubt.
  • Unchanged photo URLs cost nothing, so a full catalogue push does not re-process images — see Properties.
  • Read RateLimit-Remaining as you go rather than counting requests yourself, and smooth large syncs over the hour rather than bursting them.

A second, separate ceiling: photos per day

POST /v1/properties/{property_id}/photos/presign draws on the hourly quota like every other call and on a daily allowance of 200 photos per API key, reset at midnight UTC. That figure is today’s default rather than contract: the changelog will say so if it moves, and quota.remaining_today is read from the server so nothing has to hard-code it. It exists because a photo is not a request. Every photo that lands is processed and put through an automated review on our side, so the cost we are bounding is per image and recurs for as long as an import runs — an hourly bucket would either refuse a legitimate first catalogue import or be far too loose to bound the day.
  • Every successful presign returns quota.remaining_today. Pace against it rather than discovering the ceiling as an error.
  • Crossing it answers 429 with code: "photo_quota_exceeded" — a different code from rate_limited, because the answer is different. There is no Retry-After and no short wait that helps: resume after midnight UTC.
  • The presign draws on the hourly quota as well, like every other call, so a 429 from it is one of two codes. rate_limited carries Retry-After and clears within the hour; photo_quota_exceeded carries none and clears at midnight UTC. Branch on code, never on the status alone.
  • Nothing is reserved and no upload is authorized when it fires, and a batch that would cross the ceiling is refused whole rather than partly filled. You are charged only for photos you were actually given forms for.
  • A property also holds at most thirty photos, counting the ones still uploading. A batch that would take it past thirty is a 400, not a 429, and reserves nothing.
Photo URLs pushed on PUT /v1/properties/{provider_id} do not draw on this allowance — they are fetched on our own schedule, which is bounded separately.

A third ceiling: redriving a webhook delivery

POST /v1/webhooks/deliveries/{delivery_id}/redrive draws on the hourly quota like every other call and on its own allowance: a redrive budget per credential, over a rolling 24 hours — it does not reset at midnight or any other fixed time, it simply frees up as the trailing window moves past an earlier redrive.
  • Using it up answers 429 with code: "redrive_budget_exhausted" and a Retry-After header. That code is how you tell the two ceilings apart: the hourly gateway limit is rate_limited and can fire on any call, this one is redrive_budget_exhausted and can only fire here. Retry-After is the wait until the oldest replay falls out of the trailing window.
  • We do not publish the figure here: it is an operational setting we tune, not a contract.
  • A refused redrive costs nothing. It does not spend the allowance, and nothing about the delivery changes — you are exactly as free to redrive it once the window allows as you were before you asked.
  • A redrive is never bulk — one call replays exactly one delivery — so this ceiling bounds how much of an endpoint’s backlog you can churn through, not how many deliveries exist.

The token exchange is counted separately

POST /v1/token does not draw on the 600. It has two ceilings of its own, both today’s defaults rather than contract: 60 exchanges an hour from one IP address, and 1,000 an hour per API key. Mint one token per batch of work, not one per request: a token lives fifteen minutes by default, which is an entire sync’s worth of calls under one exchange. An integration that exchanges a key per request will meet the exchange’s ceiling long before the contract quota. The MCP documentation server draws on the same per-key hourly ceiling as the exchange and on nothing else — it reads published documentation and touches no workspace, so it is not counted against the contract quota. See Connect your AI assistant.