# Ky's error model

Ky separates failures in the HTTP lifecycle from failures in the data you validate after a response arrives. Use the error type to decide whether to inspect a response, retry a request, report a timeout, or fix the schema.

Call the default export [`ky`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#default) for requests and method shortcuts such as `ky.get()`.

## One hierarchy, one deliberate exception

The [`KyError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#kyerror) hierarchy and its deliberate [`SchemaValidationError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#schemavalidationerror) exception are covered in [How Ky works](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/how-ky-works); this page applies that distinction when choosing an error branch.

```mermaid
flowchart TD
    Request["Ky request"] --> Response{"Response received?"}
    Response -->|"no"| Network["NetworkError"]
    Response -->|"yes"| Status{"2xx status?"}
    Status -->|"no"| HTTP["HTTPError"]
    Status -->|"yes"| Body{"Body operation succeeds?"}
    Body -->|"timeout"| Timeout["TimeoutError"]
    Body -->|"too large"| Size["ResponseSizeError"]
    Body -->|"JSON schema rejects"| Schema["SchemaValidationError"]
    Body -->|"yes"| Value["Validated or typed value"]
```

For the broad cross-realm [`isKyError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#iskyerror) check and its schema-validation boundary, see [How Ky works](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/how-ky-works).

## HTTP failures

An [`HTTPError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-httperror#httperror) means Ky received a non-2xx response while `throwHttpErrors` is enabled. Inspect `error.response.status`, `error.response.headers`, and `error.data`. Ky populates `data` before error hooks run and parses JSON responses with `parseJson` when that option is set, or with `JSON.parse` otherwise. For other content types, `data` is text.

Ky consumes the response body while populating `error.data`. Do not call `error.response.json()` or another body method afterwards; use `error.data`. The response remains useful for status and headers. See [error handling and validation](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/error-handling-and-validation) for the handling pattern used by this guide.

Use [`isHTTPError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#ishttperror) to narrow an unknown catch value before reading the response.

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

async function loadStatus(): Promise<void> {
	try {
		await ky.get('https://api.example.com/status').json();
	} catch (error) {
		if (isHTTPError(error)) {
			console.error(error.response.status, error.data);
			return;
		}

		throw error;
	}
}

void loadStatus();
```

The call either gives you the parsed body shortcut or enters the HTTP branch with the status and pre-parsed error data. A network failure does not enter this branch because it has no response to expose.

## Network and timeout failures

[`NetworkError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#networkerror) means the request failed at the network layer, such as through DNS failure, connection refusal, or an offline runtime. It carries the `request`, and the original runtime error is its `cause`. Network errors are automatically retried for retriable methods. A connection that drops while `.json()` or another shortcut reads an already received response is also wrapped as `NetworkError`, but Ky does not retry that body-read failure because the response already arrived. Runtime-specific detection can miss an unfamiliar error shape; use the `shouldRetry` option for that case.

[`TimeoutError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#timeouterror) means the request timed out and also carries the `request`. `timeout` applies to an attempt, while `totalTimeout` limits the whole operation across attempts and delays. Both are distinct from a server response with a non-2xx status.

Use [`isNetworkError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#isnetworkerror) and [`isTimeoutError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#istimeouterror) to narrow these two cases.

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

async function requestWithDiagnostics(): Promise<void> {
	try {
		await ky.get('https://api.example.com/report', {
			timeout: 1_000,
			totalTimeout: 5_000,
		}).json();
	} catch (error) {
		if (isTimeoutError(error)) {
			console.error('Timed out:', error.request.url);
			return;
		}

		if (isNetworkError(error)) {
			console.error('Network failure:', error.request.url);
			return;
		}

		throw error;
	}
}

void requestWithDiagnostics();
```

The timeout branch reports an attempt or overall time limit. The network branch reports a request that did not produce a usable response. A network failure while a shortcut reads an already received response is not retried; use `shouldRetry` when your runtime needs a custom retry decision.

## Response-size failures

[`ResponseSizeError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#responsesizeerror) is the shipped error for a response body that exceeds `maxResponseSize`. It carries the request and the configured byte limit. The limit counts bytes from the decompressed response stream as Ky consumes it, and exceeding it does not automatically retry.

`ResponseSizeError`, `maxResponseSize`, and [`isResponseSizeError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#isresponsesizeerror) are not in the current Ky 2.1.0 npm release. Use them only after installing a release that exports them; do not add this branch to an application pinned to 2.1.0.

## Forced retries

Use the forced-retry branch when response content—not a transport failure—requires another attempt. The lifecycle and hook pattern are covered in [How Ky works](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/how-ky-works); this page shows how to identify the resulting [`ForceRetryError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#forceretryerror) with [`isForceRetryError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#isforceretryerror).

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

const api = ky.extend({
	retry: {limit: 1},
	hooks: {
		afterResponse: [() => ky.retry()],
		beforeRetry: [({error, retryCount}) => {
			if (isForceRetryError(error)) {
				console.log(`Forced retry #${retryCount}: ${error.code}`);
			}
		}],
	},
});

async function runForcedRetry(): Promise<void> {
	try {
		await api.get('https://api.example.com/catalog');
	} catch (error) {
		if (isForceRetryError(error)) {
			console.log('Retry limit reached:', error.code);
			return;
		}

		throw error;
	}
}

void runForcedRetry();
```

This differs from an ordinary retry because the response has already arrived: the `beforeRetry` branch can identify the explicit response-driven request, and the configured limit bounds repeated requests.

## Schema-validation failures

`.json()` can receive a Standard Schema-compatible validator. [`SchemaValidationError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#schemavalidationerror) means Ky received the response and the validator rejected its JSON value. Read its `issues` property. Do not classify it with `isKyError()`; handle it separately from transport, status, retry, and timeout failures.

```ts
import ky, {isKyError, SchemaValidationError} from 'ky';
import {z} from 'zod';

const userSchema = z.object({name: z.string()});

async function loadUser(): Promise<void> {
	try {
		const user = await ky.get('https://api.example.com/user').json(userSchema);
		console.log('Validated user:', user.name);
	} catch (error) {
		if (error instanceof SchemaValidationError) {
			console.error('The response arrived, but validation failed:', error.issues);
			return;
		}

		if (isKyError(error)) {
			console.error('The HTTP lifecycle failed:', error.message);
			return;
		}

		throw error;
	}
}

void loadUser();
```

The success path receives the schema-inferred value, while the validation branch receives the validator's issues. A successful HTTP response does not guarantee a successful schema validation.

## Choosing a check

For HTTP, network, and broad lifecycle checks, use the guidance in [Requests and responses](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/requests-and-responses). This page adds the timeout, forced-retry, response-size, and schema-validation distinctions described above.

For shared defaults, [`replaceOption`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#replaceoption) can replace a merged option when you build a derived instance; it does not change the error classification. The default export [`ky`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#default) provides the request shortcuts and the `retry` control used above.
