| Internet-Draft | Agent-Friendly HTTP API Profile | June 2026 |
| Gaikwad | Expires 11 December 2026 | [Page] |
AI agents are a fast-growing kind of HTTP API client. A human developer reads an API's documentation once and then writes code that calls it. An AI agent works from the machine-readable description each time it plans a step. It works within a limited amount of text it can hold at once. It retries often. It picks operations by matching their descriptions to a goal. An API built mainly for human developers can lead an agent into failures that are easy to avoid, such as repeating a write, running out of room for the task, or getting stuck on an error it cannot recover from.¶
This document lists the properties that make an HTTP API easy for AI agents to use, including APIs reached through tool-calling layers such as the Model Context Protocol. It gathers these properties into a profile of existing HTTP and API-description mechanisms, with a checklist at the end. It covers the API and its description. It does not define new ways to identify, authenticate, or authorize an agent. That work is happening elsewhere and is referenced here for context.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 11 December 2026.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
Autonomous software agents built on large language models are starting to call HTTP APIs. Sometimes the agent forms the HTTP request itself. More often it goes through a tool-calling layer that presents each API operation as a callable "tool". The Model Context Protocol [MCP] is one common example of such a layer. Many of its servers are generated automatically from an existing API description such as an OpenAPI document [OPENAPI].¶
One property matters most. The description of the API, and the data the API returns, both become input to the agent's own reasoning. Several single points in this document are also ordinary good API practice: stable operation names, bounded pages, structured errors, idempotency. This document explains why each one matters more when the client is an agent. It adds the guidance that is specific to agents. It collects the result as a profile that a provider can apply and check against. This document does not assume that an agent is reliable or well-behaved. It treats the agent as a client whose behavior is shaped by the shape of the API.¶
There are two layers to keep separate. The API layer is the HTTP API and its machine-readable description. The tool layer is the surface that a tool-calling protocol or generator builds from that description. This document is about the API layer. A good API layer is the most reusable way to get a good tool layer. The tool layer is increasingly handled by its own protocols.¶
The advice here is written for resource-oriented HTTP APIs, where each operation acts on an identified resource. Most of it also applies to query-based APIs such as GraphQL and to remote-procedure-call styles such as gRPC. A few points change. A query-based API lets the client ask for exactly the fields it needs, which handles the over-fetching concern in Section 8 on its own. In return it adds query-cost and query-depth concerns that an agent-facing deployment SHOULD limit. A reader using this document with another style should map each point to the matching idea in that style.¶
This document is informational. It lists considerations, gives non-normative recommendations, and assembles a profile and a checklist. It does not define a protocol or a wire format. These topics are out of scope:¶
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here. This document is informational. These words give guidance to API authors. They are not protocol requirements.¶
The advice below follows from a few facts about how an agent uses an API. Stating them shows where the advice applies.¶
These facts apply most directly when operation descriptions, schemas, error messages, and response bodies reach a language model or a model-guided planner. When something in between filters, checks, or shortens the data before the model sees it, the space and selection effects shrink but do not disappear. The retry and side-effect effects usually remain.¶
This is the main idea of the document. An agent-facing API SHOULD give the agent machine-readable signals it can act on. It SHOULD avoid relying on prose that the agent has to read and interpret. A sentence such as "call this carefully" or "try again later" has to be understood by the model, and the model follows such sentences unevenly. The same intent stated as a field or a flag can be acted on directly. This swap shows up throughout the profile:¶
retryable flag the agent can read (Section 9);¶
When an API is reached through a generated tool layer, its machine-readable description is the single source the tools are built from. Anything missing from the description is missing from the tool. So treat the description as a main deliverable. Do not treat it as documentation written after the code.¶
operationId. Identifiers generated from the URL path
produce tool names the agent picks badly.¶
additionalProperties to false on objects, or use the equivalent,
so the agent cannot add invented parameters. Include a real example.¶
An agent learns patterns and reuses them. Anything inconsistent across
the API makes it guess wrong. Keep naming, identifier formats, pagination,
error shape, and authentication the same across the whole API. For example,
a status value that is pending on one operation, IN_PROGRESS
on another, and done on a third will cause errors. When an
operation only works in certain states of a resource, document the states
and the moves between them. Then the agent can work out which operations are
valid without guessing. Keep enumerated values stable. The agent may have
seen and remembered them in an earlier call.¶
A complete resource-oriented API does not become a good tool surface on its own. Turning every endpoint into its own tool gives the agent a surface that is large, low-level, and hard to work through.¶
A response uses up the agent's space budget on the turn it arrives, and on every later turn it stays in view. Asking for too much costs more each turn. A response that is too large is also a denial-of-service and cost problem (see Section 17).¶
next_page link, so
the agent does not have to compute offsets or build URLs. It does both of
those unreliably.¶
An error response is not the end of the story. It is the input to the agent's next decision. An error that is unstructured or unclear leads the agent to retry something that cannot work, or to give up on something a retry would have fixed.¶
Use a structured, machine-readable error format such as Problem Details for HTTP APIs [RFC9457], which is a standard JSON shape for errors. Include a stable error code that is separate from the HTTP status code.¶
Problem Details lets you add your own fields. For an agent-facing API,
say plainly whether a failed call can be retried, using a field. Do not
leave the agent to read this off the status code. Report every validation
problem at once, with the field name for each one. A small, consistent set
of fields across the API does the job: a boolean retryable, an
optional retry-delay hint, and a list of field errors. With these the agent
can decide to retry, to change the request, or to stop, without guessing.
The field names here are only examples. This document does not register
them. That would be the job of a later specification (see Section 19). Here is a retryable rate-limit error:¶
A validation error sets retryable to false and lists the fields
that were wrong. That tells the agent to change the request before it sends
it again:¶
Agents retry after timeouts, after unclear results, and as their plan changes. So safe writing for agents covers more than one idea. It covers repeating a write safely, previewing it, confirming it, cancelling it, and undoing it. As a rule, prefer writes that can be previewed, cancelled, and reversed over writes that take effect at once and cannot be undone.¶
X-Dry-Run:
true) or a query parameter (for example, ?dry_run=true)
works well. The response lists the predicted changes. Appendix D shows this.¶
Agent traffic can come in bursts. It often lacks the natural pacing of a person at a keyboard. Signal clearly when the client should slow down, so a well-behaved agent can hold back on its own.¶
Retry-After header
field. A client should follow it ahead of its own backoff timer.¶
An operation that takes more than a few seconds does not fit a single blocking request. The connection may not stay open. An agent that waits for it cannot do anything else in the meantime.¶
The agent's behavior comes from the API description. So a change to the description is a change to the agent's tools. Manage change carefully.¶
Deprecation and Sunset header fields and a link to the
replacement. Show it in the API description too. Release notes on their own
do not reach the agent.¶
An agent does best with one machine-readable, low-noise place to learn what the API can do. A complete API description [OPENAPI] is the main artifact for this. Some providers also publish a short, plain-text index of their documentation aimed at models. The llms.txt convention [LLMSTXT] is one community example, and it is not a standard. Generate any such file from the same source as the human documentation, so the two do not drift apart. Keep it focused on parameters, the meaning of errors, and authentication.¶
After an agent runs, an operator often needs to work out what it did. To
let the agent's steps line up with the API's own logs, accept and pass along
a standard correlation identifier. The W3C Trace Context traceparent
header field [W3C-TRACE-CONTEXT] is one such identifier, and
a generic correlation header is another. Record it in the API's logs next to
the identity that acted. This helps with debugging, with working out cost,
and with the audit trail in Section 17.¶
This document does not say how an agent authenticates to an API, or how a user grants it authority. A single static credential that covers the whole API is a poor fit for an agent. If it leaks, it reaches everything. The direction the field is taking is to issue narrow, short-lived credentials that can be revoked, and to record delegation openly. The user the agent acts for is recorded separately from the agent. That keeps actions traceable.¶
Authors building for agents should follow the relevant work, including OAuth 2.0 Token Exchange [RFC8693], protected resource metadata [RFC9728], resource indicators [RFC8707], dynamic client registration [RFC7591], on-behalf-of authorization for agents [I-D.oauth-ai-agents-obo], and the wider analysis in [I-D.klrc-aiagent-auth].¶
Building an API for agents changes its security picture. The changes connect to the points above.¶
Injected instructions in text. The agent treats the text it receives as input to its reasoning. So both operation descriptions and response bodies can carry instructions that an attacker planted. This is called indirect prompt injection. Keep trusted control fields apart from untrusted content. Mark natural-language content that came from users or third parties as data, not as instruction. One way is to put it in its own named field and keep a note of where it came from. Do not put externally supplied text into operation descriptions or API documentation unless it is clearly marked as untrusted. Enforce access decisions at the API. Do not rely on instructions in the agent's prompt, because injected text can override them.¶
Running out of room and money. An API can return a payload far larger than expected, and so can a downstream system that has been compromised. A huge payload can fill the agent's space budget and make the task fail. Where use is billed, it can also run up a large, unexpected cost. People often treat the limits on response size and the cursor-based pagination in Section 8 as performance tuning. For an agent they are also a security control. Enforce them on the server. Do not count on the client to ask for less.¶
Unsafe retries. The idempotency, preview, and undo guidance in Section 10 is also a security matter. Agents retry more than clients driven by a person, and an unsafe retry can repeat an effect that changes state. Apply least privilege to operations that have real side effects. Keep an audit log that records who acted and under what delegation. Consider requiring a confirmation step for high-risk actions.¶
Wider reach when access is broad. The discovery artifacts in Section 14 make the API easier for any automated client to read, including a hostile one. They do not replace authorization or rate limiting. Authorization is out of scope here. Still, broad access together with easy discovery widens how much a tricked or compromised agent can reach. Scope access narrowly to the task. That limits the damage an injected instruction can do.¶
An HTTP API meant for agents can be called agent-friendly, in the sense of this document, when:¶
This document has no IANA actions. It does not define any protocol element, header field, media type, or registry entry. The field names used in the error examples in Section 9 are only examples. If such Problem Details fields are ever standardized, registering them would be the job of that later specification.¶
This appendix applies the profile to a Model Context Protocol [MCP] server for a project management system. The system has
projects, tasks, and assignees. Each tool uses a strict JSON Schema, so the
model can find and call it without inventing parameters. Every input schema
sets additionalProperties to false, uses a fixed value set for
closed choices, marks the required fields, and bounds the size of any
collection.¶
An agent is often told a project by name, not by identifier. A lookup tool turns the name into an identifier the other tools need. This follows the rule in Section 7 not to demand an identifier the agent cannot get:¶
{
"name": "find_projects",
"description": "Find projects by name.",
"inputSchema": {
"type": "object",
"additionalProperties": false,
"required": ["name_contains"],
"properties": {
"name_contains": { "type": "string" },
"limit": {
"type": "integer",
"minimum": 1, "maximum": 20, "default": 10
}
}
}
}
¶
A creation tool keeps a strict input schema. Its description, kept short
in the example, should also say that the operation notifies the assignee,
point the agent to find_projects when it has only a name, and
explain that reusing the idempotency_key returns the original task
instead of making a duplicate:¶
{
"name": "create_task",
"description": "Create a task in a project.",
"inputSchema": {
"type": "object",
"additionalProperties": false,
"required": ["project_id", "title"],
"properties": {
"project_id": { "type": "string" },
"title": { "type": "string", "maxLength": 200 },
"assignee_email": { "type": "string", "format": "email" },
"priority": {
"type": "string",
"enum": ["low", "medium", "high"],
"default": "medium"
},
"idempotency_key": { "type": "string" }
}
}
}
¶
A search tool bounds its result size and returns an opaque cursor, so the
agent never computes an offset. The status filter lets it narrow
the results before it fetches them:¶
{
"name": "find_tasks",
"description": "Search tasks in a project (bounded page).",
"inputSchema": {
"type": "object",
"additionalProperties": false,
"required": ["project_id"],
"properties": {
"project_id": { "type": "string" },
"status": {
"type": "string",
"enum": ["open", "in_progress", "done"]
},
"limit": {
"type": "integer",
"minimum": 1, "maximum": 50, "default": 20
},
"cursor": { "type": "string" }
}
}
}
¶
A result is bounded. It carries human-readable labels and a next action, and it gives back a cursor for the following page:¶
{
"tasks": [
{
"task_id": "t_8a1",
"title": "Draft launch checklist",
"status": "in_progress",
"assignee": { "id": "u_42", "name": "Priya Rao" },
"next_actions": { "complete": "update_task_status" }
}
],
"next_cursor": "eyJvIjoyMH0"
}
¶
To read the next page, the agent calls the same tool again and passes the cursor straight back. It does no arithmetic:¶
{
"name": "find_tasks",
"arguments": {
"project_id": "p1",
"status": "open",
"cursor": "eyJvIjoyMH0"
}
}
¶
A batch tool creates several tasks in one call. It bounds the array size, and its result reports one outcome per item, so the agent can see which tasks were created and which were rejected:¶
{
"name": "create_tasks_batch",
"description": "Create several tasks in one project.",
"inputSchema": {
"type": "object",
"additionalProperties": false,
"required": ["project_id", "tasks"],
"properties": {
"project_id": { "type": "string" },
"idempotency_key": { "type": "string" },
"tasks": {
"type": "array",
"maxItems": 50,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["title"],
"properties": {
"title": { "type": "string", "maxLength": 200 },
"priority": {
"type": "string",
"enum": ["low", "medium", "high"]
}
}
}
}
}
}
}
¶
The batch result shows the per-item outcomes. One task was created and one was rejected, and the agent can tell exactly which is which:¶
{
"results": [
{ "index": 0, "status": "created", "task_id": "t_901" },
{ "index": 1, "status": "rejected", "code": "TITLE_TOO_LONG" }
],
"created_count": 1,
"rejected_count": 1
}
¶
A state-changing tool uses a fixed value set for the closed list of states and accepts an idempotency key. Its description should note the side effect of notifying the assignee:¶
{
"name": "update_task_status",
"description": "Move a task to a new workflow state.",
"inputSchema": {
"type": "object",
"additionalProperties": false,
"required": ["task_id", "status"],
"properties": {
"task_id": { "type": "string" },
"status": {
"type": "string",
"enum": ["open", "in_progress", "done", "cancelled"]
},
"idempotency_key": { "type": "string" }
}
}
}
¶
Each choice maps back to the profile. The intent-revealing names and the
use-when guidance reduce wrong-operation mistakes (Section 5). The fixed value sets and
additionalProperties: false stop the agent from inventing values
and parameters. The bounded limit and the opaque cursor follow
Section 8. The idempotency key and the stated side effects
follow Section 10. The batch result follows the
partial-failure rule in Section 7.¶
An operation described only well enough for someone who already knows the system leaves the agent to guess its scope and its side effects:¶
# Too little for an agent operationId: updateUser summary: Updates a user¶
The same operation, described for an agent, names its exact purpose, says when not to use it, and states the side effect:¶
# Enough for an agent operationId: updateUserEmailAddress summary: Change a user's email address description: > Use this only to change an existing user's email address. Do not use it for other profile fields. Use updateUserProfile for those. Side effect: sends a verification email. The new address stays unverified until the user confirms it.¶
The request selects three fields and a bounded page, so the response stays small. The response gives back a ready-to-use link for the next page, so the agent does no offset arithmetic:¶
A dry-run request reports what an operation would do, without doing it. The agent can check the predicted effects and decide whether to go ahead:¶
Sending the same request without the X-Dry-Run header performs
the operation. The predicted-effects shape is the same, so the agent can use
it to decide whether to go ahead or to ask for confirmation first.¶
This document gathers considerations discussed across the API design and AI agent engineering communities. The author thanks the authors of the referenced specifications and drafts, whose work covers the identity and authorization topics this document leaves to them, and the reviewers whose feedback shaped the profile structure, the worked examples, and the treatment of context exhaustion and observability.¶