Rate Limiting
The REST API has exactly one limiter, and it is simpler than most: 100 requests per IP address per hour. That is the whole model. There are no tiers, no per-endpoint budgets and no separate bucket for heavy operations.

How the counting works
Every request to /api/v1/… records a timestamp against the caller's IP address. Before a request is handled, the timestamps from the last 60 minutes are counted, and if the count has reached the limit the request is refused.
The window slides. It is not a clock-hour that resets on the hour, so sixty requests at 14:59 do not pair with sixty more at 15:01 for a 120-request minute - the first sixty age out one by one, sixty minutes after each was made.
Three consequences follow from "per IP" that surprise people:
The counter is shared by everything behind one address. Two n8n workflows on the same server, or an office behind one public IP, draw from the same 100. Issuing a second API key does not give you a second budget, because keys are not counted at all.
The limiter runs before authentication. A request with a bad key, or no key, still consumes a slot - which is deliberate, since that is what makes the limit useful against a script hammering the endpoint.
And /api/v1/health counts too. A monitor polling it every thirty seconds spends 120 requests an hour and locks out everything else on that address. Poll it every five minutes.
Behind a CDN or reverse proxy, the client address is taken from the forwarded headers only when the connection genuinely arrives from a trusted proxy - a Cloudflare edge address, or a local address on a host that puts its own layer in front. Otherwise the raw connection address is used, so a spoofed header cannot be used to escape somebody else's counter or to poison it.
The 429
HTTP/1.1 429 Too Many Requests
Content-Type: application/json; charset=utf-8
{
"success": false,
"error": { "code": 429, "message": "Rate limit exceeded" }
}
That is the entire response. There is no Retry-After header and there are **no X-RateLimit-* headers**, on this response or on successful ones - so a client cannot read how much budget it has left and cannot be told when to come back.
The practical rule that follows: on a 429, back off on a schedule of your own. Something like 60 seconds, then 5 minutes, then give up and alert. Retrying immediately in a tight loop is worse than useless, because every attempt is itself a request that keeps the window full.
Staying inside the limit
Spend requests on pages, not on rows. GET /api/v1/posts?per_page=100 is one request for a hundred posts; fetching them one id at a time is a hundred. per_page is capped at 100, so three requests cover three hundred posts.
Cache what barely changes. Categories, tags and settings are a handful of rows that a daily sync would still get right. There is no conditional-request support to help you here - the API sends no ETag and no Last-Modified, so a repeat call costs a full request every time. Caching on your side is the only way to avoid it.
Prefer the event to the poll. If you are calling the API on a timer to notice when something changed, an outgoing webhook tells you instead, at no cost to your budget.
Raising the limit
The value is the API_RATE_LIMIT constant, defined in config/environment.php, which reads an environment variable of the same name when one is present. Set it there and the new number applies on the next request - nothing to restart, nothing to clear.
Raise it deliberately rather than reflexively. The limit exists because a runaway loop against a shared host starves the site itself, and on most installs the right answer to "we keep hitting 100 an hour" is a client that batches and caches rather than a bigger number.