> **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.

# Accept one-time payments \[Charge per request with a payment-gated API]

Build a payment-gated image generation API that charges $0.01 per request using `mppx`.
The server returns a random photo from [Picsum](https://picsum.photos) behind a paywall,
but you could swap in an AI model like [OpenAI Image Generation](https://developers.openai.com/api/reference/resources/images) instead.

## Demo

Try the payment-gated image generation API. Click **Run demo** to create a wallet, fund it, and make a paid request.

<div style={{ height: 480 }}>
  ```text
  GET /api/photo

  402 Payment Required

  Pay $0.01 with Tempo to receive a photo.
  ```
</div>

## Prompt mode

Paste this into your coding agent to build the entire guide in one prompt:

```text
Use https://mpp.dev/guides/one-time-payments.md as reference.
Add mppx to my app with a payment-gated photo endpoint 
that charges $0.01 per request using the Tempo payment method with 
PathUSD. When payment is verified, fetch a random photo from 
https://picsum.photos/1024/1024 and return the URL as JSON.
```

## Manual mode

Select your framework to follow a step-by-step guide. If your framework isn't listed, choose **Other** for a generic [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) approach compatible with most TypeScript server frameworks.

### Next.js

::::steps
### Install `mppx`

:::code-group
```bash [npm]
$ npm install mppx viem
```

```bash [pnpm]
$ pnpm add mppx viem
```

```bash [bun]
$ bun add mppx viem
```
:::

### Set up `Mppx` instance

Set up an `Mppx` instance with the `tempo` method.

* `recipient` is the address where you receive payments.
* `currency` is the token address for payments (in this case, `pathUSD`).

```ts [app/api/photo/route.ts]
import { Mppx, tempo } from 'mppx/nextjs'

export const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})
```

### Create the `/api/photo` route

Create the photo route. This route is **currently unpaid**.

```ts [app/api/photo/route.ts]
import crypto from 'crypto'
import { Mppx, tempo } from 'mppx/nextjs'

export const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

// [!code focus:start]
export const GET = async () => {
  const res = await fetch('https://picsum.photos/1024/1024')
  return Response.json({ url: res.url })
}
// [!code focus:end]
```

### Add `.charge` to the route handler

Add payment verification using `mppx.charge` as route middleware.
The handler runs only after payment is verified.

```ts [app/api/photo/route.ts]
import crypto from 'crypto'
import { Mppx, tempo } from 'mppx/nextjs'

export const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

// [!code focus:start]
export const GET =
  mppx.charge({ amount: '0.01', description: 'Random stock photo' }) // [!code ++]
  (async () => {
    const res = await fetch('https://picsum.photos/1024/1024')
    return Response.json({ url: res.url })
  })
// [!code focus:end]
```

### Test via the `mppx` CLI

```bash [terminal]
# Create account funded with testnet tokens
$ npx mppx account create

# Make a paid request
$ npx mppx http://localhost:3000/api/photo 
```
::::

### Hono

::::steps
## Install `mppx` and `hono`

:::code-group
```bash [npm]
$ npm install mppx hono viem
```

```bash [pnpm]
$ pnpm add mppx hono viem
```

```bash [bun]
$ bun add mppx hono viem
```
:::

### Set up `Mppx` instance

Set up an `Mppx` instance with the `tempo` method.

* `recipient` is the address where you receive payments.
* `currency` is the token address for payments (in this case, `pathUSD`).

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

const app = new Hono()

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})
```

### Create the `/api/photo` route

Create the photo route. This route is **currently unpaid**.

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

const app = new Hono()

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

// [!code focus:start]
app.get('/api/photo', async (c) => {
  const res = await fetch('https://picsum.photos/1024/1024')
  return c.json({ url: res.url })
})
// [!code focus:end]
```

### Add `.charge` to the route handler

Add payment verification using `mppx.charge` as route middleware.
The handler runs only after payment is verified.

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

const app = new Hono()

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

// [!code focus:start]
app.get(
  '/api/photo',
  mppx.charge({ amount: '0.01', description: 'Random stock photo' }), // [!code ++]
  async (c) => {
    const res = await fetch('https://picsum.photos/1024/1024')
    return c.json({ url: res.url })
  },
)
// [!code focus:end]
```

### Test via the `mppx` CLI

```bash [terminal]
# Create account funded with testnet tokens
$ npx mppx account create

# Make a paid request
$ npx mppx http://localhost:3000/api/photo 
```
::::

### Workers

::::steps
## Install `mppx`

:::code-group
```bash [npm]
$ npm install mppx viem
```

```bash [pnpm]
$ pnpm add mppx viem
```

```bash [bun]
$ bun add mppx viem
```
:::

### Set up `Mppx` instance

Set up an `Mppx` instance with the `tempo` method.

* `recipient` is the address where you receive payments.
* `currency` is the token address for payments (in this case, `pathUSD`).

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

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})
```

### Create the `/api/photo` route

Create the photo route. This route is **currently unpaid**.

```ts [src/index.ts]
import crypto from 'crypto'
import { Mppx, tempo } from 'mppx/server'

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

// [!code focus:start]
export default {
  async fetch(request: Request) {
    const res = await fetch('https://picsum.photos/1024/1024')
    return Response.json({ url: res.url })
  },
}
// [!code focus:end]
```

### Add `.charge` to the route handler

Add payment verification using `mppx.charge`. If the status is `402`, return the Challenge. Otherwise, fetch the photo and attach a Receipt to the response.

```ts [src/index.ts]
import crypto from 'crypto'
import { Mppx, tempo } from 'mppx/server'

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

// [!code focus:start]
export default {
  async fetch(request: Request) {
    const result = await mppx.charge({ // [!code ++]
      amount: '0.01', // [!code ++]
      description: 'Random stock photo', // [!code ++]
    })(request) // [!code ++]

    if (result.status === 402) return result.challenge // [!code ++]

    const res = await fetch('https://picsum.photos/1024/1024')
    return result.withReceipt(Response.json({ url: res.url })) // [!code ++]
  },
}
// [!code focus:end]
```

### Test via the `mppx` CLI

```bash [terminal]
# Create account funded with testnet tokens
$ npx mppx account create

# Make a paid request
$ npx mppx http://localhost:8787 
```
::::

### Express

::::steps
## Install `mppx` and `express`

:::code-group
```bash [npm]
$ npm install mppx express viem
```

```bash [pnpm]
$ pnpm add mppx express viem
```

```bash [bun]
$ bun add mppx express viem
```
:::

### Set up `Mppx` instance

Set up an `Mppx` instance with the `tempo` method.

* `recipient` is the address where you receive payments.
* `currency` is the token address for payments (in this case, `pathUSD`).

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

const app = express()

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})
```

### Create the `/api/photo` route

Create the photo route. This route is **currently unpaid**.

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

const app = express()

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

// [!code focus:start]
app.get('/api/photo', async (req, res) => {
  const response = await fetch('https://picsum.photos/1024/1024')
  res.json({ url: response.url })
})
// [!code focus:end]
```

### Add `.charge` to the route handler

Add payment verification using `mppx.charge` as route middleware.
The handler runs only after payment is verified.

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

const app = express()

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

// [!code focus:start]
app.get(
  '/api/photo',
  mppx.charge({ amount: '0.01', description: 'Random stock photo' }), // [!code ++]
  async (req, res) => {
    const response = await fetch('https://picsum.photos/1024/1024')
    res.json({ url: response.url })
  },
)
// [!code focus:end]
```

### Test via the `mppx` CLI

```bash [terminal]
# Create account funded with testnet tokens
$ npx mppx account create

# Make a paid request
$ npx mppx http://localhost:3000/api/photo 
```
::::

### Other

This guide walks through using `mppx/server` directly with any [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)-compatible framework: [Bun](https://bun.sh), [Deno](https://deno.com), [Cloudflare Workers](https://workers.dev), and others.

<div className="h-6" />

::::steps
## Install `mppx`

:::code-group
```bash [npm]
$ npm install mppx viem
```

```bash [pnpm]
$ pnpm add mppx viem
```

```bash [bun]
$ bun add mppx viem
```
:::

### Set up `Mppx` instance

Set up an `Mppx` instance with the `tempo` method.

* `recipient` is the address where you receive payments.
* `currency` is the token address for payments (in this case, `pathUSD`).

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

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})
```

### Create the `/api/photo` route

Create the photo route. This route is **currently unpaid**.

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

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

// [!code focus:start]
Bun.serve({
  async fetch(request) {
    const res = await fetch('https://picsum.photos/1024/1024')
    return Response.json({ url: res.url })
  },
})
// [!code focus:end]
```

### Add `.charge` to the route handler

Add payment verification using `mppx.charge`. If the status is `402`, return the Challenge. Otherwise, fetch the photo and attach a Receipt to the response.

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

const mppx = Mppx.create({
  secretKey: process.env.MPP_SECRET_KEY || crypto.randomBytes(32).toString('base64'),
  methods: [tempo.charge({
    testnet: true,
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

// [!code focus:start]
Bun.serve({
  async fetch(request) {
    const result = await mppx.charge({ // [!code ++]
      amount: '0.01', // [!code ++]
      description: 'Random stock photo', // [!code ++]
    })(request) // [!code ++]

    if (result.status === 402) return result.challenge // [!code ++]

    const res = await fetch('https://picsum.photos/1024/1024')
    return result.withReceipt(Response.json({ url: res.url })) // [!code ++]
  },
})
// [!code focus:end]
```

### Test via the `mppx` CLI

```bash [terminal]
# Create account funded with testnet tokens
$ npx mppx account create

# Make a paid request
$ npx mppx http://localhost:3000
```
::::

## With Stripe

Accept MPP payments through Stripe for refunds, reporting, and multi-currency payouts. Read the [Stripe documentation](https://docs.stripe.com/payments/machine/mpp) for the full integration walkthrough.

## Next steps
