How the limits work
Every request goes through two checks, in this order. The first one that runs out answers429.
The per-account limit and the 300 requests/min per-IP limit are token buckets. The bucket holds as many tokens as the limit, every request spends one, and one token comes back every
60 / limit seconds. On Pro (40/min), that is one token every 1.5 seconds. In practice:
- An integration that has been idle can spend the whole bucket in one burst.
- The sustained rate never exceeds the published number: 40/min stays 40/min.
- When you run out, you get capacity back in seconds, not at the next minute.
2 × limit − 1 requests can pass within a given 60-second span (the full bucket plus the tokens that come back during that span); over longer periods the sustained rate is the limit.
The 10 requests/min per-IP limit of POST /v1/calls is still a fixed one-minute window. It gets the tighter limit because it dials a real phone; reads and the rest of the writes share the wide one.
Per-account limit by plan
The account limit is shared by every API key of the account. Creating more keys does not add throughput: five keys of the same account draw from the same bucket.
If you need a sustained higher limit (a large outbound campaign, for example), write to us at team@ryvo.so and we can raise it case by case.
Rate limit headers
Every response that got past authentication carries the state of your account bucket, errors included:
Read
X-RateLimit-Remaining to slow down before you hit 429, instead of reacting after.
Response when you exceed the limit
error, which is a stable code, not on message.
The Retry-After header tells you how many seconds to wait before trying again. It is always at least 1. With a token bucket it is usually a few seconds: the time until the next token.
A 429 can also come from our edge network, before the request reaches the API. That one may not carry the JSON body or the X-RateLimit-* headers, so branch on the status code and Retry-After, not on the body.
When we cannot check your limit
If our rate limiting store is down or too slow to answer, we refuse the request with503 and the code rate_limit_unavailable, with Retry-After: 5 and no X-RateLimit-* headers. It means we could not check your limit, not that you used it up: do not slow down your integration because of it, just retry after Retry-After.
Recommended strategy: exponential backoff with jitter
Best practices
Queue on your side
Queue on your side
For outbound campaigns of hundreds or thousands of calls, put the leads in a queue and process them with a worker that stays under your limit with some headroom (8 requests/min from a single IP, for example).
One key per integration, for traceability
One key per integration, for traceability
Giving n8n and your CRM different keys lets you revoke one without touching the other and tells you who made each request. It does not split the limit: all the keys of an account share it.
Always send Idempotency-Key
Always send Idempotency-Key
Combined with backoff, it guarantees that retries after a
429 do not fire duplicate calls when the first attempt succeeded but the response was lost.