Back Original

Designing APIs for Agents

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 humans

When 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 agents

AI 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. They can also produce thousands of lines of code to reach their goals in seconds. This changes everything.

Good APIs for agents

Agents 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:

  1. 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.

  2. 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. 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 SagindykMika Sagindykco-founder of 2027.devRead more from Mika
  3. In 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.

  4. 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.

Putting this into practice

Freestyle 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. 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();


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

📦 Sandbox APIs

Closing

Building for agents is finally diverging from building for human engineers. Many core principles will carry over: good documentation matters, following usage patterns and making clear errors still matter. But a lot of what used to matter doesn't anymore. Lines of code, errors in onboarding, required reading to become productive, even tokens required for onboarding are all fairly irrelevant to how good an API is for agents. Everything in here wouldn't have been correct with GPT-4.5, so it might not be relevant for GPT-6, but I'm looking forward to seeing where this goes.