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.

The Automation Access block, showing the REST endpoint address
The limit counts requests to this endpoint, per IP address.

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.

Be the first to know

New features, release notes and CMS guides. We send a couple of emails a month.