> **Can't find what you're looking for?** Use `search_docs` on the docs MCP server at `https://mpp.dev/api/mcp` to find what you need.

# Discovery \[Let clients automatically discover your API's pricing]

## Overview

MPP's discovery system lets clients and agents learn what your endpoints cost before making a request. You serve a standard [OpenAPI 3.1](https://spec.openapis.org/oas/v3.1.0) document at `/openapi.json` with `x-payment-info` extensions that advertise one or more payment offers for each paid operation. Registries aggregate these documents so agents can find paid APIs automatically, and provide value-added services like reputation, search, and analytics.

```mermaid
flowchart LR
  Agent -->|discovers| Registry
  Agent -->|402 + pay| ServerA[Server A]
  Agent -->|402 + pay| ServerB[Server B]
  ServerA --- Registry
  ServerB --- Registry
```

:::info[Discovery is advisory]
Discovery documents are informational hints. The runtime `402` Challenge remains the authoritative source of payment terms. Clients use discovery for display and planning, but defer to the Challenge for actual payment.
:::

## Registries

Registries aggregate discovery documents from multiple services, making it easy for clients and agents to find paid APIs.

| Registry | Description | How to add |
|----------|-------------|------------|
| [MPPScan](https://mppscan.com) | Public registry of MPP-enabled services with search and analytics | [Manually register](https://www.mppscan.com/register) in one click |
| [MPP Services directory](https://mpp.dev/services) | Curated list of live services on mpp.dev | [Submit a PR](https://github.com/tempoxyz/mpp) to add your service |

Agents can query the curated services directory over MCP at
`https://mpp.dev/mcp/services`. The server is read-only and exposes tools to
list services, rank services for an agent task, inspect endpoint offers, get
usage recipes, look up services by payment recipient, inspect available filters,
and fetch advisory OpenAPI summaries.

### Services MCP

The services MCP server is the agent-facing discovery surface for the curated
MPP directory:

```text
https://mpp.dev/mcp/services
```

Use it when an agent needs to:

* rank paid APIs by task, category, integration, or payment method
* turn a selected service into a usage recipe with endpoint candidates
* compare endpoint-level payment offers before constructing a request
* identify which services publish offers for a payment recipient from a `402`
  Challenge
* inspect catalog facets before narrowing a search
* fetch a live OpenAPI summary or registry-derived endpoint view

```json
{
  "mcpServers": {
    "mpp-services": {
      "url": "https://mpp.dev/mcp/services"
    }
  }
}
```

The MCP server is advisory and read-only. After discovery, clients call the
target service directly and treat the runtime `402` Challenge as authoritative.

For agent setup, MCP Inspector smoke tests, example prompts, and recipes, see
[Discover MPP services on Tempo docs](https://docs.tempo.xyz/guide/machine-payments/discover-services).

## Quick start

The `mppx` SDK generates discovery documents from your route configuration. Add `discovery()` to your server and it serves `/openapi.json` automatically.

```ts [server.ts]
import { Hono } from 'hono'
import { Mppx, discovery } from 'mppx/hono'
import { tempo } from 'mppx/server'

const app = new Hono()

const mppx = Mppx.create({
  methods: [
    tempo.charge({
      currency: '0x20c0000000000000000000000000000000000000',
      recipient: '0x...',
      testnet: true,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY,
})

app.get('/v1/fortune', mppx.charge({ amount: '0.01' }), (c) => c.json({ fortune: 'You will be rich' }))

// [!code hl:start]
discovery(app, mppx, {
  auto: true,
  info: { title: 'Fortune API', version: '1.0.0' },
})
// [!code hl:end]
```

This generates a `GET /openapi.json` endpoint with canonical `x-payment-info.offers[]` entries on each paid route.

### Composed offers

Pass a composed handler to `discovery()` when one route accepts multiple payment options. The generated operation preserves every nested offer in the same order as the runtime Challenges.

```ts twoslash [server.ts]
import { Hono } from 'hono'
import { discovery } from 'mppx/hono'
import { Mppx, stripe, tempo } from 'mppx/server'

const app = new Hono()
const charge = tempo.charge({
  currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
  recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
})
const card = stripe.spt({
  decimals: 2,
  networkId: 'acct_1234',
  paymentMethodTypes: ['card'],
  secretKey: 'sk_live_...',
})
const mppx = Mppx.create({ methods: [charge, card] })

const pay = mppx.compose(
  [charge, { amount: '0.01', description: 'Premium report' }],
  [card, { amount: '1', currency: 'usd', description: 'Premium report' }],
)

app.get('/v1/report', async (c) => {
  const result = await pay(c.req.raw)
  if (result.status === 402) return result.challenge
  return result.withReceipt(c.json({ report: '...' }))
})

discovery(app, mppx, {
  info: { title: 'Reports API', version: '1.0.0' },
  routes: [{ handler: pay, method: 'get', path: '/v1/report' }],
})
```

`mppx` derives discovery prices from each method's canonical Payment Request. Defaults and method transforms therefore match the Challenges returned by the paid route.

### Express

```ts [server.ts]
import express from 'express'
import { Mppx, discovery } from 'mppx/express'
import { tempo } from 'mppx/server'

const app = express()

const mppx = Mppx.create({
  methods: [
    tempo.charge({
      currency: '0x20c0000000000000000000000000000000000000',
      recipient: '0x...',
      testnet: true,
    }),
  ],
  secretKey: process.env.MPP_SECRET_KEY,
})

const pay = mppx.charge({ amount: '0.01' })
app.get('/v1/fortune', pay, (req, res) => res.json({ fortune: 'You will be rich' }))

// [!code hl:start]
discovery(app, mppx, {
  info: { title: 'Fortune API', version: '1.0.0' },
  routes: [{ handler: pay, method: 'get', path: '/v1/fortune' }],
})
// [!code hl:end]
```

### Next.js

In Next.js, `discovery()` returns a route handler you export from an API route.

```ts [app/openapi.json/route.ts]
import { discovery } from 'mppx/nextjs'
import { mppx, pay } from '../fortune/route'

// [!code hl:start]
export const GET = discovery(mppx, {
  info: { title: 'Fortune API', version: '1.0.0' },
  routes: [{ handler: pay, method: 'get', path: '/api/fortune' }],
})
// [!code hl:end]
```

## How it works

Your server exposes a `GET /openapi.json` endpoint that returns an OpenAPI document. Paid operations include an `x-payment-info` extension with one or more payment offers, and the document root can include `x-service-info` for service-level metadata.

```json [/openapi.json]
{
  "openapi": "3.1.0",
  "info": { "title": "My API", "version": "1.0.0" },
  "x-service-info": {
    "categories": ["ai"],
    "docs": {
      "homepage": "https://example.com",
      "apiReference": "https://example.com/docs",
      "llms": "/llms.txt"
    }
  },
  "paths": {
    "/v1/generate": {
      "post": {
        // [!code hl:start]
        "x-payment-info": {
          "offers": [
            {
              "amount": "1000000",
              "currency": "0x20c0000000000000000000000000000000000001",
              "description": "Generate text with Tempo",
              "intent": "charge",
              "method": "tempo"
            },
            {
              "amount": "100",
              "currency": "usd",
              "description": "Generate text with Stripe",
              "intent": "charge",
              "method": "stripe"
            }
          ]
        },
        // [!code hl:end]
        "responses": {
          "200": { "description": "Successful response" },
          "402": { "description": "Payment Required" }
        }
      }
    },
    "/v1/models": {
      "get": {
        "responses": {
          "200": { "description": "Successful response" }
        }
      }
    }
  }
}
```

### `x-payment-info`

Add this extension to any operation that requires payment. Prefer the canonical multi-offer shape:

| Field | Type | Description |
|-------|------|-------------|
| `offers` | `Offer[]` | Ordered list of payment offers the client can choose from |

### `offers[]`

| Field | Type | Description |
|-------|------|-------------|
| `amount` | `string \| null` | Payment amount in base units |
| `currency` | `string` | Currency code or token address |
| `description` | `string` | Human-readable description of the charge |
| `intent` | `string` | Payment intent (`charge` or `session`) |
| `method` | `string` | Payment method identifier (`tempo`, `stripe`) |

:::note[Compatibility shorthand]
The flat single-offer form is still valid as shorthand for older emitters, but use it only for backward compatibility. New documents write the same data under `offers[]`.

```json [openapi.json]
{
  // [!code hl:start]
  "x-payment-info": {
    "amount": "1000000",
    "currency": "0x20c0000000000000000000000000000000000001",
    "description": "Generate text with Tempo",
    "intent": "charge",
    "method": "tempo"
  }
  // [!code hl:end]
}
```
:::

### `x-service-info`

Optional root-level metadata about the service:

| Field | Type | Description |
|-------|------|-------------|
| `categories` | `string[]` | Free-form service categories (for example, `ai`, `payments`) |
| `docs.homepage` | `string` | Link to the service homepage |
| `docs.apiReference` | `string` | Link to API documentation |
| `docs.llms` | `string` | Link to an `llms.txt` file for AI consumption |

## Build manually

You can author a discovery document by hand following the [discovery specification](https://paymentauth.org/draft-payment-discovery-00.html). The document is a standard OpenAPI 3.1 file with two extensions:

::::steps
### Create the OpenAPI skeleton

Start with a standard OpenAPI 3.1 document:

```json [openapi.json]
{
  "openapi": "3.1.0",
  "info": {
    "title": "My API",
    "version": "1.0.0"
  },
  "paths": {}
}
```

### Add `x-payment-info` to paid operations

For each endpoint that requires payment, add the `x-payment-info` extension with an `offers` array. Add more objects to `offers[]` when the client can choose between alternative payment methods or currencies. Amounts are in base units (for example, `1000000` for $1.00 with 6 decimals).

```json [openapi.json]
{
  "paths": {
    "/v1/generate": {
      "post": {
        "summary": "Generate text",
        // [!code hl:start]
        "x-payment-info": {
          "offers": [
            {
              "amount": "1000000",
              "currency": "0x20c0000000000000000000000000000000000001",
              "intent": "charge",
              "method": "tempo"
            }
          ]
        },
        // [!code hl:end]
        "responses": {
          "200": { "description": "Successful response" },
          "402": { "description": "Payment Required" }
        }
      }
    }
  }
}
```

:::warning[Include a 402 response]
Operations with `x-payment-info` must include a `402` response in the `responses` object. Validators flag this as an error if missing.
:::

### Add `x-service-info` (optional)

Add service-level metadata to the document root:

```json [openapi.json]
{
  // [!code hl:start]
  "x-service-info": {
    "categories": ["ai", "text-generation"],
    "docs": {
      "homepage": "https://example.com",
      "apiReference": "https://example.com/docs/api",
      "llms": "https://example.com/llms.txt"
    }
  }
  // [!code hl:end]
}
```

### Serve at `/openapi.json`

Serve the document at `GET /openapi.json` with appropriate caching:

```http
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=300
```
::::

## CLI

Generate a static discovery document from a config module:

```bash [terminal]
$ npx mppx discover generate ./discovery.config.ts
```

Validate an existing discovery document from a file or URL:

```bash [terminal]
$ npx mppx discover validate https://example.com/openapi.json
```

## Validation

Common validation issues:

| Issue | Severity | Description |
|-------|----------|-------------|
| Missing `402` response | Error | Operations with `x-payment-info` must include a `402` response |
| Invalid amount format | Error | Each `offers[].amount` value must be a non-negative integer string |
| Missing `requestBody` | Warning | `POST`/`PUT`/`PATCH` operations without a `requestBody` definition |
| Invalid URI in docs | Error | `docs` links must be valid URIs or absolute paths |

## Specification

[IETF Specification](https://paymentauth.org/draft-payment-discovery-00) — Read the full specification
