2026-04-07  ·  15 viewsopenapidiscoveryx402mppAI agents

openapi.json for AI Agents: How Payment Discovery Actually Works

robots.txt tells crawlers what to access. llms.txt tells agents what your site means. openapi.json tells agents what your API costs and how to pay. Here's how to write one that works — including the warnings that won't show up in the docs.

When a human wants to use a paid API, they go to the website, find the docs, sign up, get an API key, and eventually make a request. That flow assumes a human at a keyboard.

AI agents don't work that way. They need to discover APIs, understand the payment terms, and make payments — all in a single automated flow, without a human in the loop. The mechanism that makes this possible is openapi.json.

This isn't the OpenAPI spec you write for human developers. It's a structured file that tells agents what your endpoints cost, what payment protocols you accept, and how to invoke them. Get it right and your service is automatically discoverable by any agent that understands the standard. Get it wrong and agents either ignore you or pay incorrectly.


What Agents Are Actually Looking For

When an agent discovers a new service, it fetches GET /openapi.json first. It's looking for a few specific things:

  1. What endpoints exist and what they do (standard OpenAPI)
  2. Which endpoints require payment (x-payment-info extension)
  3. What payment protocols are accepted (protocols array)
  4. How much each endpoint costs (price with mode and amount)
  5. Guidance on how to use the service (x-guidance)

If any of these are missing or malformed, the agent either can't pay or pays incorrectly. There's no human to ask for clarification.


The Basic Structure

A valid discovery document is a standard OpenAPI 3.1 file with two additions: x-guidance in the info object, and x-payment-info on each paid operation.

{
  "openapi": "3.1.0",
  "info": {
    "title": "Your API",
    "version": "1.0.0",
    "x-guidance": "Use GET /api/v1/report with wallet, from, and optionally to parameters to get a spending breakdown by service. Requires USDC payment via MPP or x402."
  },
  "paths": {
    "/api/v1/report": {
      "get": {
        "summary": "Get spending report",
        "parameters": [...],
        "x-payment-info": {
          "price": {
            "mode": "fixed",
            "amount": "0.001000",
            "currency": "USD"
          },
          "protocols": [
            {
              "mpp": {
                "method": "tempo",
                "intent": "charge",
                "currency": "0x20c000000000000000000000b9537d11c60e8b50"
              }
            },
            {
              "x402": {
                "network": "eip155:8453",
                "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
              }
            }
          ]
        },
        "responses": {
          "200": { "description": "Spending report" },
          "402": { "description": "Payment required" }
        }
      }
    }
  }
}

x-guidance: The Agent's Entry Point

x-guidance is how you tell an agent what your service does and how to use it. Put it in info, not at the path level.

"info": {
  "title": "paylog",
  "x-guidance": "Use GET /api/v1/report to get MPP spending history for a Tempo wallet address. Required params: wallet (0x...), from (YYYY-MM-DD). Optional: to (YYYY-MM-DD, defaults to today), resolve (true for unknown address resolution). Returns total_spent_usd and per-service breakdown."
}

Write this as if you're briefing someone who has never seen your API and needs to make a correct call on the first try. Include required parameters, value formats, and what the response contains.

Common mistake: putting guidance (without the x- prefix) or putting it at the root level instead of inside info. Both are legacy formats. Use info["x-guidance"].


x-payment-info: The Payment Terms

x-payment-info goes on each operation that requires payment. The canonical format:

"x-payment-info": {
  "price": {
    "mode": "fixed",
    "amount": "0.001000",
    "currency": "USD"
  },
  "protocols": [
    {
      "mpp": {
        "method": "tempo",
        "intent": "charge",
        "currency": "0x20c000000000000000000000b9537d11c60e8b50"
      }
    }
  ]
}

price: Use the object form with mode, amount, and currency. A string like "$0.001" is legacy format and will trigger a validation warning.

protocols: An array of payment method objects. Each object has exactly one key — the protocol name — whose value is the payment parameters. The mpp entry requires method, intent, and currency. An empty object {} fails schema validation.

Multiple protocols: If you accept both MPP and x402, list both. Agents pick the one they support.


What Not to Include

Don't put operator-only endpoints in openapi.json.

If you have a dashboard, admin panel, or internal endpoint that requires authentication or isn't meant for general use, leave it out of the discovery document. Include it and the validator will flag it: operations without proper payment info or auth declarations generate L2_AUTH_MODE_MISSING warnings.

Don't include endpoints that redirect or proxy to other services without making this clear in the description. Agents may follow redirects and end up making payments to unexpected destinations.


MPP Discovery: x-service-info

MPP adds an optional x-service-info extension at the document root for service-level metadata:

{
  "openapi": "3.1.0",
  "info": { ... },
  "x-service-info": {
    "categories": ["data", "payments"],
    "docs": {
      "homepage": "https://yourservice.dev",
      "apiReference": "https://yourservice.dev/docs",
      "llms": "/llms.txt"
    }
  },
  "paths": { ... }
}

The llms field links to your llms.txt, completing the agent-facing documentation stack: openapi.json describes what you cost, llms.txt describes what you do in natural language, robots.txt controls crawl access.


Serving the File

The file should be served at GET /openapi.json with appropriate caching:

// app/openapi.json/route.ts (Next.js)
export const dynamic = 'force-static'  // cache this indefinitely

import spec from './spec.json'
import { NextResponse } from 'next/server'

export function GET() {
  return NextResponse.json(spec, {
    headers: { 'Cache-Control': 'public, max-age=300' }
  })
}

Force-static caching is important. Discovery requests come from registries and agents that may probe your endpoint frequently. Serving the spec dynamically on every probe is unnecessary compute cost.


Validation

The AgentCash Discovery Spec validator checks your document and reports warnings at two levels:

# Check the full site
npx @agentcash/discovery@latest discover "https://yourservice.dev"

# Check a specific endpoint
npx @agentcash/discovery@latest check "https://yourservice.dev/api/v1/report"

Warnings worth knowing:

Warning Cause Fix
L2_PAYMENT_INFO_LEGACY Using string price format or old pricingMode field Use price: { mode, amount, currency } object
L2_AUTH_MODE_MISSING Endpoint has no payment info or auth declared Remove from spec or add x-payment-info
L2_GUIDANCE_LEGACY Using guidance instead of x-guidance Move to info["x-guidance"]
Schema validation error on protocols Empty mpp: {} object Add required method, intent, currency fields

Run the validator after any change to the spec. Warnings don't break payment flows, but they indicate the document won't be surfaced correctly by registries that score discovery quality.


How Agents Use This in Practice

When an agent encounters a service it hasn't seen before:

  1. Fetches GET /openapi.json
  2. Reads info["x-guidance"] to understand what the service does
  3. Finds the target operation (by path, summary, or parameters)
  4. Reads x-payment-info to get the price and protocols
  5. Selects a protocol it supports, constructs the payment
  6. Makes the actual request with the payment header

The entire discovery-to-payment flow happens without human input. If the spec is correct, the first call succeeds. If it's malformed or missing information, the agent either fails or falls back to asking the user for help — which defeats the purpose.


Registries

Once your openapi.json is correct, register with the aggregators that agents use for discovery:

  • MPPscanmppscan.com/register — public registry for MPP-enabled services
  • Bazaar — x402's discovery layer, indexed via CDP facilitator usage
  • mpp.dev/services — curated list; submit a PR to the mpp repo

Being listed in registries means agents don't need to know your URL in advance. They can discover your service by category, capability, or price, then make their first call without any prior configuration.


paylog.dev's own discovery document is at paylog.dev/openapi.json. If you want a working example to compare against, that's the canonical reference.