Import
import { ApiClient, ApiError, createApiClient } from "bytekit/api-client";
What it does
ApiClient is the main request abstraction in ByteKit. It wraps the Fetch API with timeouts, request/response interceptors, automatic retries, circuit breaker protection, localized error messages, and optional response validation through built-in schemas or external libraries like Zod and Valibot.
Constructor
const api = new ApiClient(config: ApiClientConfig);
ApiClientConfig
| Property | Type | Default | Description |
|---|
baseUrl | string | URL | — | Required. Base URL for all requests. Alias: baseURL. |
defaultHeaders | HeadersInit | {} | Headers sent with every request. |
timeoutMs | number | 15000 | Request timeout in milliseconds. |
locale | "en" | "es" | "es" | Locale for built-in error messages. |
interceptors | ApiClientInterceptors | — | Request and response interceptor functions. |
disableInterceptors | boolean | false | Disable all interceptors. |
logger | Logger | — | Logger instance for request/error logging. |
retryPolicy | RetryConfig | — | Retry configuration (see RetryPolicy). |
circuitBreaker | CircuitBreakerConfig | — | Circuit breaker configuration (see CircuitBreaker). |
fetchImpl | typeof fetch | globalThis.fetch | Custom fetch implementation. |
errorMessages | Record<Locale, Record<number, string>> | Built-in | Override default error messages per status code and locale. |
logHeaders | boolean | false | Include headers in log output. |
redactHeaderKeys | string[] | Auth/cookie keys | Header keys to redact from logs. |
Basic setup
const api = new ApiClient({
baseUrl: "https://api.example.com",
timeoutMs: 10_000,
defaultHeaders: {
Authorization: `Bearer ${token}`,
},
});
Methods
HTTP verbs
| Method | Signature | Description |
|---|
get | get<T>(path, options?): Promise<T> | GET request. |
post | post<T>(path, bodyOrOptions?): Promise<T> | POST request. Accepts a body directly or RequestOptions. |
put | put<T>(path, bodyOrOptions?): Promise<T> | PUT request. Same flexibility as post. |
patch | patch<T>(path, bodyOrOptions?): Promise<T> | PATCH request. Same flexibility as post. |
delete | delete<T>(path, options?): Promise<T> | DELETE request. |
getList | getList<T>(path, options?): Promise<PaginatedResponse<T>> | GET with built-in pagination, sorting, and filters. |
post, put, and patch accept either a plain body object or a full RequestOptions object with a body property — whichever is more convenient.
RequestOptions<T>
| Property | Type | Description |
|---|
searchParams | Record<string, QueryParam> | Query string parameters. |
headers | HeadersInit | Per-request headers (merged with defaults). |
timeoutMs | number | Override the client-level timeout. |
validateResponse | ValidationSchema | SchemaAdapter<T> | Validate the response body. |
signal | AbortSignal | Cancellation signal. |
skipRetry | boolean | Skip retry policy for this request. |
skipInterceptors | boolean | Skip interceptors for this request. |
body | FormData | string | Record<string, unknown> | Request body (when using options form). |
ListOptions<T>
Extends RequestOptions with pagination, sorting, and filter helpers.
| Property | Type | Description |
|---|
pagination | { page?, limit?, offset? } | Pagination parameters. |
sort | { field?, order? } | Sort field and direction. |
filters | Record<string, QueryParam> | Filter parameters merged into query string. |
Examples
Typed GET
type User = { id: number; name: string };
const user = await api.get<User>("/users/1");
POST with payload
const created = await api.post<{ id: string }>("/users", {
name: "Ada Lovelace",
});
POST with full options
const created = await api.post<{ id: string }>("/users", {
body: { name: "Ada Lovelace" },
headers: { "X-Idempotency-Key": crypto.randomUUID() },
});
Paginated list
const result = await api.getList<Product>("/products", {
pagination: { page: 1, limit: 20 },
sort: { field: "price", order: "desc" },
filters: { category: "electronics" },
});
console.log(result.data); // Product[]
console.log(result.pagination.total); // total count
Response validation with Zod
import { zodAdapter } from "bytekit/schema-adapter";
import { z } from "zod";
const UserSchema = z.object({ id: z.number(), name: z.string() });
const user = await api.get("/users/1", {
validateResponse: zodAdapter(UserSchema),
});
Interceptors
Interceptors let you transform requests before they are sent and responses before they are returned.
const api = new ApiClient({
baseUrl: "https://api.example.com",
interceptors: {
request: async (url, init) => {
const headers = new Headers(init.headers);
headers.set("X-Trace-Id", crypto.randomUUID());
return [url, { ...init, headers }];
},
response: async (response) => {
// Log slow responses
return response;
},
},
});
ApiError
Thrown when a response has a non-2xx status code.
| Property | Type | Description |
|---|
status | number | HTTP status code. |
statusText | string | HTTP status text. |
message | string | Localized error message. |
body | unknown | Parsed response body, if available. |
isTimeout | boolean | true when the error was caused by a timeout. |
try {
await api.get("/protected");
} catch (err) {
if (err instanceof ApiError) {
console.error(err.status, err.message);
}
}
ApiError also exposes a details getter, toJSON(), and a readable toString() for debugging.
Factory
createApiClient is a shortcut for new ApiClient(config).
import { createApiClient } from "bytekit/api-client";
const api = createApiClient({
baseUrl: "https://api.example.com",
});
ApiClient requires either baseUrl or baseURL in the config. An error is thrown at construction time if neither is provided.