Release candidate — 1.0.0-rc.7
StitchAPI
GuidesResilience

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: 429on: [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.

See also

On this page