# Configure retries, retry codes, and token refresh

Use [`ky`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#default) when you need to decide which failures are retried, identify a forced retry with a machine-readable code, or replace an expired credential before the next attempt.

## Choose when Ky retries

Set `retry.limit` and `retry.statusCodes` for the ordinary retry policy. Use [`HTTPError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-httperror#httperror) when the policy depends on an HTTP response, and use `shouldRetry` when it also depends on the retry count. It runs only after the method and retry-limit checks, and its result controls the decision: `true` forces a retry, `false` prevents one, and `undefined` keeps Ky's default checks.

`beforeRetry` has a different job. Ky calls it after a retry has been selected, so use it to change the request rather than to decide whether the retry happens.

```ts
import ky, {HTTPError} from 'ky';

async function main(): Promise<void> {
	const response = await ky.get('https://api.example.com/report', {
	retry: {
		limit: 3,
		methods: ['get'],
		statusCodes: [429, 500, 503],
		shouldRetry: ({error, retryCount}) => {
			if (error instanceof HTTPError && error.response.status === 429) {
				return retryCount <= 2;
			}

			if (error instanceof HTTPError && error.response.status >= 400 && error.response.status < 500) {
				return false;
			}

			return undefined;
		},
	},
	});

console.log(response.status);
}

void main();
```

The request retries up to three times for the configured `GET` statuses. A `429` retries only on the first two retry decisions; other client errors do not retry. Returning `undefined` leaves network errors and the configured status-code behavior to Ky's default logic.

The default retry delay grows exponentially. Set `retry.delay` to calculate it from the attempt count, `retry.backoffLimit` to cap each delay, and `retry.jitter` to add randomness. A server retry-timing header takes precedence over jitter. Set `retry.retryOnTimeout: true` when a timeout before receiving a response is also retryable. A timeout while a shortcut method reads an already received body is not retried.

Retries buffer a streaming request body through `tee()`. That can consume substantial memory, so set `retry: {limit: 0}` for a large streaming upload that does not need retries.

## Force a retry and attach a code

The addition here is a response-content-triggered retry—even after a successful HTTP status—with a reason carried into the retry hook. For ordinary retry selection and hook roles, see [How Ky works](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/how-ky-works).

```ts
import ky, {isForceRetryError} from 'ky';

const api = ky.extend({
	retry: {limit: 2},
	hooks: {
		afterResponse: [
			async ({response, retryCount}) => {
				if (response.status !== 200) {
					return response;
				}

				const data = await response.clone().json<{ready: boolean}>();
				if (!data.ready && retryCount === 0) {
					return ky.retry({code: 'REPORT_NOT_READY'});
				}

				return response;
			},
		],
		beforeRetry: [
			({error, retryCount}) => {
				if (isForceRetryError(error)) {
					console.log(`Forced retry #${retryCount}: ${error.code}`);
				}
			},
		],
	},
});

async function main(): Promise<void> {
	const report = await api.get('https://api.example.com/report').json<{ready: boolean}>();
	console.log(report.ready);
}

void main();
```

When the response says `ready: false` on the first attempt, Ky selects another attempt and the `beforeRetry` hook sees the `REPORT_NOT_READY` code. The sample logs the later response's `ready` field; it is `undefined` if the service omits that field. If the retry limit is exhausted, the forced retry is surfaced as a `ForceRetryError` through Ky's error lifecycle.

## Distinguish the failure you handle

The addition here is one caller branch that reports the relevant request detail for each failure. For the guards' meanings, see [How Ky works](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/how-ky-works).

```ts
import ky, {isHTTPError, isNetworkError, isTimeoutError} from 'ky';

async function main(): Promise<void> {
	try {
		await ky.get('https://api.example.com/account', {timeout: 5_000}).json();
	} catch (error) {
		if (isHTTPError(error)) {
			console.error('HTTP status:', error.response.status);
		} else if (isNetworkError(error)) {
			console.error('No response from:', error.request.url);
		} else if (isTimeoutError(error)) {
			console.error('Timed out:', error.request.url);
		} else {
			throw error;
		}
	}
}

void main();
```

See [error handling and validation](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/error-handling-and-validation) for the response-error body handling and error model.
