Skip to content
D
Documentation

Retry failed requests

how-to
3 min readUpdated

Configure retry limits, eligible methods and statuses, and the delay between attempts; add a custom decision when the default retry rules do not match your API. Use retries for requests that can safely be repeated after a transient failure.

Set retry limits and eligibility

Set retry defaults on a ky instance when several calls share the same policy, or pass retry on an individual request. The value can be a number, which sets only the retry limit, or a RetryOptions object.

  1. Create an instance with the retry policy and make a request:

    ts
    import ky from 'ky';
    
    const api = ky.extend({
    	retry: {
    		limit: 3,
    		methods: ['get'],
    		statusCodes: [429, 503],
    		afterStatusCodes: [429, 503],
    		maxRetryAfter: 30_000,
    		backoffLimit: 2_000,
    		jitter: true,
    	},
    });
    
    const run = async (): Promise<void> => {
    	const report = await api.get('https://api.example.com/reports').json<unknown>();
    	console.log(report);
    };
    
    void run();
    

    The instance makes GET requests eligible for up to three retries on the selected statuses. For those statuses, Ky uses retry timing headers when available and caps a header-provided delay at 30 seconds. Otherwise it uses the configured backoff and jitter. If an attempt succeeds, .json() resolves with the parsed response body; if retries are exhausted, the request rejects with its error.

Customize the retry decision

Use shouldRetry to decide whether a failed attempt merits another try. Ky calls it only after the retry limit and method checks pass. Return true to retry, false to stop, or undefined (or no value) to keep Ky's default decision. A true result bypasses the default timeout and status-code checks, but not the retry limit or method checks.

  1. Add a decision for rate limits while leaving other failures to the default rules:

    ts
    import ky, {HTTPError} from 'ky';
    
    const api = ky.extend({
    	retry: {
    		limit: 3,
    		methods: ['get'],
    		statusCodes: [429, 503],
    		shouldRetry: ({error, retryCount}) => {
    			if (error instanceof HTTPError) {
    				const status = error.response.status;
    
    				if (status === 429) {
    					return retryCount <= 2;
    				}
    
    				if (status >= 400 && status < 500) {
    					return false;
    				}
    			}
    
    			return undefined;
    		},
    	},
    });
    
    const run = async (): Promise<void> => {
    	const report = await api.get('https://api.example.com/reports').json<unknown>();
    	console.log(report);
    };
    
    void run();
    

    retryCount starts at 1 for the first retry. This policy allows at most two retries for 429, rejects other 4xx failures without retrying, and delegates other failures to Ky's default checks. For example, a 503 remains eligible under the configured statusCodes; recognized network errors also use the default behavior for retriable methods. shouldRetry decides whether Ky retries. The beforeRetry hook runs only after Ky confirms the retry and is where you modify the request.

Retry options

The defaults below apply when you provide an object and omit a field. Set limit to 0 to disable retries. A numeric retry value is shorthand for limit; the other defaults remain in effect.

OptionTypeDefaultWhat it does
limitnumber2Sets the number of retries. It must be a finite, non-negative integer.
methodsreadonly HttpMethod[]['get', 'put', 'head', 'delete', 'options', 'trace', 'query']Restricts retries to these HTTP methods.
statusCodesreadonly number[][408, 413, 429, 500, 502, 503, 504]Lists HTTP response statuses eligible for retry. 413 is retried only when the response has a retry timing header.
afterStatusCodesreadonly number[][413, 429, 503]Selects statuses that use retry timing headers. Each status must also appear in statusCodes.
maxRetryAfternumberInfinityCaps a delay obtained from a retry timing header.
backoffLimitnumberInfinityCaps each calculated retry delay in milliseconds.
delay(attemptCount: number) => numberattemptCount => 0.3 * (2 ** (attemptCount - 1)) * 1000Calculates a delay in milliseconds; the attempt count starts at 1.
jitterboolean | ((delay: number) => number)undefinedAdds random variation to calculated retry delays. true uses full jitter. Jitter does not change a delay supplied by a server timing header.
retryOnTimeoutbooleanfalseEnables retries when a request times out before a response arrives. A timeout while a shortcut method reads a received response body is not retried.
shouldRetry(state: ShouldRetryState) => boolean | void | Promise<boolean | void>undefinedApplies a custom retry decision after limit and method checks pass.

Pitfalls

  • With retries enabled, Ky buffers a streaming request body in memory so it can replay it. For a large streaming upload that does not need retries, set retry: {limit: 0}.
  • A retry request returned from beforeRetry or supplied to ky.retry({request}) is used as-is and is not sanitized. If it targets another origin, remove credentials you do not want forwarded.
  • Ky automatically retries recognized network errors for retriable methods. Other unrecognized errors are not retried by default; use shouldRetry when your runtime or application needs a different decision.
  • When Ky populates HTTPError.data, it consumes the response body. Do not then call error.response.json() or another body method; use error.data instead. See Ky errors and validation.

Was this page helpful?