REST/OpenAPI Adapter
The @yak-io/rest package exposes a REST API to the assistant as a single rest_<name> tool.
The model authors a request (method, path, query, body) from the OpenAPI spec you provide, and the
adapter hands it to your client to execute — Yak never makes the call itself, so your existing
base URL, auth, and transport all apply.
Adapters compose into one tool manifest and one onToolCall via createYakToolset, so REST
calls flow through the same path as every other tool, including
useYakToolEvent.
Installation
npm install @yak-io/restpnpm add @yak-io/restyarn add @yak-io/restbun add @yak-io/restQuick Start
import { createYakToolset, YakProvider, YakWidget } from "@yak-io/react";
import { createRESTToolAdapter } from "@yak-io/rest";
const openApiSpec = {
openapi: "3.0.0",
paths: {
"/orders": { get: { summary: "List orders" } },
"/orders/{id}": { get: { summary: "Get order by ID" } },
},
};
const toolset = createYakToolset([
createRESTToolAdapter({
name: "orders",
spec: openApiSpec,
// Run the model-authored request with your own client (base URL, auth, transport).
// Use anything — axios, your app's API client, or a plain fetch like this.
execute: async ({ method, path, query, body }) => {
const url = new URL(path, "https://api.example.com");
if (query) for (const [k, v] of Object.entries(query)) url.searchParams.set(k, v);
const res = await fetch(url, {
method,
headers: { "Content-Type": "application/json", Authorization: `Bearer ${getToken()}` },
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
return res.json();
},
}),
]);
export default function App() {
return (
<YakProvider
appId="your-app-id"
getConfig={async () => ({ routes, ...(await toolset.getConfig()) })}
onToolCall={toolset.onToolCall}
>
<YakWidget />
</YakProvider>
);
}The model receives the spec in the tool's description and produces a
{ method, path, query, body } request. Yak passes that request straight to your execute
callback and returns whatever it resolves to — you own the base URL, credentials, and transport.
Already have an API client (axios, a configured fetch, your app's SDK)? Hand the request
straight to it. The adapter only builds the tool and the request shape — it never owns the client.
Lazy spec (fetched at runtime)
If your OpenAPI spec lives behind a URL — or is generated at runtime — pass spec as a resolver
instead of an object or string. The factory stays synchronous, so you still construct the adapter
without await; the resolver runs once when the toolset first materializes its tools, and the
resulting spec is cached.
createRESTToolAdapter({
name: "billing",
// Resolved once, on first use — not at construction.
spec: async () => {
const res = await fetch("https://api.example.com/openapi.json", {
headers: { Authorization: `Bearer ${getToken()}` },
});
return res.json();
},
execute: ({ method, path, query, body }) => myApiClient(method, path, { query, body }),
});The resolver is invoked once and cached, so the spec is fetched a single time — not on every chat open. A rejection is not cached: a transient failure simply retries on the next load.
Multiple APIs
const toolset = createYakToolset([
createRESTToolAdapter({ name: "billing", spec: billingSpec, execute: callBillingApi }),
createRESTToolAdapter({ name: "catalog", spec: catalogSpec, execute: callCatalogApi }),
]);Configuration
| Option | Type | Description |
|---|---|---|
name | string | Tool name suffix — exposed as rest_<name>. |
spec | Record<string, unknown> | string | (() => Record<string, unknown> | string | Promise<…>) | OpenAPI spec — an object, a JSON string, or a resolver returning either (sync or async). Resolved once and cached; use a resolver to fetch the spec lazily. |
execute | (request: RESTRequest) => unknown | Promise<unknown> | Runs the model-authored { method, path, query, body } with your client. Return the result; throw to surface an error. |
id | string | Stable id for diagnostics. Defaults to the tool name. |
Auth & transport
Your execute callback owns the request, so the base URL, authentication, retries, and error
handling are entirely yours — reuse the same authenticated client the rest of your app already
uses. If execute runs in the browser, the API must allow your origin (CORS); to keep the call
server-side instead, route it through your backend (e.g. front your API with
createNextYakHandler and bridge it via createYakServerAdapter).
Migrating from schemaSources
Earlier versions used getConfig().schemaSources plus an onRESTSchemaCall prop. Replace both
with a createRESTToolAdapter composed through createYakToolset (see Quick Start). Your old
onRESTSchemaCall handler becomes the adapter's execute callback — same idea (Yak hands you the
request, your client runs it), now scoped per adapter and flowing through onToolCall, so
useYakToolEvent fires for REST calls too.
Keep the spec focused on the operations you want to expose, and enforce authorization on your API for any write operations.