Yak Docs
Tool Adapters

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/rest
pnpm add @yak-io/rest
yarn add @yak-io/rest
bun add @yak-io/rest

Quick 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

OptionTypeDescription
namestringTool name suffix — exposed as rest_<name>.
specRecord<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.
idstringStable 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.

On this page