The Problem
Every REST API endpoint you want an agent to use requires a tool. A tool needs three things: a Zod schema describing the parameters, a description the LLM can read, and an execute function that makes the HTTP request. For a single endpoint that is fine. For an API with forty endpoints, it is tedious and error-prone. OpenAPI specs already contain all the information you need. The parameter types are there. The descriptions are there. The URL patterns and HTTP methods are there. The only question is how to convert that information into tools.The Solution
createOpenApiTools reads an OpenAPI 3.0+ spec and returns a Record<string, Tool> — one tool per operation. Each tool has a Zod schema derived from the operation’s parameters and request body, a description from the operation’s summary, and an execute function that builds the correct HTTP request and returns the response.
tools is now a map of operation IDs to AI SDK tools. Hand them to an agent:
{ limit: z.number().optional() }. It decides which endpoints to call, fills in the parameters, and gets back JSON responses. No glue code required.
How It Works
The conversion follows four steps:-
Parse the spec. Smithers loads the OpenAPI document (JSON object, URL, or file path), resolves
$refpointers, and extracts every operation. -
Convert schemas. Each operation’s path parameters, query parameters, header parameters, and request body are converted from JSON Schema into Zod schemas. Strings become
z.string(), integers becomez.number().int(), objects becomez.object()with the correct shape. When a schema is too complex for clean conversion, Smithers falls back toz.any()with a description so the LLM still knows what to provide. -
Build the tool. Each operation becomes an AI SDK
tool()with the converted schema asinputSchema, the operation summary asdescription, and an execute function that assembles the HTTP request. -
Execute at runtime. When an agent calls the tool, the execute function substitutes path parameters into the URL, appends query parameters, sets headers (including authentication), sends the request via
fetch, and returns the response body.
Authentication
Three authentication methods are supported:Filtering Operations
Most APIs have endpoints you do not want an agent calling. Useinclude or exclude to control which operations become tools:
Single Operation
If you only need one tool from a spec, usecreateOpenApiTool:
Observability
Every OpenAPI tool call emits anOpenApiToolCalled event and updates three metrics:
smithers.openapi.tool_calls— counter of total callssmithers.openapi.tool_call_errors— counter of failed callssmithers.openapi.tool_duration_ms— histogram of call durations
Synchronous Loading
Two variants exist. The asynccreateOpenApiTools and createOpenApiTool work with any input — objects, local files, or remote URLs (fetched via fetch). The sync variants createOpenApiToolsSync and createOpenApiToolSync skip the network fetch step, so they only work with spec objects or local file paths:
Operation ID Fallback
If an OpenAPI operation does not have anoperationId, Smithers generates one from the HTTP method and path. For example, GET /pets/{petId} becomes get_pets_petId. The generated ID strips braces and non-alphanumeric characters, joining segments with underscores.
You should still set explicit operationId values in your spec whenever possible — they make tool names readable and stable. The fallback exists so that specs without IDs still produce usable tools.
Loading a Spec via the Effect Layer
For Effect-native code,loadSpecEffect returns an Effect.Effect<OpenApiSpec> so you can compose spec loading with your existing Effect pipeline:
loadSpecEffect resolves URLs via fetch, reads local files synchronously, and parses both JSON and YAML. Pass a spec object and it returns immediately.
Request Body Handling
When an operation has arequestBody with application/json content, Smithers adds a body parameter to the generated Zod schema. The agent fills body as a plain object; the execute function serializes it with JSON.stringify and sends it with Content-Type: application/json.
Required request bodies become required body parameters; optional request bodies become optional.
in: cookie are silently skipped — cookies are not exposed to agents.
Non-JSON Response Handling
If the API returns a response with a non-JSON content type (anything that does not includeapplication/json), the execute function returns the raw response text as a string. The agent receives that string as the tool result and can parse or summarize it as needed.
Error Result Wrapping
When an HTTP call fails (network error, timeout, unexpected exception), the tool does not throw. Instead it returns a structured error object:Schema Composition: allOf, anyOf, oneOf
Smithers converts OpenAPI composition keywords to Zod:| Keyword | Zod equivalent |
|---|---|
allOf with one entry | the single entry schema |
allOf with multiple entries | z.intersection(schemaA, schemaB) chained |
oneOf | z.union([...variants]) |
anyOf | z.union([...variants]) |
$ref references are detected and replaced with z.any() annotated with the circular reference path.
Nullable and Default Values
Two OpenAPI schema properties affect the generated Zod schema:nullable: true— wraps the schema with.nullable()so the agent can providenulldefault: <value>— adds.default(<value>)so missing inputs fall back to the spec default
When to Use OpenAPI Tools
Use them when you have an existing REST API with an OpenAPI spec and you want agents to interact with it. They are particularly good for:- Internal APIs with dozens of endpoints
- Third-party APIs that publish OpenAPI specs
- Rapid prototyping where hand-writing tools is too slow