RateLimitError
The delegate-backoff error: a rate-limit response surfaced for an outer gate to back off on, instead of being retried internally.
What you'll see
A stitch with throttle: { delegate: true }
that gets a rate-limit response (status in throttle.on, default [429])
rejects with a RateLimitError instead of retrying. It is a StitchError
— a subclass — so it carries the usual status / attempts / body / url
and is caught by a generic instanceof StitchError arm, while remaining its own
exported class you can catch specifically to hand the signal to whatever owns
your backoff:
import { , } from 'stitchapi';
const = ({
: 'https://api.example.com',
: '/pages/{id}',
: { : true },
});
try {
await ({ : { : '42' } });
} catch () {
if ( instanceof ) {
.(.); // 429 (the rate-limit status that was hit)
.(.); // ms parsed from Retry-After, or undefined
.(..); // the raw response, for other rate headers
}
}RateLimitError carries, on top of the inherited StitchError fields
(status, attempts, body, url):
retryAfter— the wait the server asked for, parsed fromRetry-After(delta-seconds or an HTTP-date) into milliseconds;undefinedwhen the header is absent or unparseable.response— the rawAdapterResult, so you can read other rate headers the API sent (X-RateLimit-Remaining, a reset timestamp, and so on).
Its status is the rate-limit status (e.g. 429, or whatever you listed in
throttle.on) — always present, where the base class leaves it optional.
Test RateLimitError before StitchError. Because it subclasses
StitchError, a catch whose first arm is instanceof StitchError will
swallow the rate-limit signal and never reach a later RateLimitError arm —
which defeats the point of delegating. Put the specific class first, as the
example above does.
This is the one entry in the error catalog that is a real
runtime identity rather than a documentation ID: RateLimitError is an
exported class you can instanceof. The STITCH_* entries are page IDs —
those failures all arrive as a plain StitchError with no .code, told
apart by their fields.
Why it happens
You opted into delegate-backoff because an outer gate or circuit — not StitchAPI — owns the rate budget. So when the API returns a rate-limit status, the stitch deliberately does not retry it and does not pace it on the internal throttle: it surfaces the outcome so your gate can apply the backoff, persist it, and decide when to release. This is the mode working as designed, not a new failure — the rate limit reached you on purpose.
How to handle it
- Feed it to your outer gate. Use
retryAfter(falling back to your own default when it'sundefined) to set the gate's penalty window, then let the gate gate the next attempt. That's the whole point of delegating. - Read it off the stream instead. If you consume
the event stream rather than awaiting, the same
outcome arrives as an
errorevent carryingstatusandretryAfter— notry/catchneeded. - Branch without throwing.
.safe()returns the very sameRateLimitErrorinerror—SafeResult.erroris typedStitchError, and this is one, so nothing is downgraded.instanceof RateLimitError,retryAfter,bodyandresponseall work identically whether youawaitor.safe(). - Reconsider whether to delegate. If nothing outside StitchAPI actually
owns the backoff, you probably want internal handling instead — drop
throttle.delegateand use retry + throttle.
Stuck, or debugging with an agent? Ask the docs
MCP — search_docs('RateLimitError') pulls
this page and the related guides straight into context.