Skip to Content
API ReferencecreateApi()

createApi(executor, router)

Produces a fully-typed API client. Each endpoint becomes an async function with the signature (params, signalOrOptions?) => Promise<Response> — the second argument is an AbortSignal or a { signal?, headers?, timeout? } options object (see Per-call options).

import { createApi } from '@routar/core' const todoApi = createApi(executor, todoRouter) // params follow { path?, query?, body? } await todoApi.getDetail({ path: { id: 1 } }) await todoApi.create({ body: { title: 'buy milk' } }) await todoApi.update({ path: { id: 1 }, body: { completed: true }, query: { version: 2 } })

Overloads

FormUsage
createApi(executor, router)Pre-built router via defineRouter — preferred for multi-environment setups
createApi(executor, prefix, endpoints)Inline endpoints with a URL prefix
createApi(executor, endpoints)Inline endpoints without a prefix — simplest form for small APIs
// with a pre-built router createApi(executor, todoRouter) // inline with prefix createApi(executor, '/todos', { getList: endpoint({ ... }) }) // inline without prefix createApi(executor, { getList: endpoint({ ... }) })

Parameters

ParameterTypeDescription
executorExecutorHTTP transport. Create with createFetchExecutor or createAxiosExecutor.
routerRouterDef | RouterEndpointsRouter definition or inline endpoints record
prefixstring (optional)URL prefix when passing endpoints inline

Request params

Request params map to { path?, query?, body? } matching the request schema shape:

// request: { path: z.object({ id: z.number() }) } await api.getDetail({ path: { id: 1 } }) // request: { query: z.object({ page: z.number() }) } await api.getList({ query: { page: 2 } }) // request: { body: z.object({ title: z.string() }) } await api.create({ body: { title: 'buy milk' } })

Per-call options

The second argument accepts either a bare AbortSignal (backward compatible) or an options object { signal?, headers?, timeout? }:

// cancel with an AbortSignal const controller = new AbortController() await todoApi.getList({}, controller.signal) controller.abort() // per-call headers + timeout await todoApi.create( { body: { title: 'buy milk' } }, { headers: { 'Idempotency-Key': key }, // merged over executor defaults timeout: 30_000, // throws TimeoutError when exceeded signal: controller.signal, }, )
  • headers are seeded onto the request and merged over the executor’s default headers (per-call wins). A plugin’s onRequest runs afterward and wins on a key collision (plugins are cross-cutting policy such as auth).
  • timeout aborts the request with a TimeoutError and is applied by the core client, so it works on every executor (fetch, Axios, ky, custom) and composes with any executor-level timeout (whichever fires first).

Validation modes

validate controls whether request/response schemas run, per phase. Each phase accepts true (default — validate and throw on failure), false (skip), or 'warn':

// observe response drift without breaking production const api = createApi(executor, todoRouter, { validate: { request: true, response: 'warn' }, onValidationError: (err, ctx) => { // ctx: { kind: 'request' | 'response', method, url, data } Sentry.captureException(err, { extra: ctx }) }, })

In 'warn' mode a parse failure passes the raw data through (no outage) and calls onValidationError(error, context) instead of throwing — the drift-observation middle ground between full validation and validate: false. onValidationError is never called under true (which throws) or false (which skips).

SSR/CSR pattern

The typical pattern is to create two clients from the same router — one for CSR, one for SSR:

export const todoApi = createApi(clientExecutor, todoRouter) // CSR export const todoServerApi = createApi(serverExecutor, todoRouter) // SSR

See the SSR/CSR guide for a complete example.

Last updated on