Browse documentation
Capacity and quotas
Rotuli can refuse a request for two unrelated reasons: you have used your monthly quota, or the shared Companies House budget is momentarily empty. This page explains why both limits exist, how they interact, and what that means for your retry logic.
Two limits with different owners
Your monthly quota belongs to you. It is the number of requests your tier includes, counted per account across a quota window. Nobody else's traffic affects it, and the only ways to get more are to wait for the window to reset or to upgrade.
The Companies House budget belongs to everyone. Most Rotuli data comes from Companies House, and Companies House limits how fast any single integrator can call it. Every customer's uncached lookups draw on that one allowance. It refills within seconds, but at a busy moment it can be empty even when your own quota is almost untouched.
Because the two limits have different owners, they fail with different codes, carry different Retry-After values, and are charged differently.
| Monthly quota | Companies House budget | |
|---|---|---|
| Whose it is | Yours alone | Shared by every Rotuli customer |
| Size | 500 to 50,000 requests, by tier | Just under 600 Companies House requests per rolling 5 minutes |
| Refills | Once, at the end of your quota window | Continuously, every second |
| What spends it | Every request you make, cached or not | Only lookups that have to go to Companies House |
| When it runs out | 429 rate_limit_exceeded | 429 upstream_capacity, 503 upstream_unavailable or a partial response |
| Typical wait | Until your window resets, or upgrade | Seconds |
The shared Companies House budget
Companies House allows each integrator 600 requests per rolling 5 minutes. Rotuli calls Companies House with one platform key, so that limit is a ceiling on all customers' uncached lookups combined, not a per-customer allowance.
A cache hit never reaches Companies House, so it does not spend the budget. Your quota is a different matter: a cached response costs you one request like any other. The reverse also holds. Passing fresh=true to the entity endpoint discards the cached copy, so that request always spends the shared budget.
To stay under the Companies House limit, Rotuli runs its own token bucket in front of every outbound call. The bucket is tuned a little below 600 per 5 minutes, so Rotuli runs out first and Companies House never has to refuse it. When the bucket is empty, Rotuli sheds the request: it answers immediately rather than holding the request in a queue. Queueing would turn a short burst into long, unpredictable latency for everyone, and a fast refusal with a Retry-After lets you decide what to do.
Shedding is graded by tier. As the bucket drains, Free requests are refused first, then Growth, so the last of the capacity goes to Enterprise. Under sustained pressure, a Free key sees upstream_capacity sooner and more often than an Enterprise key making the same call. In quiet periods the grading has no visible effect.
Why some sheds fail and others return partial data
A company profile is built from several sources. The core Companies House record establishes whether the company exists at all. Without it, an almost empty response could read as "not found" or "nothing to worry about". So when the core lookup is shed, the whole request fails with 429 upstream_capacity. Search has only one source, so a shed search fails too, as 503 upstream_unavailable.
A secondary source, such as persons with significant control, officers or charges, is different. The company is already confirmed, and the rest of the profile is useful without it. When one of these is shed, you get a 200 partial response with the missing source listed in sources_failed. Any risk flag that depended on it is marked indeterminate instead of being quietly dropped. See Interpreting risk signals for how to read those responses.
Why refunds work the way they do
The rule follows what you received. A response that served data, even partial data, costs one request. A REST request that the shared budget shed before anything was fetched served nothing, and the cause was outside your control, so it is refunded: upstream_capacity and upstream_unavailable do not reduce your quota. Retrying them costs nothing until a retry succeeds.
Other refused requests are charged. control_graph_capacity is the one that surprises people. Its causes include your own account's activity, such as a graph already running or your admission allowance being empty, and the refund rule does not try to tell those apart from platform-wide causes. Each retry of a capacity-refused graph therefore costs one request.
Over MCP, there are no refunds at all. A tool error travels inside a successful protocol response, so every tools/call costs one request whatever it returns. The one exception is get_account_status, which is never counted.
Control graphs: ceilings, not reserved throughput
A control graph fans out from one company to its corporate controllers and related companies, and a single graph can need up to 30 Companies House calls. To keep graphs from crowding out ordinary lookups, they have their own limits: one graph at a time per account, two across the platform, an admission allowance per account, and per-graph caps on depth, nodes, outbound calls and time.
Those per-graph numbers are the most a graph may use, not an amount set aside for you. Graphs also draw on the shared Companies House budget, and they may only use part of it, so ordinary lookups always keep some headroom. On a busy platform a graph can stop well short of its caps. When that happens you still get the evidence gathered so far, as a 200 with status: partial and a frontier that says where and why traversal stopped. Only a graph that cannot start at all returns control_graph_capacity.
What this means for your retry logic
Branch on the error code, not the HTTP status. Two codes share 429 but call for opposite responses.
- rate_limit_exceeded
- Do not retry automatically.
Retry-Aftercounts down to the end of your quota window and can be days away. Stop the job, alert someone, and wait or upgrade. - upstream_capacity and upstream_unavailable
- Retry after
Retry-After, which is usually seconds. Add some random jitter so that many workers do not retry in the same instant. On REST these are refunded, so a retry is free. If they persist during a bulk job, slow the job down: the budget is shared, and a steady pace sheds less than bursts do. - control_graph_capacity
- Retry after
Retry-After, and run graphs one at a time per account. Running them in parallel only produces this error, and each refusal costs a request. - A partial
200 - This is not an error. Decide whether you need the missing source. If you do, retry later rather than immediately; the retry costs a request.
- internal_error and upstream_error
- Retry with exponential backoff and a retry limit. Each attempt costs a request.
- Any other 4xx
- Do not retry. The same request fails the same way until you change it or your tier.
Reference
- Rate limits: quotas, quota windows, headers, request costs and control-graph limits.
- Error codes: every code, where it comes from and what to do.