Retry & backoff
Retry on configurable status codes with expo/jitter/fixed backoff and respect for Retry-After.
Add retry when an API returns transient failures — rate limits and gateway
hiccups — and you want the stitch to wait and try again instead of surfacing the
first error.
Example
import { } from 'stitchapi';
const = ({
: 'https://api.example.com',
: '/users/{id}',
: {
: 3,
: [429, 503],
: { : 'expo-jitter', : 200 },
},
});Options
attempts is the total number of attempts including the first call, so
attempts: 3 means up to two retries; the default is 1, which disables
retrying.
on selects the response status codes that trigger a retry — a StatusMatch: a
single status, a list, or a predicate (status) => boolean (on: 429 ≡
on: [429]). It defaults to [429, 502, 503, 504] — the usual transient set —
and you narrow or widen it per stitch. The predicate receives the status and
nothing else, so a vendor that reports throttling in the body of a 200 is
invisible to every policy you can write here —
rate limits priced in query cost works
that case through a surface instead.
backoff chooses how long to wait between attempts. A bare curve is the whole
policy — backoff: 'expo-jitter' — and the object form adds the two bounds that
shape it, base and max:
retry: { attempts: 3, backoff: { curve: 'expo', base: 200, max: '10s' } }'expo' doubles the delay each attempt starting from base, 'expo-jitter'
spreads it randomly up to that exponential value to avoid thundering herds, and
'fixed' waits a constant base every time. Every computed delay is clamped to
max.
respect honors a Retry-After header on the failing response — delta-seconds
or an HTTP-date — using it in place of the computed backoff. It defaults to
true: the statuses on retries by default are the ones RFC 9110 defines the
header for, so when the server tells you when to come back, guessing at a curve
instead is strictly worse information. Set respect: false to force the computed
curve regardless.
There is deliberately no ceiling on an honored Retry-After. A server asking for
five minutes gets five minutes, because the place to say how long you're willing
to wait is timeout.total — one patience
budget for the whole call, not a second one hidden in retry. It already bounds
every backoff sleep, so a long Retry-After under a total budget fails with the
timeout rather than parking the call; the wait also ends early if the caller's
AbortSignal fires. A stitch with retry and no timeout.total will wait
as long as the server asks.
Each retry surfaces as a progress event (phase retry) on the
event stream, so a trace sees every wait and
re-attempt. For the exhaustive field list and defaults, see
Reference → Config types.
Anti-pattern: don't add retry to a stitch that performs a
non-idempotent write — a POST that creates an order, for instance — because
a re-attempt after a timeout or 503 can silently apply the side effect
twice; pair retries with an idempotency key so the server collapses the
duplicate instead. See Idempotency.
When the write is a batch and only some of its items failed, the whole
request is the wrong retry unit — batch writes that fail one item at a
time walks that case through.