Ky separates errors in its HTTP lifecycle from errors raised after a successful response fails schema validation.
How the errors differ
KyError is the base class for errors Ky raises during its HTTP lifecycle. A non-2xx response raises HTTPError when throwHttpErrors is enabled; it includes the response, request, normalized options, and pre-parsed response data. A failed connection raises NetworkError, which includes the request and original error as cause. A request timeout raises TimeoutError, which includes the request. A forced retry initiated by an afterResponse hook is represented by ForceRetryError.
By contrast, SchemaValidationError means Ky received a successful response, parsed its JSON, and the supplied Standard Schema validator rejected that data. It extends Error, not KyError, and exposes the validator's issues.
The lifecycle error family also includes ResponseSizeError, raised when a response body exceeds maxResponseSize; it carries the configured byte limit in maxResponseSize. Use isResponseSizeError to narrow a caught value to that error type. These APIs are unreleased and require the next Ky release; they are not available in the current npm release, Ky 2.1.0.
In that release, ResponseSizeError is a KyError, and isResponseSizeError(error) identifies it.
mermaidflowchart TD A["Ky request"] --> B{"HTTP lifecycle"} B -->|"non-2xx, throwHttpErrors enabled"| C["HTTPError"] B -->|"connection failure"| D["NetworkError"] B -->|"request timeout"| E["TimeoutError"] B -->|"response body exceeds maxResponseSize"| J["ResponseSizeError"] B -->|"forced retry from afterResponse"| F["ForceRetryError"] B -->|"successful response"| G["Parse and validate JSON"] G -->|"schema rejects data"| H["SchemaValidationError"] G -->|"schema accepts data"| I["Validated data"]
Identify an error at the call site
Use Ky's type guards to narrow unknown caught values to lifecycle errors. Check the schema error separately with instanceof; the catch block below handles it before the Ky lifecycle guards. The sample calls ky and uses Zod as a Standard Schema-compatible validator. Install both packages with npm install ky zod, and point the request at an endpoint that returns a JSON user object.
tsimport ky, {
SchemaValidationError,
isHTTPError,
isKyError,
isNetworkError,
isTimeoutError,
} 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/users/1').json(userSchema);
console.log('Validated user:', user);
} catch (error) {
if (error instanceof SchemaValidationError) {
console.error('Response data did not match the schema:', error.issues);
} else if (isHTTPError(error)) {
console.error('HTTP status:', error.response.status);
console.error('Error response body:', error.data);
} else if (isNetworkError(error)) {
console.error('Network failure for:', error.request.url);
} else if (isTimeoutError(error)) {
console.error('Timed out request:', error.request.url);
} else if (isKyError(error)) {
console.error('Other Ky lifecycle error:', error.message);
} else {
console.error('Other error:', error);
}
}
}
void loadUser();
If the response contains a user with a string name, the call returns and logs the validated user. A non-2xx response enters the HTTPError branch, a connection failure or timeout enters its matching branch, and a successful response with invalid data enters the schema-error branch. The guards narrow error so each branch can read that error type's properties.
Ky detects network errors with runtime-specific heuristics, so an unrecognized runtime may not wrap a connection failure in NetworkError.
When Ky fills HTTPError.data, it consumes the response body. Read the parsed error body from error.data; calling error.response.json() or another body method afterward does not work. The response remains available for status and headers, and data can be undefined if the body is empty, unreadable, or cannot be parsed.
For the general lifecycle guards and hook timing, see How Ky works. For the additional forced-retry case, isForceRetryError identifies the ForceRetryError signal in beforeRetry and beforeError hooks.
Was this page helpful?