From axios-retry
Who this is for: apps that call axiosRetry(instance, options) and want backoff with jitter, Retry-After, timeouts per attempt and a circuit breaker per host, without touching their call sites.
attachResilience(instance, options) from resilience-pipeline/axios takes the same place as axiosRetry(instance, options): one call per axios instance, interceptors and call sites unchanged, per-request overrides on the request config. The difference is what you get without options: axios-retry retries 3 times with no delay; attachResilience adds a total timeout, a timeout per attempt, exponential backoff with jitter, and a circuit breaker per host. See the axios integration for the full reference.
Option mapping
Section titled “Option mapping”| axios-retry 4 | attachResilience |
|---|---|
axiosRetry(instance, options) |
attachResilience(instance, options), returns { eject() } |
retries (default 3) |
retry: { maxRetryAttempts } (default 3). retry: false disables retries |
retryCondition (default isNetworkOrIdempotentRequestError) |
retryOn: status codes, or ({ response, error, attemptNumber }) => boolean |
retryDelay (default: no delay, or Retry-After when present) |
default: exponential from 200 ms with full jitter, Retry-After honoured |
retryDelay: axiosRetry.exponentialDelay |
the default; tune with retry: { delay, maxDelay, useJitter } |
retryDelay: axiosRetry.linearDelay() |
retry: { backoffType: 'linear', delay } |
retryDelay: (retryCount, error) => ms |
retry: { delayGenerator: ({ outcome, attemptNumber }) => ms } |
shouldResetTimeout: false (axios timeout covers all retries) |
totalTimeout (default 30 s) |
shouldResetTimeout: true |
attemptTimeout (default 10 s) |
onRetry(retryCount, error, config) |
retry: { onRetry: (args) => … }; to change the request, onBeforeAttempt(config, context) |
onMaxRetryTimesExceeded(error, retryCount) |
retry: { onRetryExhausted: (args) => … } |
validateResponse |
retryOn with a predicate over response |
per request: { 'axios-retry': { retries: 0 } } |
per request: { resilience: { retries: 0 } } |
isNetworkError, isRetryableError, … helpers |
built into the default retryOn (see below) |
| not available | circuitBreaker (per host), concurrency, retryUnsafeMethods, timeProvider |
Side by side
Section titled “Side by side”Defaults
Section titled “Defaults”import axios from 'axios';import axiosRetry from 'axios-retry';
const api = axios.create({ baseURL: 'https://api.example.com', timeout: 10_000 });axiosRetry(api, { retries: 3, retryDelay: axiosRetry.exponentialDelay });import axios from 'axios';import { attachResilience } from 'resilience-pipeline/axios';
const api = axios.create({ baseURL: 'https://api.example.com' });attachResilience(api, { name: 'api', totalTimeout: '10s' });Leave axios’ own timeout at 0 and let the pipeline time out: an axios timeout fails the attempt with ECONNABORTED, which is then retried.
Custom condition, delay and logging
Section titled “Custom condition, delay and logging”axiosRetry(api, { retries: 5, retryCondition: (error) => axiosRetry.isNetworkOrIdempotentRequestError(error) || error.response?.status === 429, retryDelay: (retryCount) => retryCount * 1000, onRetry: (retryCount, error) => log.warn(`retry ${retryCount}: ${error.message}`), onMaxRetryTimesExceeded: (error) => log.error(`gave up: ${error.message}`),});import { attachResilience } from 'resilience-pipeline/axios';
attachResilience(api, { name: 'api', retryOn: [429, 500, 502, 503, 504], // network errors and timeouts are still retried retry: { maxRetryAttempts: 5, backoffType: 'linear', delay: '1s', onRetry: ({ attemptNumber }) => log.warn(`retry ${attemptNumber + 1}`), onRetryExhausted: () => log.error('gave up'), },});A status list in retryOn replaces the default statuses; network errors and timeouts stay retryable. A function in retryOn decides alone, for errors as well as responses.
Per-request overrides
Section titled “Per-request overrides”await api.get('/report', { 'axios-retry': { retries: 0 } });await api.get('/report', { resilience: { retries: 0, timeout: '2m' } });await api.post('/orders', order, { resilience: { idempotent: true } });Importing resilience-pipeline/axios adds the typed resilience field to AxiosRequestConfig. Fields: retries, retryOn, timeout, attemptTimeout, idempotent.
Refreshing a token between attempts
Section titled “Refreshing a token between attempts”axiosRetry(api, { retryCondition: (error) => error.response?.status === 401, onRetry: async (_count, _error, config) => { config.headers.Authorization = `Bearer ${await tokens.refresh()}`; },});import { AxiosHeaders } from 'axios';import { attachResilience } from 'resilience-pipeline/axios';
attachResilience(api, { retryOn: [401, 429, 503], onBeforeAttempt: async (config, { attemptNumber }) => { if (attemptNumber === 0) return; const headers = new AxiosHeaders(config.headers as AxiosHeaders); return { ...config, headers: headers.set('Authorization', `Bearer ${await tokens.refresh()}`) }; },});onBeforeAttempt receives a copy of the config for each attempt and may return a replacement. It is needed because retries happen inside the adapter: request interceptors run once per request, not once per attempt (axios-retry re-sends the request through the instance, so its retries do run the interceptors again).
Behavioural differences to watch
Section titled “Behavioural differences to watch”- Timeouts are on by default. 30 s for the whole call and 10 s per attempt. A slow endpoint that used to work may now fail with
TimeoutRejectedError; raiseattemptTimeout/totalTimeout, or set them per request.totalTimeout: Infinitydisables the total budget. - What is retried. axios-retry’s default retries network errors, and 429 or 5xx responses on
GET HEAD OPTIONS PUT DELETE; it doesn’t retry axios’ own timeout (ECONNABORTED). Ours retries network errors (a fixed list of codes such asECONNRESET,ECONNABORTED,ETIMEDOUT,ERR_NETWORK), attempt timeouts, and408 429 500 502 503 504, onGET HEAD OPTIONS TRACE PUT DELETE. Other 5xx statuses (501, 505…) are not retried. - POST with an
Idempotency-Keyheader is retried automatically.retryUnsafeMethods: trueretries every method;resilience: { idempotent: false }forbids retries for one request. - Delays. axios-retry waits 0 ms by default (or the
Retry-Aftervalue);exponentialDelaystarts at 200 ms plus up to 20% added randomness. Ours starts at 200 ms with full jitter (anywhere between 0 and the computed delay), and usesRetry-Afterwhen the response has one. - Custom
delayGeneratorreplaces theRetry-Afterhandling. If you setretry.delayGenerator, read the header yourself orRetry-Afteris ignored. - Callback arguments. axios-retry passes
retryCountfrom 1, the error and the config.onRetryreceives{ outcome, context, attemptNumber, retryDelay, duration }, withattemptNumberfrom 0. For a non-2xx status,outcome.erroris theAxiosErrorandoutcome.error.responsethe response. - Errors reaching the caller. After the last retry, the caller gets the original
AxiosError, as with axios-retry. Rejections by the pipeline (TimeoutRejectedError,BrokenCircuitError,BulkheadRejectedError) are notAxiosErrors. - Removal. axios-retry registers interceptors;
attachResiliencereplaces the instance’s adapter.eject()restores the previous adapter. A per-requestadapterbypasses the pipeline. - Bundled browser code may need
getAdapter: axios.getAdapter(see Bundled browser code).
What you gain
Section titled “What you gain”- Timeouts per attempt and in total, a circuit breaker and an optional concurrency limit per host,
Retry-Aftersupport. - Telemetry for every retry, timeout and breaker transition, exportable to OpenTelemetry or Prometheus.
- The same engine as
createResilientFetch(), so fetch and axios clients behave the same.
Related: axios integration, fetch, Retry, Circuit breaker.