Designing APIs for Agents - Freestyle Blog

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:

twilio.py

client.messages.create(
    body="Join Earth's mightiest heroes. Like Kevin Bacon.",
    from_="+15017122661",
    to="+15558675310",
)

stripe.ts

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.

  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.

  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.

  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.

Putting this into practice

Freestyle builds the most powerful virtual machines in the sandbox space as measured by virtualization quality, scale, configuration, 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. But we got rid of all of that months ago. We removed every complex SDK package and indirection in favor of a guide.

In practice in other APIs and SDKs

🤖 Agent frameworks

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.",
}));
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.",
});
  ---
description: How this team defines revenue. Load before any revenue question.
  ---

Revenue is net of refunds, over the subscription term. Weeks are Monday-anchored, UTC.

📦 Sandbox APIs

import { freestyle } from "freestyle";

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

// Write the wrapper you want. It's all just exec.
const gitClone = (url: string, dir: string, depth = 1) =>
    vm.exec(`git clone --depth ${depth} ${url} ${dir}`);

await gitClone("https://github.com/acme/app", "/app");

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.