Most APIs were designed for two consumers: a human reading documentation and code a human wrote once. An AI agent is neither. It discovers endpoints at runtime from a machine-readable description, fills in arguments by pattern-matching field names, retries automatically when something fails, and chains five calls together without anyone watching each step. APIs that are merely usable by humans are frequently misusable by agents, and the misuse looks like success until it creates a duplicate charge or a deleted record.

The good news: the properties that make an API agent-friendly are the same mature API design practices that help human integrators. This article covers the six that matter most, with concrete request and response shapes.

1. Every mutating request is idempotent

An agent will retry your call. It will retry because a connection dropped, because its context window was compacted mid-task, or because the user said "try again." If POST /charges creates a second charge on retry, the agent will eventually create one.

Accept an idempotency key on every non-GET operation and persist the result:

POST /v1/refunds HTTP/1.1
Idempotency-Key: ord_8821.refund.2026-10-14.01
Content-Type: application/json

{"order_id": "ord_8821", "amount": 4900, "reason": "duplicate_shipment"}

The first request processes normally. A replay with the same key returns the stored response, whether the original succeeded or failed in a known way. Keys should be scoped per authenticated client and expire on a documented horizon (24 hours is a common minimum). Document this explicitly in the operation description; agents that know about idempotency keys will then generate stable ones themselves.

2. Errors are data, not prose

A human reads "Something went wrong, please try again later." and opens Slack. An agent reads it and either retries blindly or invents a fix. RFC 9457 Problem Details gives errors a type, a status, and a stable place for field-level validation detail:

{
  "type": "https://errors.example.com/insufficient-inventory",
  "title": "Insufficient inventory",
  "status": 409,
  "detail": "Requested 20 units of SKU-7; only 3 are reserved for this account.",
  "instance": "/v1/orders",
  "retryable": false,
  "errors": [
    {
      "field": "quantity",
      "code": "above_available_limit",
      "value": 20,
      "max": 3
    }
  ]
}

Three pieces of information decide what an agent does next, and all three belong in the machine-readable body:

  • Is it retryable? A boolean beats inferring from status code ranges.
  • When? For 429 and 503, honor Retry-After seconds rather than guessing.
  • Which argument was wrong? Field-level errors let the agent correct and re-prompt itself; a generic 400 forces it to guess.

The full standard and OpenAPI modeling are covered in REST error responses in 2026: RFC 9457 Problem Details.

3. Long work never blocks a request

Agents are impatient schedulers: if a call takes 45 seconds, something in the stack will time out and retry it. Long-running operations must return immediately with a job handle. The 202 Accepted pattern plus a status endpoint is the most agent-friendly shape:

HTTP/1.1 202 Accepted
Location: /v1/jobs/job_4f2a
Retry-After: 5

{"job_id": "job_4f2a", "status": "queued", "status_url": "/v1/jobs/job_4f2a"}
{ "job_id": "job_4f2a", "status": "succeeded", "result_url": "/v1/reports/rpt_91" }

Terminal states need an exhaustive enum (queued, running, succeeded, failed, canceled) and failed must carry the same structured error format as synchronous calls. For event-driven agents, offer a webhook in addition to polling and document it with the OpenAPI 3.1 webhooks object; then the agent (or its host) can subscribe instead of busy-looping. Testing both delivery styles is covered in documenting webhooks in OpenAPI 3.1.

4. Schemas are strict, explicit, and reuse components

Agents fill forms. They do exactly as well as the form allows:

  • Set "additionalProperties": false on request bodies so a hallucinated field is rejected at the boundary with a clear error instead of being silently ignored.
  • Use enum or const for closed value sets; never encode statuses as undocumented integers.
  • Make optionality honest. A field that is actually required in practice must be marked required; agents treat optional fields as ignorable.
  • Express nullability explicitly with type: ["string", "null"] rather than relying on the old nullable shortcut.
  • Reuse named schemas in components/schemas. The same Order returned by create, get, and list gives the agent one concept to learn instead of three.

When the same schemas back generated MCP tools, strictness compounds: the tool's input schema is the OpenAPI schema, so there is no second description to drift.

5. Pagination and names are boring on purpose

Clever URLs and cursor formats cost nothing for humans (who click links) and a great deal for agents (which construct them). Two rules:

  • Every collection returns a stable pagination envelope with opaque cursors and a clear stop condition, never unbounded arrays. The details are in cursor vs offset pagination: what to put in your OpenAPI spec.
  • Operation behavior follows the method. GET never mutates, DELETE is idempotent, PUT replaces, PATCH updates. Agents infer intent from HTTP semantics; surprising them here causes the worst class of bug, because the call "works."

6. Describe behavior, not just shape

An OpenAPI document that only lists fields leaves the agent to infer rules it cannot see. Descriptions should carry the operational facts that change a decision:

paths:
  /v1/orders:
    post:
      summary: Create an order
      description: >
        Creates a pending order and reserves inventory for 15 minutes.
        Payment must be captured within that window or the reservation
        is released. Safe to retry with the same Idempotency-Key.
      parameters:
        - in: header
          name: Idempotency-Key
          required: true
          schema: { type: string, maxLength: 128 }

Side effects, time windows, rate limits per tier, and ordering guarantees ("events may arrive out of order; sort by sequence") are exactly the context a human gets from a senior engineer and an agent otherwise hallucinates.

The payoff: one description, every consumer

When these six practices are in place, the OpenAPI document becomes a genuinely complete contract for non-human callers. Generate reference docs for people, typed clients for code, and MCP tools for agents from the same source, and every consumer sees the same idempotency rules, error taxonomy, and strict schemas. The agent stops being a special integration problem; it is just another client of a well-designed API.

That is the core idea behind a spec-driven, local-first API workspace: design the contract with AI assistance before code exists, then derive docs, mocks, tests, and MCP tools from it. You can try that flow at the online demo, and the testing side of the same contract is described in scenario testing for REST APIs.