Delegate backoff
Surface rate-limit outcomes as a RateLimitError so an outer gate owns the backoff, instead of retrying and throttling them internally.
By default a stitch handles a rate limit itself: a 429 listed in
retry.on is retried with backoff, the
Retry-After header is consumed to pace that wait, and the built-in
throttle spaces calls before they leave.
That's the right behavior when the stitch is the only thing talking to the API.
It's the wrong behavior when something outside StitchAPI already owns the
rate budget — an outer gate or circuit with its own Retry-After hook and a
persisted backoff window. There, internal retry hides the rate-limit signal from
the gate, and the internal throttle double-counts against it. Turn on
delegate-backoff mode and the stitch surfaces the rate-limit outcome instead
of absorbing it, so the outer gate decides what to do.
Example
import { , } from 'stitchapi';
// Your own rate-gate / circuit, living outside StitchAPI.
declare const : { (: number): void };
const = ({
: 'https://api.example.com',
: '/pages/{id}',
: { : true }, // surface 429s; don't retry or throttle them
});
try {
await ({ : { : '42' } });
} catch () {
if ( instanceof ) {
// Hand the signal to your outer gate — it owns the backoff.
.(. ?? 1000);
}
}A 429 no longer retries: the call rejects with a
RateLimitError carrying status, the
retryAfterMs parsed from Retry-After (delta-seconds or an HTTP-date), and
the raw response so you can read any other rate headers the API sent. The
internal throttle is bypassed for the call, so it adds no pacing of its own.
Options
delegate switches the mode on; it defaults to false, so a stitch behaves
exactly as before until you opt in.
on is the set of statuses treated as a rate-limit signal — it defaults to
[429]. Widen it when an API signals back-pressure with another status (for
example { delegate: true, on: [429, 503] }); a status not in on is left to
the ordinary retry path.
Reading it off the stream
If you iterate the event stream instead of
awaiting, the same outcome arrives as an error event carrying status and
retryAfterMs, so a streaming consumer gets the identical structured hint:
import { } from 'stitchapi';
// Your own rate-gate / circuit, living outside StitchAPI.
declare const : { (: number): void };
const = ({
: 'https://api.example.com',
: '/pages/{id}',
: { : true },
});
for await (const of .({ : { : '42' } })) {
if (. === 'error' && . !== ) {
.(.);
}
}What still runs
Delegate mode changes only how a rate-limit status is handled. Everything else
on a stitch is untouched: input validation, templating, transform, unwrap,
and drift all still run on a successful response,
and non-rate-limit failures (a 500, a transport error) behave exactly as they
do without the flag. A circuit
block, if you also set one, still applies — you can let the host layer both.
Delegate mode and internal retry-on-rate-limit are mutually exclusive for
the delegated statuses: a status in rateLimit.on is surfaced, never
retried, even if it also appears in retry.on.
See Reference → Config types for every field.