Your APIs have three kinds of consumers now: human developers who build with them, application code that calls them at runtime, and AI agents that invoke them autonomously during task execution. The first two are well-understood. The third one will break your API in ways you didn’t design for.

This isn’t theoretical. In the past year, the systems I’ve seen fail in agent-integrated production environments share the same root cause: APIs that were designed assuming a human developer was somewhere in the request path. When the human exits and the agent takes over, assumptions break. Some of those breaks are inconvenient. Some are expensive. A few are catastrophic.

Here’s what actually matters when you’re designing or retrofitting an API to be agent-ready.

The Fundamental Difference: Agents Don’t Have Judgment

A human developer calling your API brings implicit knowledge that your API specification doesn’t document. They know that a create_order call probably shouldn’t be retried if they’re not sure whether the first one succeeded. They know that a 403 Forbidden on a bulk delete endpoint probably means they should stop, not retry with different parameters. They know that when the API docs say “idempotent,” it means something specific.

An AI agent has none of this. It has the spec, the last response body, and whatever context it was given at the start of the task. It will retry a non-idempotent endpoint on timeout because the task says the order needs to be created, and the order isn’t created yet. It will interpret a 403 based on whatever error handling logic it has, which may not match your intent. It will make a decision about what to do next based on your response payload, and if that payload doesn’t give it enough information, it will guess.

This isn’t a failure of AI capability — it’s a design gap. APIs designed for human developers offload judgment to the human. APIs designed for AI agents need to encode that judgment in the API itself.

Pattern 1: Mandatory Idempotency Keys

This is the most important pattern and the one most APIs get wrong by not having it at all.

An agent working on a task will encounter timeouts, network errors, and ambiguous failures. When a request times out, the agent has two options: assume it succeeded (risk: no-op on a real operation) or retry (risk: duplicate on a side-effecting operation). Without idempotency keys, there’s no safe choice.

Idempotency keys give agents a third option: retry safely, because the server will detect the duplicate and return the same result as the first successful execution.

POST /api/payments
Idempotency-Key: task-{task_id}-payment-{attempt_id}

{
  "amount": 4999,
  "currency": "USD",
  "customer_id": "cust_abc123"
}

Implementation requirements:

  • Accept idempotency key as a header (not a body field — it needs to be processable before the body)
  • Store request fingerprint (method + path + key) and response for at least 24 hours
  • On duplicate key: if request body matches, return original response; if body differs, return 422 with explanation
  • Make the key scope explicit: per-customer, per-task, or global

The key format matters for agents. A key like task-{task_id}-step-{step_id}-{operation} lets the agent construct predictable, unique keys without maintaining external state. Agents that have to generate random UUIDs and remember them are harder to build and harder to debug when things go wrong.

Pattern 2: Machine-Readable Error Responses

Human-readable error messages are for engineers reading logs. Agent-consumable error responses are for systems that need to decide what to do next without asking a human.

The difference is structural:

Human-readable (not agent-friendly):

{
  "error": "Payment failed because the card was declined by the issuer."
}

Agent-ready:

{
  "error": {
    "code": "CARD_DECLINED",
    "category": "terminal",
    "retry_safe": false,
    "human_message": "Payment failed because the card was declined by the issuer.",
    "suggested_action": "REQUEST_NEW_PAYMENT_METHOD",
    "context": {
      "decline_code": "insufficient_funds",
      "can_retry_with_different_amount": false
    }
  }
}

The fields that matter for agents:

  • code: stable machine-readable identifier, not a string for display
  • category: transient (retry may work) | permanent (retry won’t help) | authorization (agent needs elevated permissions)
  • retry_safe: explicit boolean — don’t make the agent infer this from the error code
  • suggested_action: what the agent should do next, in terms it can act on

This design also benefits human debugging. Machine-readable errors are better for logs, alerting, and dashboards than free-form strings.

Pattern 3: Context Anchors in Responses

Agents work in sessions. A session might involve dozens of API calls across multiple services, with the agent building a model of the world based on what it’s learned. When a response gives the agent a reference it can use to anchor future operations, you reduce the chance that the agent drifts or invents state it doesn’t have.

Concretely: every create or update operation should return a full resource representation, not just an ID.

Weak response (forces agent to make another call):

{
  "order_id": "ord_xyz789",
  "status": "created"
}

Context-anchored response:

{
  "order_id": "ord_xyz789",
  "status": "pending_payment",
  "total_amount": 4999,
  "items": [...],
  "next_steps": ["complete_payment", "cancel_order"],
  "payment_url": "https://checkout.example.com/ord_xyz789",
  "_links": {
    "self": "/api/orders/ord_xyz789",
    "payment": "/api/orders/ord_xyz789/payment",
    "cancel": "/api/orders/ord_xyz789/cancel"
  }
}

The next_steps array is particularly valuable for agents: it explicitly enumerates what the agent is allowed to do from this state, rather than making the agent reason about it from the response payload.

HAL and JSON:API both formalize this pattern under HATEOAS. In practice, you don’t need full HATEOAS compliance — you need _links with the operations that matter and next_steps with human-readable action labels.

Pattern 4: Rate Limits With Budget Visibility

HTTP rate limit headers (X-RateLimit-Remaining, X-RateLimit-Reset) exist, but most APIs implement them in ways that make agent rate limit management difficult.

The problem: an agent working on a complex task might make hundreds of calls. Without visibility into its current rate limit budget, it discovers the limit by hitting it — at which point it either has to wait or abort the task. If the task is time-sensitive, this is expensive.

Better approach:

HTTP/1.1 200 OK
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1722844800
X-RateLimit-Window: 3600
X-Agent-Budget-Suggested-Pace: 14/min

The X-Agent-Budget-Suggested-Pace header tells the agent what call rate will keep it within budget for the reset window. This lets a well-designed agent throttle itself proactively rather than hitting limits reactively.

For APIs used in agentic workflows, also consider: endpoint-level rate limits instead of global limits. An agent doing 200 reads and 10 writes shouldn’t hit a global limit designed to prevent write abuse.

Pattern 5: Pagination That Agents Can Drive

Pagination is where many API designs quietly fail agents. Cursor-based pagination is fine. Offset pagination is fine. The problem is pagination that requires the agent to remember state between pages.

If an agent is paginating through 10,000 results and a network error occurs at page 47, what happens? If the cursor is opaque and stateless (encodes everything needed to resume), the agent can retry from the last successful cursor. If the cursor is server-side session state that expires, the agent has to restart from page 1.

Requirements for agent-safe pagination:

  • Cursors should be self-contained (base64 encoded offset or timestamp, not a server session key)
  • Include total_count if feasible — agents planning tasks benefit from knowing the scope
  • Include has_more as an explicit boolean, not derived from whether the current page is full
  • Return the cursor in the response, not as a response header that agents might miss

Pattern 6: Align With MCP From the Start

The Model Context Protocol (MCP) is rapidly becoming the standard interface layer between AI agents and external tools. If your API is likely to be consumed by AI agents — and in 2026, most APIs are — designing for MCP compatibility from the start saves significant retrofit work later.

MCP wraps your API as a set of “tools” that agents can discover and call. Each tool has a name, a description, and a typed input schema. The agent calls the tool by name with typed arguments, and the tool returns a result the agent can reason about.

What this means for API design:

  • Verb-noun naming for operations: create_order, get_order_status, cancel_order — not REST-style POST /orders, GET /orders/{id}, DELETE /orders/{id}. Agents reason about tool names; REST paths are incidental.
  • Typed input schemas: JSON Schema for every operation, with descriptions on every field. The agent uses these descriptions to decide which tool to call and how to fill in the arguments.
  • Single-purpose operations: Avoid “smart” endpoints that do different things based on which fields you send. Agents do better with explicit operations that have clear, bounded behavior.
  • Result schemas: Return typed, structured results — not polymorphic responses where the shape depends on a flag in the request.

You don’t have to implement MCP natively. An MCP wrapper layer can translate between MCP tool calls and your existing REST or GraphQL API. But if your API has naming conventions, schema design, and error handling designed for MCP compatibility, the wrapper becomes nearly automatic.

The Practical Test

Before you ship an API that agents will consume, run this test: give an agent that has never seen your API its MCP description (or just the OpenAPI spec), ask it to complete a realistic multi-step task, and watch what breaks.

What breaks is your API design. Not the agent’s capability — the design choices you made assuming a human would fill in the gaps.

The checklist:

  • Every write operation has an idempotency key
  • Error responses have machine-readable codes and retry_safe booleans
  • Create/update responses return full resource representations with next_steps
  • Rate limit headers include forward-looking budget information
  • Pagination uses stateless, self-contained cursors
  • Operations have verb-noun names with typed input/output schemas

None of these changes are expensive. Most of them improve the human developer experience too — machine-readable errors, full resource responses, and stateless cursors are good API design regardless of whether agents are in the picture.

The teams that build agent-ready APIs now will have a significant advantage when autonomous agents start making the majority of API calls to their systems — which, based on current growth rates, is probably closer than most teams think.


Thuận Lương is a Technical Lead with 15+ years of experience in .NET, cloud architecture, and AI systems. He writes about real-world lessons from building production systems.

Export for reading

Comments