Skip to main content

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

PropertyTypeDefaultDescription
baseUrlstring | URLRequired. Base URL for all requests. Alias: baseURL.
defaultHeadersHeadersInit{}Headers sent with every request.
timeoutMsnumber15000Request timeout in milliseconds.
locale"en" | "es""es"Locale for built-in error messages.
interceptorsApiClientInterceptorsRequest and response interceptor functions.
disableInterceptorsbooleanfalseDisable all interceptors.
loggerLoggerLogger instance for request/error logging.
retryPolicyRetryConfigRetry configuration (see RetryPolicy).
circuitBreakerCircuitBreakerConfigCircuit breaker configuration (see CircuitBreaker).
fetchImpltypeof fetchglobalThis.fetchCustom fetch implementation.
errorMessagesRecord<Locale, Record<number, string>>Built-inOverride default error messages per status code and locale.
logHeadersbooleanfalseInclude headers in log output.
redactHeaderKeysstring[]Auth/cookie keysHeader 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

MethodSignatureDescription
getget<T>(path, options?): Promise<T>GET request.
postpost<T>(path, bodyOrOptions?): Promise<T>POST request. Accepts a body directly or RequestOptions.
putput<T>(path, bodyOrOptions?): Promise<T>PUT request. Same flexibility as post.
patchpatch<T>(path, bodyOrOptions?): Promise<T>PATCH request. Same flexibility as post.
deletedelete<T>(path, options?): Promise<T>DELETE request.
getListgetList<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>

PropertyTypeDescription
searchParamsRecord<string, QueryParam>Query string parameters.
headersHeadersInitPer-request headers (merged with defaults).
timeoutMsnumberOverride the client-level timeout.
validateResponseValidationSchema | SchemaAdapter<T>Validate the response body.
signalAbortSignalCancellation signal.
skipRetrybooleanSkip retry policy for this request.
skipInterceptorsbooleanSkip interceptors for this request.
bodyFormData | string | Record<string, unknown>Request body (when using options form).

ListOptions<T>

Extends RequestOptions with pagination, sorting, and filter helpers.
PropertyTypeDescription
pagination{ page?, limit?, offset? }Pagination parameters.
sort{ field?, order? }Sort field and direction.
filtersRecord<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.
PropertyTypeDescription
statusnumberHTTP status code.
statusTextstringHTTP status text.
messagestringLocalized error message.
bodyunknownParsed response body, if available.
isTimeoutbooleantrue 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.