Skip to content
HN On Hacker News ↗

Designing APIs for Agents

▲ 79 points 48 comments by benswerd 1mo ago HN discussion ↗

Pangram verdict · v3.3

We believe that this document is fully human-written

1 %

AI likelihood · overall

Human
100% human-written 0% AI-generated
SEGMENTS · HUMAN 5 of 5
SEGMENTS · AI 0 of 5
WORD COUNT 1,710
PEAK AI % 4% · §1
Analyzed
Jul 15
backend: pangram/v3.3
Segments scanned
5 windows
avg 342 words each
Distribution
100 / 0%
human / AI fraction
Verdict
Human
Pangram v3.3

Article text · 1,710 words · 5 segments analyzed

Human AI-generated
§1 Human · 4%

Designing APIs for agents is different from designing them for humans. Most consumers of APIs today do so through agent-written code. This was not true two years ago, and it changes how we should think about designing them.I no longer believe in most systems built for humans. I'm skeptical of most packages, I'm anti-utility, and I'm pro extremely long names now — all opinions I've done complete 180s on in the past 24 months.Good APIs for humansWhen I design a good API for humans, my first step is sketching the minimal usage patterns, the onboarding, and the top ten use cases. From those, I imagine the code I'd ideally write to get there. Then I try using it, find the edges where I got stuck, and add good defaults and configuration where necessary.I want people to have something functional in fifty lines. From there, the API should be approachable enough that reaching for more functionality is obvious — that's where they'd look. Ideally, someone who needs only one of twenty fields should only become aware of the other nineteen through the autocomplete around the one they want. A good SDK should be self-explanatory enough that you can use it with only minimal skimming of the docs, if that.Some great examples:client.messages.create( body="Join Earth's mightiest heroes. Like Kevin Bacon.", from_="+15017122661", to="+15558675310", )const paymentIntent = await stripe.paymentIntents.create({ amount: 1099, currency: "usd", automatic_payment_methods: { enabled: true, }, });These embody the values above. I implemented both as a fourteen-year-old with no issues, and without having to learn about Stripe's tax options, what ACH is, or any of the fifty other things Stripe does — and without learning about Twilio's scheduling, bots, or anything else. Later on I did. But these SDKs let me make a ton of progress without knowing much about what was actually going on.This is the opposite of how you should design for agentsAI agents can read our entire docs in one sitting. The average Claude Code prompt uses 10k+ tokens; on their first prompt, agents can read your entire API and every relevant document.

§2 Human · 0%

They can also produce thousands of lines of code to reach their goals in seconds. This changes everything.Good APIs for agentsAgents need clarity above everything else — APIs where reading the code tells you exactly what it does. There is a lot more code in the AI-agent era, which makes debugging much harder, and the only way to solve that is through precise definitions of what the code is expected to do.That means:Defaults are bad. Agents can be expected to read the documentation, register what good starting values are, and fill them all in, in place. Explicitness is cheap now, and bringing specificity to expected behavior reduces bugs. The examples above each had thirty fields I didn't fill in because I didn't comprehend them. An agent should fill in every single one.This isn't to say the less important fields shouldn't have a comment between them and the more important ones to separate them — just that the tax on filling out large amounts of seemingly unimportant fields has disappeared, while the tax of understanding what code does has gone way up.Errors are not bad. Many great APIs I've used have smoothed over my stupidity: accepting uppercase for lowercase-only fields and coalescing it, or accepting multiple keys for the same thing the way Postgres booleans accept true, yes, on, 1. This is actively bad for agent codebases. It leads to different functions using different values for the same thing, which causes headaches for later reviewers.Modern harnesses can be expected to read the docs when debugging — bonus points if you tell them to in the error message. Half-correct coalescing should not happen; let the agent explicitly coalesce the values however it wants on its side. For humans, errors in onboarding are bad and you should minimize them. For agents, they are opportunities to clarify what something actually means.This doesn't mean all errors are good. Blank internal errors with no direction make agents spiral, but that's an argument against bad errors, not errors. A good error is an amazing tool: it means the AI had a misconception about your API and solved it. And it will have misconceptions. Even with everything in context, backwards compatibility breaks and confusion happens. Errors that lead to clarity solve this.Errors are one of the best surfaces an agent has for finding the happy path, but only if they're precise.

§3 Human · 0%

Unlike humans, agents follow instructions literally, so a vague or wrong error makes them spiral instead of recover. In our data, 27% of the friction agents hit comes from errors, so great error design is as important as great docs.Mika Sagindykco-founder of 2027.devRead more from MikaIn distribution, not in hallucination. When an API is unclear, agents tend to hallucinate and use it like similar APIs they've seen. For this reason I prefer specific field names to general ones. Take the field name. Some APIs use it for a display name, others for a full ID, others for a scoped ID. If you ask an agent to use an API with name in it across ten different use cases, it will use it in five different ways — and following the point above, only one can be right. Can name have spaces, slashes, dashes, underscores, emoji?This problem compounds as the codebase grows. Reviewers who read this code in the future will interpret name in those same five different ways again. Prefer explicitness: in place of name, I prefer displayName, slug, externalId, or anything you choose that won't carry the same assumptions. These are words an agent can truly understand — not get stumped by, and not misinterpret. Doc comments help cement the correct interpretation too.Facts, not feelings. The value of an API in the era of agents is that it provides a fact the agent or its human team cannot replicate internally. That can be the fact of a bill paid, a message sent, or a virtual machine provisioned (shameless plug for Freestyle VMs). Utilities beyond that are largely irrelevant, and can be replaced with documentation and guides explaining how to get somewhere. In that vein, I'm fairly skeptical of most SDKs for APIs that do more than expose API norms in language-specific patterns — transforming API errors into strongly typed TypeScript errors, for example.

§4 Human · 0%

Putting this into practiceFreestyle builds the most powerful virtual machines in the sandbox space as measured by virtualization quality (we support everything others don't, like Docker-in-Docker, nested virtualization, and advanced networking), scale (we allow 8x larger memory footprints in our public tiers, and much more for enterprise), configuration (we support the full operating system, rootFS, and networking, including private VPCs and VPN connections), stability, and full memory snapshots that actually work ;) — all provisioned in 400ms.This puts us in a weird spot. We work with users solving fundamentally complex problems that are open-ended, hard to debug, and the most difficult in the industry — people come to us for what other sandboxes cannot do. To that end, we try our very best to not do much: just provide the facts I gave above, as consistently as possible.We used to try to hide as much of the complexity as we could. We built a declarative functional build system combined with SDK utilities that let us author packages that came with dependencies. The idea was: "If a user wants Bun, give them the Bun package and let them not worry about the internals, because we made Bun work well." But we didn't. It was never configurable enough, there were always more questions than answers, agents didn't get it and misused it in every possible way, and in the end the complexity of trying to use these abstract layers became the hardest part of onboarding to us. See below how beautiful the code snippet looks — and how little you get about what's going on in it.import { freestyle, VmSpec } from "freestyle"; import { VmBun } from "@freestyle-sh/with-bun";

const { vm } = await freestyle.vms.create({ spec: new VmSpec({ with: { bun: new VmBun() } }), });

await vm.bun.install({ deps: ["zod"], global: true }); await vm.bun.runCode(`import { z } from "zod"; console.log(z.string().parse("hi"))`);This was optimized around my former belief that humans needed to get something working fast and would dive into the details later. But we got rid of all of that months ago. We removed every complex SDK package and indirection in favor of a guide.

§5 Human · 2%

Now when you want anything complex like that, you tell your agent to read the guide, it takes the code relevant to it, adapts it to your project's norms, customizes the behavior however it wants, and has a much cleaner onboarding experience combined with much clearer code at the end.import { freestyle } from "freestyle";

const { vm } = await freestyle.vms.create();

// Just exec — install a package, write a file, run it. No bespoke APIs. await vm.exec("cd /tmp && /opt/bun/bin/bun add zod"); await vm.exec(`echo 'import { z } from "zod"; console.log(z.string().parse("hi"))' > /tmp/main.ts`); const { stdout } = await vm.exec("cd /tmp && /opt/bun/bin/bun run main.ts");In practice in other APIs and SDKs🤖 Agent frameworks🏆 Excellent — Flue Framework. It provides minimal semantics for the core things agents need. Everything is a pluggable function, primarily defined through its interactions with other parts of the agent. Flue is completely clear about what it does — and it actually doesn't do much: it provides shared semantics for building harnesses.import { defineAgent } from "@flue/runtime";

// The whole agent is one function returning config — every part is a value. export default defineAgent(() => ({ model: "anthropic/claude-sonnet-4-6", instructions: "Triage the incoming GitHub issue.", }));👍 Good — Vercel AI SDK. Shared SDK semantics around connecting to agents, and very useful. generateText and streamText are great, clear functions that let agents treat complex interactions with many different inference APIs as facts.import { generateText } from "ai"; import { openai } from "@ai-sdk/openai";

// One call in, one fact out. const { text } = await generateText({ model: openai("gpt-5"), prompt: "Summarize this transcript.", });😬 Medium — Mastra. Mastra has a lot of good scheduling, and workflows as well; most of their agent is built from pluggable types you can implement yourself. That said, I think it's heavy-handed and unclear in many places.