# Requests and responses

Ky is a Fetch-based HTTP client that builds a request from a Fetch-compatible input and options, then exposes response body shortcuts, typed JSON, and schema validation on the returned promise.

## The request and response flow

Pass a string, URL, or Request as the input. Ky applies its options, constructs the Request, performs the fetch, and gives you a response promise whose body methods can be called before awaiting the response object.

```mermaid
flowchart LR
    A["Input + options"] --> B["Ky builds Request"]
    B --> C["Fetch"]
    C --> D["ResponsePromise"]
    D --> E["json(), text(), blob(), formData(), arrayBuffer(), bytes()"]
    E --> F["typed value or schema-validated value"]
    C --> G["HTTPError for non-2xx"]
    E --> H["SchemaValidationError for rejected JSON"]
```

The `input` accepts the [`Input`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#input) type: a `string`, `URL`, or `Request`. The default [`ky`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#default) export also provides method shortcuts such as `ky.post()`. A shortcut sets the request method; the other options remain Fetch options or Ky options.

When you pass `json`, Ky stringifies the value, uses it as the request body, and sets `Content-Type: application/json` unless your `headers` option supplies a content type. Use `json` instead of manually creating a `body` for JSON data.

```ts
import ky from 'ky';

async function main(): Promise<void> {
	const result = await ky.post('https://api.example.com/users', {
		json: {name: 'Ada'},
	}).json<{id: string; name: string}>();

console.log(result.id, result.name);
}

main();
```

This sends a JSON `POST` and returns the parsed response body as the type supplied to `json()`. The promise returned by the request is a [`ResponsePromise`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-responsepromise#responsepromise), so you do not need to await a separate response before selecting a body method.

## Choose a response body

The response promise provides `json()`, `text()`, `formData()`, `arrayBuffer()`, `blob()`, and `bytes()` shortcuts. `bytes()` is available when the runtime supports `Response.prototype.bytes()`.

```ts
import ky from 'ky';

async function main(): Promise<void> {
	const text = await ky.get('https://api.example.com/status').text();
	const file = await ky.get('https://api.example.com/report.pdf').blob();
	const raw = await ky.get('https://api.example.com/archive.bin').arrayBuffer();

console.log(text, file.size, raw.byteLength);
}

main();
```

Each call consumes the response body in the selected representation. If you need response metadata as well, await the promise itself. The resulting [`KyResponse`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#kyresponse) has the standard `Response` properties and methods plus Ky's typed `json()` method.

```ts
import ky from 'ky';

async function main(): Promise<void> {
	const response = await ky.get('https://api.example.com/status');

console.log(response.status, response.headers.get('content-type'));
const body = await response.json<{status: string}>();
console.log(body.status);
}

main();
```

Calling `json()` for an empty response throws because there is no JSON body to parse. When an empty body is part of your protocol, configure `parseJson` to provide the required handling rather than assuming that `json()` can parse it.

## Type JSON at the boundary

Without a type argument, `json()` returns `unknown`. Give the request or the body method a type argument when your application has a known response shape.

```ts
import ky from 'ky';

type User = {
	id: string;
	name: string;
};

async function main(): Promise<void> {
	const fromRequestType = await ky<User>('https://api.example.com/users/1').json();
	const fromBodyType = await ky('https://api.example.com/users/1').json<User>();

console.log(fromRequestType.name, fromBodyType.id);
}

main();
```

These type arguments describe the value TypeScript expects; they do not validate the bytes returned by the server. Use a Standard Schema-compatible validator when the response must be checked at runtime.

## Validate JSON at runtime

Pass a Standard Schema validator to `json(schema)`. Ky returns the validator's inferred output when validation succeeds and throws [`SchemaValidationError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#schemavalidationerror) when it fails.

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

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

async function main(): Promise<void> {
	try {
		const user = await ky('https://api.example.com/users/1').json(userSchema);
		console.log(user);
	} catch (error) {
		if (error instanceof SchemaValidationError) {
			console.error(error.issues);
		} else {
			throw error;
		}
	}
}

main();
```

Schema validation happens after the request succeeds. `SchemaValidationError` is therefore not a Ky lifecycle error and is not matched by [`isKyError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky#iskyerror)().

## Separate HTTP failures from missing responses

Ky throws [`HTTPError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-httperror#httperror) for a non-2xx response when `throwHttpErrors` is enabled. The error retains the `KyResponse` in `error.response`, the [`KyRequest`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-kyrequest#kyrequest) in `error.request`, normalized options, and a pre-parsed `error.data` value when Ky can read the error body. Because Ky consumes the body while populating `error.data`, use `error.data` rather than calling `error.response.json()` in this case.

For the error classes and type guards, see [How Ky works](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/how-ky-works#response-data-and-failures-stay-distinct).

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

async function main(): Promise<void> {
	try {
		await ky.get('https://api.example.com/users/1').json<{name: string}>();
	} catch (error) {
		if (isHTTPError(error)) {
			console.error('Status:', error.response.status);
			console.error('Error body:', error.data);
		} else {
			throw error;
		}
	}
}

main();
```

The HTTP branch can inspect the status and parsed error data. The network branch does not try to read a response because none was received. Throw an ordinary error when a caller needs a propagated failure from a retry hook; returning `ky.stop` produces an undefined response that cannot be followed by `.json()` or `.text()`.

## Related pages

- [How Ky works](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/how-ky-works)
- [JSON requests and responses](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/json-requests-and-responses)
- [Ky's error model](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-error-model)
- [Error handling and validation](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/error-handling-and-validation)
