Rate Limit Handling in Multi-Tenant Integration Pipelines
Isolate tenants with architectural layers, not retry logic alone.
A tenant running a bulk data export can eat the API headroom another tenant needs for a real-time dashboard, and the dashboard tenant is the one who calls support. That's the situation where one tenant running a bulk data export can eat the API headroom another tenant needs, and it's the reason rate limiting in multi-tenant pipelines has to be decided through architecture, not code. One retry loop can't fix fairness, isolation, and tier enforcement at the same time. Each of those needs its own layer, and this piece walks through them in the order you'd actually build them.
A team wired up one MCP server with a single OAuth credential, then let Claude run 50 concurrent conversations through it. A team wired up one MCP server with a single OAuth credential, then let Claude run 50 concurrent conversations through it. Claude fired off thousands of tool calls. The underlying SaaS API throttled the first session in under 20 seconds. The other 49 sessions inherited that same 429 cascade, because they were all sharing one identity as far as the rate limiter could tell. The demo died on stage.
That's not a fluke; it's the default outcome of skipping tenant-aware architecture. And the pressure is only going up: a single Claude conversation now averages 8 to 15 tool calls, versus 2 to 3 back in 2024 when MCP first shipped. Notion, Salesforce, and HubSpot have all tightened their limits in 2025 and 2026, specifically to slow down agent traffic. Stack on top of that the fact that the typical midsize company runs a large number of SaaS apps, each with its own pagination quirks, auth model, and rate-limit rules, and you've got a problem that compounds instead of averaging out.
Choosing the right limiting algorithm for the right traffic shape
Four algorithms appear repeatedly in rate limiter design, and each one shapes traffic differently.
Token bucket fills at a steady rate and lets requests burst up to whatever capacity is sitting in the bucket. This matches how most real APIs actually behave: they refill on a fixed schedule and tolerate short bursts above the average. Leaky bucket does the opposite: it smooths output to a constant rate no matter how requests arrive, which protects whatever's downstream but punishes anything bursty. Sliding window checks usage against a rolling time interval, so it closes the loophole that fixed windows leave open. Fixed window is simple to build, but a client can send close to double the stated limit right at the boundary between two windows, and automated traffic finds that gap fast.
Token bucket is the production default for good reason: it runs in constant time, handles bursts naturally, and plays well with atomic counters in a store like Redis. Sliding window earns its extra complexity by closing the boundary-exploitation loophole that fixed windows leave open, which matters most when traffic patterns are irregular and precision counts.
For most multi-tenant pipelines, the choice comes down to traffic shape: token bucket where bursts are expected and tolerated, sliding window where boundary exploitation is a real, documented risk.
None of this answers where the bucket state actually lives, how you know which tenant a request belongs to, or how a free-tier bucket differs from an enterprise one. That's the next three sections.
Resolving tenant identity before any limit can be enforced
Two tenants sharing a cloud-region egress address, or two CI pipelines sitting behind the same corporate proxy, look identical to a rate limiter keyed on IP address. Their buckets get merged. Tenant A burns through Tenant B's allowance without either one knowing it happened.
IP address fails as a tenant key because NAT, mobile carriers, IPv6 address churn, and corporate proxies all break the assumption that one IP equals one client. That assumption was shaky a decade ago and it's false now.
The right key is whichever identifier you can actually trust and that correlates with real usage: API key, OAuth client ID, tenant ID, org ID, or a composite key like tenant-plus-endpoint for finer control. Setting the rate limiter's key generator to something like request.tenantId partitions a separate bucket per authenticated tenant. That's a structural decision you make at the architecture level, not a setting you flip in a config file.
Tenant-level limits alone aren't enough, either. A single large tenant running noisy internal automation can chew through its entire allocation and starve its own users, so a per-user or per-token sub-limit inside the tenant boundary matters just as much as the boundary itself.
This resolution logic belongs at the gateway or reverse proxy layer, not buried in application code. Pushing it into the app layer adds latency and creates blind spots wherever the application doesn't see the full traffic picture. Get tenant identity resolved correctly here, and the distributed counter store downstream can actually enforce limits per tenant, provided that state gets shared correctly across every node.
Distributed state in Redis: where the enforcement math lives
Local enforcement breaks down the moment you scale past one node. Each gateway instance only sees its own slice of traffic, so a tenant spreading requests across multiple nodes can blow past its limit without any single node ever noticing.
The common fix: a Lua script running inside Redis that atomically reads the bucket's current state, works out how many tokens have refilled since the last check, and grants or denies the request in that same atomic step. That atomicity matters, because without it, two nodes can simultaneously approve requests that together exceed the limit, and neither one is wrong on its own.
Sharding the keyspace by something like hash(tenantId, endpointId, ruleId, dimensionKey) spreads load evenly across the Redis cluster. Hot tenants or hot endpoints that still create imbalance can get split further with key salting or dynamic re-sharding.
Token leasing helps at high volume: instead of checking out one token per request, the gateway grabs a batch of tokens from the shard at once, cutting down on round trips. But bringing Redis into the enforcement path introduces a new failure mode: the rate-limit store becomes a dependency in its own right. If Redis goes down, the enforcement layer can go down with it, unless you plan for that. Teams usually fail open with a conservative fallback limit rather than let the protective layer cause the very outage it was built to prevent.
Teams earlier in this journey don't need the full distributed setup on day one. Local enforcement paired with load-balancer stickiness gets you "good enough" fairness when the tenant population is small. Bring in distributed limiting once you have specific traffic classes where bypassing the limit does real damage.
System design guidance on multi-tenant rate limiting consistently points toward atomic Lua operations and careful sharding as the path to low enforcement latency and high availability at scale.
Tiered quota design: translating SLA differences into enforced limits
A flat rate limit treats every tenant like a peer. In practice, that means one aggressive free-tier account can eat headroom that an enterprise SLA explicitly promised to someone else.
Fixing this takes three layers working together. Soft limits on daily budgets let tenants with legitimately bursty workloads self-correct, catching overconsumption without slamming the door on a customer having a busy Tuesday. Hard limits on monthly or rolling budgets guard against runaway cost and infrastructure exhaustion when soft signals get ignored. And configurable per-tenant thresholds let premium plans get genuinely higher limits, not just higher spending caps. The architecture has to express per-tenant configuration without a redeploy every time sales closes a new contract.
Pipedrive's rollout is a solid real-world example of the model. Its token-based system gives each company account a daily API budget calculated as 30,000 base tokens times a subscription plan multiplier times seat count. Lightweight endpoints cost fewer tokens, data-heavy ones cost more, and burst limits kick in on a 2-second window. That rollout is documented as complete and applied to every Pipedrive account.
For anyone building a pipeline against APIs like this, the practical takeaway is that tier lookup has to be a live, fast query against a per-tenant config store. It has to be quick enough to sit off the hot path, because every enforcement decision depends on it.
Once tiers are wired in correctly, there's still the question of what happens the moment a tenant legitimately runs out of quota. That's where queuing and graceful degradation take over.
Priority queuing and weighted fair queuing when capacity runs short
A hard 429 kills the request. Priority queuing holds a lower-priority request until capacity opens back up, which turns a hard failure into a delay instead. That difference, delay versus death, is most of what "graceful degradation" actually means in practice.
Two traffic classes should never share the same queue. Interactive user requests are latency-sensitive and directly visible to a human waiting on a screen, so they need to jump the line. Background batch jobs and bulk exports can tolerate delay, and they should wait their turn behind interactive traffic rather than compete with it for the same slot.
Weighted Fair Queuing (WFQ) handles the cross-tenant version of this problem. When request volume spikes, WFQ stops standard-tier tenants from blocking premium-tier ones, while per-tenant limits throttle new requests from whichever tenant is being most aggressive. Fast-failing the traffic that gets shed keeps latency predictable for the flows that actually matter.
None of this works as a black box, though. The system needs to report exactly which tenant got shed and why, or there's no way to audit whether tier enforcement is actually being honored.
Agentic pipelines add a wrinkle here. When an agent spawns sub-agents that fan out their own tool calls, work queues can grow unbounded and burn through token budgets fast, well before anyone notices the cost. Bounded queues, hierarchical budgets, and adaptive concurrency limits catch that expansion before it turns into a bill nobody expected.
The broader trend through 2025 and 2026 has been pushing this queuing and priority logic down into infrastructure, service meshes and API gateways, rather than scattering it across application code.
Client-side 429 handling: exponential backoff, jitter, and shared backoff state
Not every 429 means the same thing. As of May 2026, Anthropic's API enforces three separate limits per model class: requests per minute, input tokens per minute, and output tokens per minute. Hitting any single one triggers a 429. A separate 529 overloaded_error fires when the provider itself is out of capacity, regardless of what tier the caller is on. Both errors look identical from the client's side, but they call for different responses.
The naive fix, retrying on a fixed schedule, makes things worse. Every throttled client retries at the same interval, so they all collide again on the retry, recreating the contention spike that caused the 429.
The standard fix is exponential backoff with Full Jitter, which randomizes the sleep interval to spread retries across time. AWS testing with 100 contending clients found that Full Jitter cut total call volume by more than half compared to plain exponential backoff with no randomness, because spreading retries randomly breaks the lockstep pattern.
Jitter alone still misses something, though. Each worker only knows about its own 429s. Worker A backs off, while Workers B through Z keep hammering the API until they each get their own 429 and start their own independent timer. The whole pool ends up flailing until it randomly settles into something stable, which can take a while.
Shared backoff state closes that gap. The moment any single worker hits a 429, it writes a pause flag to a shared store, Redis or some other coordination service, and the entire pool backs off together, capped at roughly one budget-refill window. One worker's bad luck becomes the whole pool's coordinated response instead of 49 separate lessons learned the hard way. Libraries like tenacity in Python and Polly in.NET implement these patterns in production today.
Exponential backoff reduces retry volume, it doesn't grant permission to retry forever. Treating backoff as a license for unlimited retries turns a temporary limit into a self-inflicted outage.
Circuit breakers and graceful degradation when backoff is not enough
Backoff assumes the throttling is temporary. When it isn't, when a service is sustained-throttling rather than just spiking, every retry is wasted effort, and continuing to retry just deepens the contention and queues up requests that were never going to succeed anyway.
That's the job a circuit breaker does: it stops calling a service that's consistently failing, rather than continuing to queue work against it. That's a fundamentally different response than backoff, and the two aren't interchangeable.
Four patterns work together to stop a single failing integration from taking down everything around it. Exponential backoff avoids hammering a struggling service. Circuit breakers stop calling a service once it's clearly not going to respond. Bulkheads cap concurrency so one slow integration can't consume all the connection or thread resources that other integrations need too. And timeouts make sure nothing waits forever, since a hung request holding a connection open causes damage just as real as a flood of 429s.
Once a breaker trips, degrading gracefully has a few real options: serve a stale cached response, drop non-essential fields to lighten the response, fail over to a secondary provider, or simply tell the user clearly that something failed instead of leaving a request hanging silently.
Normalizing error responses across providers makes all of this easier to build once instead of per-integration. Truto, for example, uses JSONata expressions to translate proprietary rate-limit headers and status codes from different third-party APIs into standard HTTP 429 responses with proper Retry-After headers. Retry logic doesn't need a custom branch for every provider it touches.
A study known as MAST analyzed more than 1,600 execution traces across seven open-source agent frameworks and found failure rates ranging from 41% to 87%. A study known as MAST analyzed more than 1,600 execution traces across seven open-source agent frameworks and found failure rates ranging from 41% to 87%. Unstructured multi-agent setups amplified errors compared to single-agent baselines. Circuit breakers aren't a nice-to-have in agentic pipelines, they're load-bearing.
Observability: making the enforcement layer auditable, not opaque
None of the architecture above means anything if nobody can see it working. Three layers need instrumentation, and they need it separately, not bundled into one dashboard that hides where the actual problem lives.
Proxy and gateway stats, from Envoy, Kong, or Nginx, cover request counts, 429 rates, and latency during throttling events. Rate-limit service counters inside Redis track per-tenant bucket state, token consumption rates, and any drift in the counters themselves. Redis health metrics, replication lag, eviction rates, connection saturation, matter because a silent failure in the enforcement store is a production incident waiting to be discovered the hard way.
The percentage of requests getting rate-limited per endpoint, retry success rates, latency percentiles specifically during throttling, and every circuit-breaker state transition are worth putting on an alert, not just a dashboard. Without that visibility, the enforcement layer turns into a black box that either works by accident or fails by surprise, and there is no way to tell which one is happening until a tenant calls support to ask why their dashboard went dark.
Sources
- System Design - Multi-Tenant API Rate Limiting Service | by Khalil Sayed | Medium
- How to Integrate with the Pipedrive API (2026 Engineering Guide) | Truto Blog
- API Rate Limiting at Scale: Patterns, Failures, and Control Strategies
- The Per-Tenant Rate Limit That Wasn't Per-Tenant
- truto.one
- Rate-limiting algorithms compared: token bucket, leaky bucket, sliding window, and fixed window
- redis.io
- en.wikipedia.org



