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.
Configuring the client
Section titled “Configuring the client”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.
Calling an operation
Section titled “Calling an operation”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). |
Errors
Section titled “Errors”Operations resolve with the response body and reject with one of two errors:
ApiErrorfor statuses that are neither 2xx nor a declared 3xx. Each operation types it per documented status (<Name>ApiError) and exports a guard, so checkingstatusnarrowsbody.RequestErrorwhen no usable response arrived. Itskindis'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
Section titled “Middleware”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.
Retries and token refresh
Section titled “Retries and token refresh”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.
Several clients
Section titled “Several clients”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.
Full responses
Section titled “Full responses”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'));Limitations
Section titled “Limitations”- Retries never resend a request body that is a stream.
- Browsers do not let scripts set the
Cookieheader, so cookie parameters only reach the server from Node.js and other server runtimes; in a browser, useincludeCredentials: trueand let the browser send its cookies. int64values arenumbers: beyond 2^53 they lose precision when the response JSON is parsed.
What's generated
openapi: 3.1.0info: title: Petstore version: '1.0'servers: - url: https://petstore.example.com/v1paths: /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: stringpets/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';};