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-Remainingas 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
429withcode: "photo_quota_exceeded"— a different code fromrate_limited, because the answer is different. There is noRetry-Afterand no short wait that helps: resume after midnight UTC. - The presign draws on the hourly quota as well, like every other call, so a
429from it is one of two codes.rate_limitedcarriesRetry-Afterand clears within the hour;photo_quota_exceededcarries none and clears at midnight UTC. Branch oncode, 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 a429, and reserves nothing.
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
429withcode: "redrive_budget_exhausted"and aRetry-Afterheader. That code is how you tell the two ceilings apart: the hourly gateway limit israte_limitedand can fire on any call, this one isredrive_budget_exhaustedand can only fire here.Retry-Afteris 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.