Stop opening dashboards.
Ask your agent.
Code mode for all your APIsInstall your APIs once, for every agent. Faster and cheaper than MCP tool calls, and the same answer every time.
Paste into Claude Code, Codex or any agent with a shell. Credentials stay in your OS keychain.
Let your agent set it up.
Paste one line into Claude Code, Codex or Cursor. Your agent runs the steps; you only finish the browser sign-ins.
Agents can read this page as markdown or start from llms.txt.
What's inside llms.txt
# Wunder > Code mode for all your APIs. Wunder is a CLI that installs OpenAPI, GraphQL and MCP APIs like packages; the agent searches for the few operations a task needs and writes one script instead of a chain of tool calls. Credentials stay in the OS keychain. Wunder works with any agent that can run a shell command: Claude Code, Codex, Cursor and others. Packages are immutable org/name versions pinned in a lockfile. Installing a schema never calls the API. ## Agent quickstart ``` Set up Wunder in this project. 1. npm i -g wunder # check: `wunder version` prints a version 2. export WUNDER_REGISTRY=https://getwunder.dev # points the CLI at this registry; keep it set for the steps below 3. wunder auth login # opens a browser; ask the user to finish sign-in (--no-browser prints the URL) 4. wunder init # creates wunder.json and AGENTS.md in the current directory 5. wunder capability search "<what you need>" # suggests catalogue APIs with their install line; exit 4 with suggestions is expected here, continue 6. wunder install <org/api> # pins the schema in the lockfile; never calls the API 7. wunder credentials login <org/api> # the user approves access; never paste tokens into chat 8. wunder script new "<task>" # writes, checks and runs a saved script; quick questions: wunder run -e '…' Run each step and stop on the first failure. Add --json to any command for one structured result; JSON mode never asks interactive questions. Exit code 2 means sign-in is required: run wunder auth login. Do not work around sign-in or consent. ``` ## Pages - [Overview](https://getwunder.dev/index.md): what Wunder is, how it works, scripts, models, security, registry and FAQ - [Explore APIs](https://getwunder.dev/explore): search the API catalogue (sign-in required); agents should use `wunder capability search` instead
One script, not a chain of tool calls.
When every API is a set of tools, every tool definition sits in the context window and every call goes through the model, one at a time, each result read back in. With Wunder the agent searches for the few operations the task needs and writes one script. The tools aren't the problem. Calling them one by one through the model is.
Tool calls through the model
215 s · $1.99Every tool definition in context. Every result read back in. Gone when the chat ends.
One script with Wunder
123 s · $0.64The agent sees only the operations it needs. The plumbing runs in Wunder, not in the model.
Install, search, run, rerun.
- 1install
Add the APIs
Like npm dependencies, pinned in a lockfile. Installing never calls the API.
wunder install github/graphql - 2search
Find the operations
Plain words in, a typed call out. The agent sees only what the task needs.
wunder capability search "open issues" - 3run
Get the answer
Quick question: one command. Bigger task: Wunder writes, checks and runs a saved script.
wunder run -e '…'wunder script new "…" - 4rerun
Run it again for cents
The script lives in your repo. Reruns skip search and script writing and reuse cached model answers.
wunder run scripts/customers-hit.js
Short scripts, no plumbing.
What Claude Code or Codex writes after a search. Wunder fills in the plumbing at run time.
Join data
GitHub issues joined with Pylon support tickets. Two APIs, two formats, one import.
import { openIssues, searchTickets } from "wunder"; // Which customer accounts mention each open issue? export default async () => { const issues = await openIssues.all({ owner: "acme", name: "api", states: ["OPEN"] }); return Promise.all(issues.map(async (issue) => { const tickets = await searchTickets.all({ search_text: issue.title }, { max: 5 }); return { issue: issue.title, accounts: tickets.map((t) => t.account?.id) }; })); };
Classify
Labels from the decisions model, with a confidence per item. Cached, so reruns don't pay again.
import { openIssues } from "wunder"; import { decisions } from "wunder:model"; // Label every open issue as bug, feature request or question. export default async () => { const issues = await openIssues.all({ owner: "acme", name: "api", states: ["OPEN"] }); return Promise.all(issues.map(async (issue) => { const label = await decisions.choice({ title: issue.title }, "Classify this issue", { bug: "a defect", feature: "a feature request", question: "a question" }); return { issue: issue.title, label: label.choice, confidence: label.confidence }; })); };
Web search
Live web results next to your own data, with a budget on web calls.
import { openIssues } from "wunder"; import { web } from "wunder:web"; // Find public reports of each open issue from the last month. export default async () => { const issues = await openIssues.all({ owner: "acme", name: "api", states: ["OPEN"] }); return Promise.all(issues.map(async (issue) => { const found = await web.search(issue.title, { count: 3, freshness: "month" }); return { issue: issue.title, links: found.results.map((r) => r.url) }; })); };
LLM output
Typed text from any model you configure, validated against a schema.
import { closedIssues } from "wunder"; import { llm } from "wunder:model"; import { z } from "wunder:zod"; // Turn last week's closed issues into release-note lines. export default async () => { const issues = await closedIssues.all({ owner: "acme", name: "api" }, { max: 50 }); const notes = await llm.json({ prompt: "One release-note line per issue:\n" + issues.map((i) => i.title).join("\n"), schema: z.object({ lines: z.array(z.string()) }), }); return notes.lines; };
One script. Any model, any search engine.
An agent on its own gives you its provider's models and built-in tools. A Wunder script can pick the right one for each step, from different providers, in the same run.
- Reason and writeGemini 3.8 Flash, Gemini 3.1 Pro, Claude Opus 5.5llm.text, llm.json
- Cheap bulk workGemini 3.1 Flash-Litellm.json
- Classify and decideTypeSafe Jevdecisions.choice, yesNo
- EmbedOpenAI text-embedding-3-smallembeddings
- Search the webPerplexity, Firecrawl, Gemini grounded searchweb.search
- Read pagesFirecrawlweb.fetch
Through the optional Wunder AI gateway, or point any of them at your own keys. The broker adds the key; the script never sees it.
import { web } from "wunder:web"; import { llm, decisions } from "wunder:model"; // Three providers, one run. Keys stay with the broker. export default async () => { // Perplexity finds the pages const { results } = await web.search( "open-source agent sandboxes", { endpoint: "search-perplexity", count: 10 }, ); // TypeSafe Jev, the default decisions model, sorts them const projects = []; for (const page of results) { const kind = await decisions.choice(page, "What is this page?", { project: "An open-source project", other: "Anything else", }); if (kind.choice === "project") projects.push(page); } // Gemini Pro writes the comparison return llm.text({ model: "google/gemini-3.1-pro-preview", prompt: "Compare these projects in five bullets.", input: projects, }); };
Same agent. Different boundaries.
Switch between the two setups. The boxes stay where they are, so you can see where the boundaries move: what code can touch, where keys live, what the model sees and who approves writes.
- What code can touch
Everything you can. MCP servers and CLIs like gh run as ordinary processes with your files, network and environment.
- Where keys live
Config files. A token per tool, often in plain text or env vars. Remote servers hold their own.
- What the model sees
Everything. Every tool definition and every response is read into context, and grows with each call.
- Who approves writes
Each harness. Safety rests on each harness's own permission system and each tool's behavior.
- Switching agents
Set it up again. One config per harness and per tool, each with its own copy of your tokens.
- What code can touch
Only the broker. The sandbox has no network, files or environment. Every call goes through the broker.
- Where keys live
The OS keychain. Only the broker reads them, at the last step, and redacts them from responses and logs.
- What the model sees
What it needs. Short guidance, search results, its script and what the script returns. Raw API responses stay in the script.
- Who approves writes
You, in one place. The broker checks every write against the approvals you granted, whatever the harness. Test runs can't write.
- Switching agents
Nothing to redo. Any agent with a shell uses the same project, keychain and approvals.
The plumbing lives in the runtime.
Paging and retries
.all()pages through any list. Retries, backoff and pacing run in the runtime, not in the script.Resumable scripts
API and model calls are journaled. A rerun after an error doesn't redo finished work, and a write ledger stops duplicates.
WUNDER.md
The agent records what it learned, such as rate limits and working scripts, and reuses it next run.
Views
wunder serveturns a saved script's results into a local page you can reopen.
Versioned API packages, pinned like dependencies.
Every package is an immutable org/name version with a hash in your lockfile. When an API ships a new schema, wunder capability check tells you what would break before you upgrade. Search finds the right operation first for 446 of 476 test prompts.
Publish your own API
wunder init --package --name acme/api \ --format openapi --schema openapi.yaml wunder publish
Wunder computes the semver bump from the schema diff and refuses to publish a breaking change as a patch.
Common questions
Is Wunder against MCP?
No. MCP is how an API describes itself, and Wunder installs MCP schemas next to OpenAPI and GraphQL. What we replace is tools called one at a time through the model.
Does the agent or the script see my API tokens?
No. Tokens live in your OS keychain and are injected at the boundary when a request leaves the sandbox. A credential is only ever sent to its configured destination; anything else is blocked.
Which agents and models does it work with?
Any agent that can run a shell command: Claude Code, Codex, Cursor and others. Your agent keeps its own model. Switching agents or models never touches your data access, keys or saved scripts.
What if my API isn't in the registry?
If it has an OpenAPI 3.x or Swagger 2.0 document, a GraphQL schema or an MCP server, publish it yourself in two commands. There are no provider-specific special cases.
Try it in one paste.
Run it in any folder. Two browser sign-ins, then your agent lists the issues assigned to you.
export WUNDER_REGISTRY=https://getwunder.dev \ && npm i -g wunder && wunder auth login && wunder init \ && wunder install github/graphql \ && wunder credentials login github/graphql \ && claude "Summarize the open GitHub issues assigned to me"
- Installs the CLI, prebuilt binary for macOS, Linux and Windows
- Signs you in to Wunder, once, in the browser
- Sets up the project, adds guidance for your agent
- Adds the GitHub API, installing never calls the API
- Logs in to GitHub, the token goes to your OS keychain
- Asks the question, works for any GitHub account; use codex or any agent with a shell