API Design for AI Agents: Building Agent-Friendly Interfaces
How to design APIs that AI agents can discover, understand, and use effectively. Covers API documentation, error handling, discoverability patterns, and best practices for agent-ready interfaces.
APIs designed for human developers don't automatically work well for **AI agents**. While developers can read documentation, infer patterns, and debug issues, agents need explicit, machine-readable information at every step. This guide covers how to design **APIs** that agents can effectively discover, understand, and use.
Why Agent-Friendly API Design Matters
The Developer vs. Agent Experience
Human developer: 1. Reads documentation 2. Understands concepts and patterns 3. Experiments in playground 4. Handles errors by reading messages and debugging 5. Builds mental model over time
AI agent: 1. Parses API specification 2. Matches capabilities to task requirements 3. Generates requests based on schemas 4. Interprets structured error responses 5. Each request is (somewhat) independent
What Agents Need
Agents require: - Discoverability: What can this API do? - Self-description: How do I use each endpoint? - Predictability: Consistent patterns across operations - Clear errors: What went wrong and how to fix it - Explicit constraints: What are the limits and rules?
API Specification Fundamentals
OpenAPI as the Foundation
OpenAPI (formerly Swagger) provides the machine-readable specification agents need:
openapi: 3.1.0
info:
title: E-Commerce API
description: |
API for product catalog, orders, and inventory management.
Designed for integration with AI agents and automation systems.
version: 2.0.0
contact:
email: api@example.com
servers:
- url: https://api.example.com/v2
description: Production
paths:
/products:
get:
operationId: listProducts
summary: List products with filtering and pagination
description: |
Returns a paginated list of products. Supports filtering by
category, price range, availability, and search query.
Results are sorted by relevance when using search, otherwise
by creation date (newest first).
parameters:
- name: category
in: query
schema:
type: string
enum: [electronics, clothing, home, sports]
description: Filter by product category
- name: min_price
in: query
schema:
type: number
minimum: 0
description: Minimum price filter (inclusive)
- name: max_price
in: query
schema:
type: number
minimum: 0
description: Maximum price filter (inclusive)
- name: in_stock
in: query
schema:
type: boolean
description: Filter to only in-stock items
- name: q
in: query
schema:
type: string
minLength: 2
description: Search query (searches name and description)
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
description: Page number for pagination
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
description: Number of results per page
responses:
'200':
description: Successful response
content:
application/json:
schema:
$ref: '#/components/schemas/ProductList'
Rich Descriptions for LLM Understanding
Descriptions are crucial—they're what LLMs use to understand when and how to use endpoints:
paths:
/orders:
post:
operationId: createOrder
summary: Create a new order
description: |
Creates a new order from items in the user's cart or from
directly specified items. The order goes through validation
to ensure:
- All items are in stock
- Prices haven't changed since cart was created
- Shipping address is valid for the destination country
- Payment method is valid and has sufficient funds
IMPORTANT: This endpoint charges the customer's payment method.
Use with appropriate user confirmation.
Returns the created order with status 'pending' if payment
is processing, or 'confirmed' if payment succeeded immediately.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
examples:
fromCart:
summary: Order from existing cart
value:
cart_id: "cart_abc123"
shipping_address_id: "addr_xyz"
directItems:
summary: Order with direct items
value:
items:
- product_id: "prod_123"
quantity: 2
shipping_address:
street: "123 Main St"
city: "New York"
postal_code: "10001"
country: "US"
Designing for Discoverability
API Capabilities Endpoint
Provide an endpoint that describes what the API can do:
paths:
/capabilities:
get:
operationId: getCapabilities
summary: Get API capabilities and feature flags
description: |
Returns information about available API features, enabled
capabilities for the authenticated client, and any current
limitations or maintenance windows.
responses:
'200':
content:
application/json:
schema:
type: object
properties:
features:
type: object
properties:
orders:
type: object
properties:
enabled: { type: boolean }
max_items_per_order: { type: integer }
supported_countries:
type: array
items: { type: string }
payments:
type: object
properties:
enabled: { type: boolean }
methods:
type: array
items: { type: string }
rate_limits:
type: object
properties:
requests_per_minute: { type: integer }
requests_per_day: { type: integer }
maintenance:
type: object
properties:
scheduled: { type: boolean }
start_time: { type: string, format: date-time }
end_time: { type: string, format: date-time }
Resource Relationships
Make relationships explicit:
components:
schemas:
Order:
type: object
properties:
id:
type: string
status:
type: string
enum: [pending, confirmed, shipped, delivered, cancelled]
items:
type: array
items:
$ref: '#/components/schemas/OrderItem'
_links:
type: object
description: HATEOAS links to related resources
properties:
self:
type: string
format: uri
description: Link to this order
customer:
type: string
format: uri
description: Link to the customer who placed the order
shipment:
type: string
format: uri
description: Link to shipment tracking (if shipped)
invoice:
type: string
format: uri
description: Link to download invoice PDF
_actions:
type: object
description: Available actions for this order
properties:
cancel:
type: object
properties:
href: { type: string, format: uri }
method: { type: string, enum: [POST] }
available: { type: boolean }
reason_if_unavailable: { type: string }
Action Availability
Tell agents what actions are currently possible:
{
"id": "order_123",
"status": "pending",
"_actions": {
"cancel": {
"href": "/orders/order_123/cancel",
"method": "POST",
"available": true
},
"modify": {
"href": "/orders/order_123",
"method": "PATCH",
"available": true,
"modifiable_fields": ["shipping_address"]
},
"refund": {
"href": "/orders/order_123/refund",
"method": "POST",
"available": false,
"reason_if_unavailable": "Order not yet paid"
}
}
}
Error Handling for Agents
Structured Error Responses
Errors must be machine-parseable:
components:
schemas:
Error:
type: object
required: [error]
properties:
error:
type: object
required: [code, message]
properties:
code:
type: string
description: Machine-readable error code
enum:
- validation_error
- not_found
- unauthorized
- forbidden
- rate_limited
- conflict
- internal_error
message:
type: string
description: Human-readable error message
details:
type: array
description: Specific validation errors or additional context
items:
type: object
properties:
field:
type: string
description: The field that caused the error
code:
type: string
description: Specific error code for this field
message:
type: string
description: Description of the field error
suggestion:
type: string
description: How to fix this error
retry_after:
type: integer
description: Seconds to wait before retrying (for rate limits)
documentation_url:
type: string
format: uri
description: Link to relevant documentation
Error Examples
// Validation error
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"details": [
{
"field": "items[0].quantity",
"code": "exceeds_stock",
"message": "Requested quantity (10) exceeds available stock (3)",
"suggestion": "Reduce quantity to 3 or less"
},
{
"field": "shipping_address.postal_code",
"code": "invalid_format",
"message": "Postal code format invalid for country US",
"suggestion": "Use format: 12345 or 12345-6789"
}
]
}
}
// Rate limit error
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded",
"retry_after": 30,
"details": [
{
"limit": "requests_per_minute",
"current": 61,
"maximum": 60,
"reset_at": "2025-01-15T10:31:00Z"
}
]
}
}
// Not found with suggestions
{
"error": {
"code": "not_found",
"message": "Product not found",
"details": [
{
"field": "product_id",
"code": "not_found",
"message": "No product with ID 'prod_xyz' exists",
"suggestion": "Check the product ID or search for products using /products?q=..."
}
],
"documentation_url": "https://api.example.com/docs/products"
}
}
Actionable Error Codes
Design error codes that suggest remediation:
ERROR_REMEDIATION = {
"validation_error": "Fix the indicated fields and retry",
"not_found": "Verify the resource ID or search for the resource",
"unauthorized": "Provide valid authentication credentials",
"forbidden": "Request additional permissions or use a different account",
"rate_limited": "Wait for retry_after seconds and retry",
"conflict": "Fetch the current resource state and resolve conflicts",
"payment_failed": "Update payment method or try a different one",
"out_of_stock": "Reduce quantity or choose a different product",
"internal_error": "Retry with exponential backoff; contact support if persistent"
}
Pagination Patterns
Cursor-Based Pagination (Recommended)
More reliable for agents than offset-based:
paths:
/products:
get:
parameters:
- name: cursor
in: query
schema:
type: string
description: |
Pagination cursor from previous response.
Omit for first page.
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 20
responses:
'200':
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Product'
pagination:
type: object
properties:
next_cursor:
type: string
nullable: true
description: Cursor for next page, null if no more pages
has_more:
type: boolean
description: Whether more results exist
total_count:
type: integer
description: Total number of results (if calculable)
Response example:
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6MTAwfQ==",
"has_more": true,
"total_count": 1247
}
}
Pagination Links
Include ready-to-use URLs:
{
"data": [...],
"pagination": {
"next_cursor": "abc123",
"has_more": true
},
"_links": {
"self": "/products?cursor=xyz789&limit=20",
"next": "/products?cursor=abc123&limit=20",
"first": "/products?limit=20"
}
}
Input Validation
Schema-Level Validation
Define constraints in the schema:
components:
schemas:
CreateProductRequest:
type: object
required: [name, price, category]
properties:
name:
type: string
minLength: 1
maxLength: 200
description: Product name (1-200 characters)
description:
type: string
maxLength: 5000
description: Product description (max 5000 characters)
price:
type: number
minimum: 0.01
maximum: 999999.99
description: Price in USD (0.01 - 999999.99)
category:
type: string
enum: [electronics, clothing, home, sports]
description: Product category
tags:
type: array
items:
type: string
maxLength: 50
maxItems: 10
description: Product tags (max 10, each max 50 chars)
sku:
type: string
pattern: '^[A-Z]{2,4}-[0-9]{4,8}$'
description: SKU in format XX-0000 (2-4 letters, dash, 4-8 digits)
Semantic Validation
Document business rules:
paths:
/orders:
post:
description: |
Creates a new order.
**Business Rules:**
- Maximum 50 items per order
- Total order value must not exceed $10,000
- At least one item required
- Cannot order more than available stock
- Shipping address must be in supported countries
- Cannot create order with items from different currencies
**Validation Order:**
1. Schema validation (required fields, types)
2. Stock availability check
3. Price validation (prices may have changed)
4. Shipping validation
5. Payment validation
Idempotency for Safe Retries
Idempotency Keys
Allow agents to safely retry requests:
paths:
/orders:
post:
parameters:
- name: Idempotency-Key
in: header
required: true
schema:
type: string
format: uuid
description: |
Unique key for this request. If the same key is sent again,
the original response will be returned without creating a
duplicate order. Keys expire after 24 hours.
Implementation guidance in docs:
## Idempotency
All state-changing endpoints support idempotency keys to enable safe retries.
### How It Works
1. Generate a unique UUID for each logical operation
2. Include it in the `Idempotency-Key` header
3. If your request times out or fails, retry with the SAME key
4. The server will return the original response if the operation completed
### Example
bash # First request (might timeout) curl -X POST /orders \ -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \ -d '{"items": [...]}'
# Retry with same key (safe) curl -X POST /orders \ -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \ -d '{"items": [...]}' # Returns same response as first request
### Key Guidelines
- Use UUIDs (v4 recommended)
- One key per logical operation (not per retry)
- Keys expire after 24 hours
- Different operations need different keys
Versioning for Stability
URL Versioning (Recommended)
https://api.example.com/v2/products
Version Lifecycle
Document version status:
info:
version: 2.0.0
x-api-lifecycle:
v1:
status: deprecated
sunset_date: 2025-06-01
migration_guide: https://api.example.com/docs/v1-to-v2
v2:
status: stable
released: 2024-06-01
v3:
status: beta
released: 2025-01-01
breaking_changes:
- Removed legacy authentication
- Changed pagination format
Deprecation Headers
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 01 Jun 2025 00:00:00 GMT
Link: <https://api.example.com/docs/v1-to-v2>; rel="deprecation"
Documentation for Agents
Machine-Readable Docs
Beyond OpenAPI, provide:
llms.txt for API:
# API: E-Commerce Platform
## Capabilities
- Product catalog search and retrieval
- Order creation and management
- Inventory tracking
- Customer management
## Authentication
OAuth 2.0 with scopes: products:read, orders:write, customers:read
## Rate Limits
- 60 requests/minute for standard clients
- 300 requests/minute for verified agents
## Common Workflows
1. Search products → Get product details → Create order
2. List orders → Get order status → Track shipment
## Endpoints Overview
- GET /products - Search and list products
- GET /products/{id} - Get product details
- POST /orders - Create new order
- GET /orders/{id} - Get order details
Example Requests
Include copy-paste examples:
paths:
/products:
get:
x-code-samples:
- lang: curl
label: Search products
source: |
curl -X GET "https://api.example.com/v2/products?q=wireless+mouse&category=electronics&limit=10" \
-H "Authorization: Bearer YOUR_TOKEN"
- lang: python
label: Python
source: |
import requests
response = requests.get(
"https://api.example.com/v2/products",
params={"q": "wireless mouse", "category": "electronics"},
headers={"Authorization": "Bearer YOUR_TOKEN"}
)
products = response.json()["data"]
Testing Agent Interactions
Sandbox Environment
Provide a safe testing environment:
servers:
- url: https://api.example.com/v2
description: Production
- url: https://sandbox.api.example.com/v2
description: |
Sandbox environment for testing.
- No real charges
- Test payment methods available
- Data resets daily
- Same rate limits as production
Test Credentials
## Sandbox Testing
### Test API Key
sk_test_abc123...
### Test Payment Methods
- `pm_card_success` - Always succeeds
- `pm_card_decline` - Always declines
- `pm_card_insufficient` - Insufficient funds error
### Test Products
- `prod_test_available` - Always in stock
- `prod_test_oos` - Always out of stock
- `prod_test_limited` - Only 1 in stock
Implementation Checklist
Specification - [ ] Complete OpenAPI 3.1 specification - [ ] Detailed descriptions for all endpoints - [ ] All parameters documented with constraints - [ ] Examples for request/response bodies - [ ] Error response schemas defined
Discoverability - [ ] /capabilities endpoint - [ ] HATEOAS links in responses - [ ] Action availability indicators - [ ] Related resource links
Error Handling - [ ] Structured error format - [ ] Field-level validation errors - [ ] Actionable error codes - [ ] Retry guidance (retry_after)
Reliability - [ ] Idempotency key support - [ ] Cursor-based pagination - [ ] Clear versioning strategy - [ ] Deprecation headers
Documentation - [ ] Machine-readable spec (OpenAPI) - [ ] llms.txt for AI discovery - [ ] Code examples in multiple languages - [ ] Sandbox environment
Conclusion
Agent-friendly API design is about making the implicit explicit. Everything a human developer would figure out from documentation, examples, and trial-and-error needs to be encoded in machine-readable formats.
The investment pays off: APIs designed for agents are also better for human developers. Clear schemas, helpful errors, and comprehensive documentation benefit everyone.
Start with a complete OpenAPI specification, then layer on discoverability features, structured errors, and agent-specific documentation. Test with actual LLM-based agents to identify gaps in your specification.
The APIs that agents can easily understand and use will be the ones that get integrated first—and become the default choices in the agentic ecosystem.