All articles

Architecture

API Design for AI Agents (and the Humans Who Debug Them)

5 May 202513 min readBy Bayseian Engineering

AI agents are becoming your API's biggest consumer, and they punish sloppy design at machine speed. Idempotency, actionable errors, MCP-aware tool descriptions, and the REST fundamentals that matter more than ever.

Your Newest API Consumer Cannot Read Your Docs Portal

For twenty years, API design meant designing for developers: humans who read documentation, hold context in their heads, and phone support when something breaks. That consumer still matters, but a growing share of production API traffic now comes from AI agents: LLM-driven systems calling your endpoints as tools, deciding autonomously what to call next based on what your API returns.

Agents are a brutally honest audit of your API design. Every convention you skipped, every vague error message, every undocumented side effect: an agent will find it, misinterpret it, and act on the misinterpretation at machine speed.

The good news is that designing for agents is designing for humans, intensified. Every property that makes an API agent-safe (predictability, idempotency, self-describing errors) has always been good practice. The difference is that agents turn "nice to have" into "load-bearing."

  • The response IS the documentation. An agent mid-task doesn't consult your docs portal. It reasons from your schema, field names and error messages. If the response doesn't explain itself, the agent guesses.
  • Retries are the norm, not the exception. Agent loops retry aggressively on ambiguity. Non-idempotent endpoints turn retries into duplicate orders, double payments, repeated emails.
  • Errors are decisions, not dead ends. A human reads "Error 500" and opens a ticket. An agent needs to decide: retry, change parameters, or give up. Your error body determines whether it decides correctly.
  • Tool definitions are the new SDK. Via MCP (Model Context Protocol) and function-calling schemas, your API's description is literally injected into a model's context. Naming and descriptions are now prompt engineering.

Agent-safe design is human-friendly design, intensified.

The Agent-Facing Design Rules

1. Make every write idempotent: accept an idempotency key.

Agents retry. Networks flake mid-call. Without idempotency keys, "create the invoice" running twice creates two invoices, and the agent has no way to know.

POST /invoices
Idempotency-Key: agent-run-7f3a-step-4

Same key + same payload → same result, returned from a dedupe cache. Stripe normalised this pattern for human developers a decade ago; for agent traffic it is non-negotiable.

2. Write error responses an agent can act on.

An agent parsing your error needs three things: what happened, whether to retry, and what to change. Encode all three:

{
"error": {
"type": "validation_error",
"message": "delivery_date must be in the future",
"field": "delivery_date",
"received": "2024-01-15",
"retryable": false,
"suggestion": "Provide an ISO 8601 date later than today."
}
}

Compare that to {"error": "Bad Request"}. A human shrugs and reads the docs; an agent either loops uselessly or hallucinates a fix.

3. Treat tool descriptions as prompt engineering.

  • Name operations by intent (cancel_order) not implementation (update_order_status_v2)
  • State side effects explicitly in the description ("Sends a confirmation email to the customer")
  • Declare consequence: MCP lets you annotate destructive vs read-only operations. Use it, because the calling harness decides whether to ask a human based on those hints
  • Keep parameter counts low and defaults sensible. Every optional parameter is a place for the model to guess wrong

4. Prefer explicit state machines over implicit workflows.

Humans learn "you have to call /submit before /approve" from the docs. Agents discover it by failing. Return the legal next actions with each resource:

{
"id": "order_123",
"status": "draft",
"available_actions": ["submit", "cancel"]
}

This is HATEOAS's old idea, finally with a consumer that genuinely uses it.

5. Rate-limit with feedback, not just refusal.

Agents respect Retry-After headers and structured 429 bodies; they cannot respect what you don't return. Include limit, remaining and reset in every response so well-behaved agent frameworks can pace themselves instead of hammering you.

6. Version aggressively, break nothing.

A human migrates their integration when you email them about a breaking change. Nobody re-prompts a thousand deployed agents. Additive evolution (new optional fields, new endpoints) is safe; renames and semantic changes silently break running systems that reason from field names.

RESTful Foundations (Still Load-Bearing)

Everything above sits on the classic conventions, which matter more with agents, because models have internalised these patterns from training data. An API that follows them gets correct agent behaviour "for free"; an API that fights them fights the model's priors.

  • GET: Retrieve resource(s). Idempotent and safe, so agents can retry freely without side effects.
  • POST: Create a new resource. Not idempotent by default, which is exactly why it needs an idempotency key when an agent might retry it.
  • PUT: Replace the entire resource. Idempotent, since sending the same full representation twice produces the same end state.
  • PATCH: Partial update, usually not idempotent, because "increment by 1" applied twice gives a different result than applied once.
  • DELETE: Remove a resource. Idempotent in principle, since deleting an already-deleted resource should be a no-op, not an error.

Resource Naming Conventions:

  • Good:
  • /users (plural, lowercase)
  • /users/123
  • /users/123/orders
  • /users/123/orders/456
  • /getUser (verb in URL, use GET /users/:id)
  • /user (singular)
  • /Users (capital)
  • /users/getUserOrders (mixed convention)
  • GET /users?role=admin&status=active
  • GET /orders?created_after=2025-01-01&limit=100
  • GET /products?search=laptop&sort=price_asc&page=2

Pagination (Required for lists):

Offset-based (simple, but slow for large offsets):
GET /users?offset=100&limit=50

Cursor-based (faster, consistent, and kinder to agents iterating a full collection):
GET /users?cursor=eyJpZCI6MTIzfQ&limit=50
Response: {
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTczfQ",
"has_more": true
}
}

HTTP Status Codes (use correctly, since agents branch on these):

CodeMeaningWhy it matters
200 OKSuccess (GET, PUT, PATCH)The default success case
201 CreatedResource created (POST)Confirms the write actually happened
204 No ContentSuccess, no body (DELETE)Nothing to return, but still a success
400 Bad RequestClient error, validation failedSay which field, not just that something failed
401 UnauthorizedNot authenticatedThe caller has no valid credentials at all
403 ForbiddenAuthenticated but no permissionDistinct from 401: an agent should stop retrying, not re-authenticate
404 Not FoundResource doesn't existNothing at that path
409 ConflictState conflictCommonly an idempotency key reused with a different payload. Fail loudly, don't silently return the wrong result
429 Too Many RequestsRate limitedAlways include Retry-After, or the caller has no idea how long to back off
500 Internal Server ErrorServer error, retryableThe caller's request was fine; your side broke
503 Service UnavailableTemporarily down, retryable with Retry-AfterDistinguishes "try again shortly" from "something is actually broken"

Security and Performance

Authentication & Authorization:

  • Pros: simple to issue and validate, no extra round trip to check identity.
  • Cons: carries no user context, and a leaked key is full access until someone notices and rotates it. There's no scoping by default.
  • Authorization Code flow: for web apps, where a human authenticates in a browser and the app never sees the user's credentials directly.
  • Client Credentials: for machine-to-machine calls where there's no human in the loop to authenticate.
  • Refresh tokens: let a client maintain long-lived access without holding a long-lived, high-risk access token.
  • Self-contained: the token carries its own claims, so validation needs no database lookup, which matters at high request volume.
  • Short-lived (15-60 min): limits the damage window if a token leaks, since self-contained tokens can't be revoked server-side before they expire.
  • Minimal claims (user_id, roles): every claim in the token is data an attacker gets for free if the token leaks; put lookups behind the API, not in the token.
  • Scope agent credentials tightly. An agent with a full-access key is a prompt injection away from using all of it. Issue per-purpose tokens with minimal scopes and short lifetimes.
  • Never trust agent-supplied content downstream. Treat text an agent passes through your API like any other untrusted user input. The agent may have been fed it by an attacker.
  • Log agent traffic distinguishably (user-agent conventions, dedicated key prefixes) so you can rate-limit, audit and, when needed, kill agent traffic separately from human integrations.
  • Fixed window: 1000 requests/hour, simple to implement, but a client can burst 2x the limit by timing requests around the window boundary.
  • Sliding window: tracks requests over a rolling interval instead of a fixed boundary, closing that burst loophole at the cost of slightly more bookkeeping.
  • Token bucket: allows controlled bursts while capping sustained rate, which suits agent traffic that batches calls rather than spacing them evenly.

Headers to Include (agents pace themselves from these):
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1704067200
Retry-After: 30

  • ETags for conditional requests (304 Not Modified): lets a client confirm nothing changed without re-downloading the full payload.
  • Cache-Control headers set deliberately: an unset or default header means every intermediary guesses at cacheability; be explicit about what can be cached and for how long.
  • Gzip compression (70-90% size reduction): near-free bandwidth savings on any text-based API response.
  • Field filtering (GET /users?fields=id,name): agents rarely need every field, and smaller responses mean fewer tokens burned parsing them.
  • Async for long operations: return 202 Accepted plus a job resource the agent can poll; never leave a connection hanging for a consumer that will time out and retry, turning one slow request into several.

## Conclusion The APIs that win the next decade will be the ones both audiences can use: humans building integrations, and agents executing tasks. That's not two designs. It's one design held to a higher standard. The principles that matter most: - Idempotency everywhere writes happen. Agent retries make duplicates a certainty, not a risk - Errors that carry decisions. Type, retryability and a suggested fix in every error body - Tool descriptions as prompt engineering. Via MCP and function-calling schemas, your naming is now part of a model's context - Explicit state, discoverable actions. Return what's legal next instead of letting consumers fail their way to understanding - Classic REST conventions. Models have internalised them from training data; following them buys correct agent behaviour for free At Bayseian, we design and operate APIs consumed by both human integrations and agentic systems across our client work, including MCP-based integrations that put internal systems safely within reach of AI agents. The pattern is consistent: teams that treat agent-facing design as a first-class requirement ship integrations that work on the first try; teams that don't spend their weeks reading agent transcripts wondering why the model "did something weird." It didn't. It did exactly what the API told it. Ready to make your API agent-ready? Contact us at contact@bayseian.com to discuss your API architecture.

APIRESTAI AgentsMCPArchitecture

Working on something like this?

No pitch, just a practical conversation with the team that builds and operates these systems in production.

Start a conversation