Back to Blog
    TechnicalAEOStrategy

    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.

    Julia Maehler··3 min read

    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.