Skip to content

02Guides

Fetch and axios

Transports, configuration, middleware, retries, timeouts, cancellation and errors.

Every generated client talks to the API through a transport: fetch (the default, with no dependency) or axios (client.adapter: 'axios'). Both sit behind one contract, so operations, errors, middleware and data-fetching hooks behave the same with either. Only the selected transport is generated.

client.ts exports client, the instance every operation uses. Configure it once at startup:

import { client } from './generated/api';
client.setConfig({
baseUrl: 'https://petstore3.swagger.io/api/v3',
headers: { 'x-app-version': '1.4.0' },
// Keys are security scheme names from the specification; functions run for every request.
auth: {
api_key: () => sessionStorage.getItem('apiKey') ?? undefined,
petstore_auth: () => localStorage.getItem('accessToken') ?? undefined,
},
timeout: 10_000,
});

setConfig merges into the current configuration and getConfig() returns a copy of it. headers may also be a function (sync or async) that computes headers per request.

Authentication. auth values are strings or functions per security scheme. API keys are sent where the scheme says (header, query or cookie); HTTP bearer, OAuth 2 and OpenID Connect values are sent as Authorization: Bearer <token>; HTTP basic values are username:password and are base64-encoded. When an operation lists several security requirements, the first one whose credentials are all configured is used. A function returning undefined means “no credentials”, so the next requirement is tried.

Base URL. It defaults to the specification’s first server (client.baseUrl in the config changes the compiled default). Operations that declare their own servers are sent there; see servers.

Every operation takes one params object (path, query, header and cookie parameters, and body) and per-call options:

import { findPetsByStatus, getPetById } from './generated/api';
const controller = new AbortController();
const pet = await getPetById({ petId: 10 }, { timeout: 5_000, signal: controller.signal });
const available = await findPetsByStatus(
{ status: 'available' },
{ headers: { 'x-request-id': crypto.randomUUID() } },
);
console.log(pet.name, available.length);
Option Effect
signal Cancels the request. The call rejects with the signal’s own abort error.
timeout Milliseconds for this call (overrides the client default); covers retries and their waits.
headers Extra headers; an undefined value removes a header.
baseUrl Sends this call elsewhere; wins over operation servers.
parseAs Reads a successful body as 'json', 'text', 'blob', 'arrayBuffer' or 'stream'.
transportOptions Passed to the transport: RequestInit fields for fetch, an axios request config.
client Uses another client instance (see below).

Operations resolve with the response body and reject with one of two errors:

  • ApiError for statuses that are neither 2xx nor a declared 3xx. Each operation types it per documented status (<Name>ApiError) and exports a guard, so checking status narrows body.
  • RequestError when no usable response arrived. Its kind is 'network', 'timeout', 'parse', 'serialization' or 'validation'.
import { getPetById, isGetPetByIdApiError, isRequestError } from './generated/api';
try {
await getPetById({ petId: 10 });
} catch (error) {
if (isGetPetByIdApiError(error)) {
if (error.status === 404) console.error('Not found:', error.body.message);
else console.error('HTTP', error.status, error.body);
} else if (isRequestError(error) && error.kind === 'timeout') {
console.error('The API did not answer in time.');
} else {
throw error; // an abort, or a bug
}
}

ApiError carries status, statusText, body, headers, response (the raw response), operationId, method and url. Aborting rethrows the original abort error rather than wrapping it, so signal.aborted checks and AbortError handling work as with plain fetch. The types are described in generated code.

Middleware runs around every request, in registration order. client.use() returns a function that removes it.

import { client } from './generated/api';
const remove = client.use({
onRequest({ request }) {
request.headers.set('x-request-id', crypto.randomUUID());
},
onResponse({ response, operation }) {
if (response.status === 401) console.warn(`${operation.operationId} needs authentication`);
},
onError({ error, operation }) {
console.error(`${operation.operationId} failed`, error);
},
});
remove();

onRequest may return a replacement request; onResponse and onError may return a response (to recover) or retry() from their context to send the request again. The exact contract is in generated code.

Two middleware are built in. They are opt-in and bundlers drop them when they are not imported.

import { client, refreshAuth, retry } from './generated/api';
// Idempotent methods only, jittered exponential backoff, honors Retry-After and cancellation.
client.use(retry({ attempts: 3, statuses: [429, 502, 503, 504] }));
// On 401: one refresh shared by every failing request, then each request is sent once more.
client.use(
refreshAuth({
scheme: 'petstore_auth',
refresh: async () => (await fetch('/token', { method: 'POST' })).text(),
}),
);

retry() retries GET, HEAD, OPTIONS, PUT, DELETE, TRACE and QUERY requests (and any request with an Idempotency-Key header) on 408, 429, 500, 502, 503, 504 and network failures by default. refreshAuth() only refreshes for operations that have security requirements, so a login endpoint never triggers it; a request is replayed at most once.

For multi-tenant servers or per-request credentials, create more clients and pass one per call:

import { client, createClient, getPetById } from './generated/api';
const tenant = createClient({
...client.getConfig(),
baseUrl: 'https://tenant-a.example.com/api/v3',
});
await getPetById({ petId: 10 }, { client: tenant });

Data-fetching hooks accept a client the same way ({ request: { client } }).

The fetch transport uses the global fetch. Supply another implementation, or fetch options per call:

import { client, createFetchTransport, findPetsByStatus } from './generated/api';
// Any fetch-compatible function, for example one that adds tracing.
const tracedFetch: typeof fetch = (input, init) => fetch(input, init);
client.setConfig({ transport: createFetchTransport({ fetch: tracedFetch }) });
await findPetsByStatus({ status: 'available' }, { transportOptions: { cache: 'no-store' } });

With client.adapter: 'axios', the client uses the default axios instance. Reuse your own instance and its interceptors:

import axios from 'axios';
import { client, createAxiosTransport } from './generated/api';
const http = axios.create({ withCredentials: true });
http.interceptors.request.use((config) => config);
client.setConfig({ transport: createAxiosTransport(http) });

HTTP errors still reject with ApiError: the AxiosError is its cause and the AxiosResponse its response. Bodies are serialized by the generated runtime before they reach axios, and responses are read as bytes and decoded by the runtime exactly as with fetch, so both transports return the same data for the same response.

client.responseStyle: 'full' makes operations resolve with { data, status, headers }, typed as one member per documented success status:

import { getPetById } from './generated/api';
const response = await getPetById({ petId: 10 });
if (response.status === 200) console.log(response.data.name, response.headers.get('etag'));
  • Retries never resend a request body that is a stream.
  • Browsers do not let scripts set the Cookie header, so cookie parameters only reach the server from Node.js and other server runtimes; in a browser, use includeCredentials: true and let the browser send its cookies.
  • int64 values are numbers: beyond 2^53 they lose precision when the response JSON is parsed.

What's generated

petstore.yaml
openapi: 3.1.0
info:
title: Petstore
version: '1.0'
servers:
- url: https://petstore.example.com/v1
paths:
/pets/{petId}:
get:
tags: [pets]
summary: Find a pet by ID.
description: Returns a single pet.
operationId: getPetById
parameters:
- name: petId
in: path
required: true
description: ID of the pet to return.
schema:
type: integer
format: int64
minimum: 1
responses:
'200':
description: The pet.
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
'404':
description: No pet has this ID.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
security:
- api_key: []
components:
securitySchemes:
api_key:
type: apiKey
in: header
name: X-API-Key
schemas:
Pet:
type: object
required: [id, name]
properties:
id:
type: integer
format: int64
name:
type: string
example: Rex
status:
type: string
description: Where the pet is in the store.
enum: [available, pending, sold]
Error:
type: object
required: [message]
properties:
message:
type: string

pets/get-pet-by-id.ts

/* eslint-disable */
// @generated by swaggerz. Do not edit: this file is overwritten on the next generation.
import { type RequestOptions, client } from '../client.js';
import type { Error as ErrorModel } from '../models/error.js';
import type { Pet } from '../models/pet.js';
import { type ApiError, type RequestError, isApiError } from '../runtime/errors.js';
import type { HttpStatus } from '../runtime/types.js';
/** Parameters of {@link getPetById}. */
export type GetPetByIdParams = {
/**
* ID of the pet to return.
*
* @format int64
* @minimum 1
*/
petId: number;
};
/** Response data of {@link getPetById}. */
export type GetPetByIdResponse = Pet;
/** Body of an error response of {@link getPetById}. */
export type GetPetByIdErrorBody = ErrorModel;
/**
* The `ApiError`s {@link getPetById} rejects with: one per documented status, so checking `status` narrows `body`.
*
* The last member covers statuses the specification does not document.
*/
export type GetPetByIdApiError =
| ApiError<ErrorModel, 404>
| ApiError<unknown, Exclude<HttpStatus<1 | 3 | 4 | 5>, 404>>;
/** Everything {@link getPetById} rejects with: an `ApiError` for an error status, or a `RequestError` when no usable response arrived. */
export type GetPetByIdError = GetPetByIdApiError | RequestError;
/**
* Find a pet by ID.
*
* Returns a single pet.
*
* @remarks `GET /pets/{petId}` (operationId `getPetById`)
* @param params - Request parameters.
* @param options - Per-request options: `signal`, `timeout`, `headers`, `client`.
* @returns `200` The pet
* @throws {@link ApiError} `404` No pet has this ID; narrow it with {@link isGetPetByIdApiError}.
* @throws {@link RequestError} When no usable response is received (network failure, timeout or invalid body).
*/
export function getPetById(params: GetPetByIdParams, options?: RequestOptions): Promise<GetPetByIdResponse> {
return (options?.client ?? client).request<GetPetByIdResponse>(
{
operationId: 'getPetById',
method: 'GET',
path: '/pets/{petId}',
pathParams: { petId: params.petId },
accept: 'application/json',
security: [['api_key']],
},
options,
);
}
/** Whether `error` is an `ApiError` rejected by {@link getPetById}. It narrows to {@link GetPetByIdApiError}, so checking `status` afterwards narrows `body` too. */
export function isGetPetByIdApiError(error: unknown): error is GetPetByIdApiError {
return (
isApiError(error) &&
error.operationId === 'getPetById' &&
error.method === 'GET'
);
}

models/pet.ts

/* eslint-disable */
// @generated by swaggerz. Do not edit: this file is overwritten on the next generation.
export type Pet = {
/** @format int64 */
id: number;
/** @example "Rex" */
name: string;
/** Where the pet is in the store. */
status?: 'available' | 'pending' | 'sold';
};
Generated by swaggerz 0.1.2, at build time.