Skip to main content
Enable agents to pay for APIs and content using MPP, an open protocol for machine-to-machine payments over HTTP. Privy’s server wallets provide the signing layer, while the mppx SDK handles the 402 payment flow automatically.

What is MPP?

MPP (Machine Payments Protocol) is an open protocol for machine-to-machine payments over HTTP. When a resource requires payment, the server responds with 402 Payment Required and payment details. The client signs a payment credential using the agent’s wallet and retries the request. Settlement happens on the Tempo blockchain using PathUSD.

Installation

npm install @privy-io/node mppx viem
  • @privy-io/node provides server-side wallet creation and signing
  • mppx provides the MPP client that handles 402 payment flows
  • viem provides the account interface used by Privy’s viem helper and mppx

Creating a Privy-backed account

MPP’s tempo.charge() payment method expects a viem Account for signing. Use Privy’s createViemAccount helper to create a viem account backed by a Privy wallet.
import {PrivyClient} from '@privy-io/node';
import {createViemAccount} from '@privy-io/node/viem';

const privy = new PrivyClient({
  appId: process.env.PRIVY_APP_ID!,
  appSecret: process.env.PRIVY_APP_SECRET!
});

const account = createViemAccount(privy, {
  walletId: 'insert-wallet-id',
  address: '0x0000000000000000000000000000000000000000'
});
Use @privy-io/node v0.20.0 or later. Earlier versions required custom Tempo transaction serialization logic.

Making MPP payments

Pass the Privy-backed account to the MPP client’s tempo.charge() method. The client automatically handles 402 responses, signs payment credentials, and retries requests.

Using mppx.fetch

import {PrivyClient} from '@privy-io/node';
import {createViemAccount} from '@privy-io/node/viem';
import {Mppx, tempo} from 'mppx/client';

const privy = new PrivyClient({
  appId: process.env.PRIVY_APP_ID!,
  appSecret: process.env.PRIVY_APP_SECRET!
});

async function makePayment(walletId: string, address: `0x${string}`, url: string) {
  const account = createViemAccount(privy, {
    walletId,
    address
  });

  const mppx = Mppx.create({
    polyfill: false,
    methods: [tempo.charge({account})]
  });

  const response = await mppx.fetch(url);
  const data = await response.json();
  return data;
}
mppx.fetch is a drop-in replacement for fetch. When a server returns 402 Payment Required, the client reads the payment requirements, signs a credential with the Privy wallet, and retries the request automatically. Use tempo({account}) instead when the client should support both one-time charges and Tempo sessions.

Using polyfill mode

Your app can also polyfill the global fetch so all HTTP requests handle 402 challenges automatically:
import {PrivyClient} from '@privy-io/node';
import {createViemAccount} from '@privy-io/node/viem';
import {Mppx, tempo} from 'mppx/client';

const privy = new PrivyClient({
  appId: process.env.PRIVY_APP_ID!,
  appSecret: process.env.PRIVY_APP_SECRET!
});

const account = createViemAccount(privy, {
  walletId: 'insert-wallet-id',
  address: '0x0000000000000000000000000000000000000000'
});

Mppx.create({
  polyfill: true,
  methods: [tempo.charge({account})]
});

// All fetch calls now handle 402 responses automatically
const response = await fetch('https://api.example.com/weather');

How it works

  1. Agent requests content: Your app calls mppx.fetch() or the polyfilled fetch()
  2. Server responds 402: Returns payment requirements (amount, currency, recipient)
  3. MPP client signs credential: Uses the Privy-backed account to sign a payment credential
  4. Retry with credential: Request repeats with the signed credential attached
  5. Server verifies and settles: Verifies the credential and settles payment on Tempo
  6. Server delivers: Returns content with 200 OK

Creating an MPP-enabled service

The mppx/nextjs package provides middleware for adding payment requirements to API routes:
// app/api/weather/route.ts
import {Mppx, tempo} from 'mppx/nextjs';

const mppx = Mppx.create({
  methods: [
    tempo.charge({
      currency: '0x20c0000000000000000000000000000000000000', // PathUSD
      recipient: process.env.MPP_RECIPIENT as `0x${string}`
    })
  ],
  secretKey: process.env.MPP_SECRET_KEY!
});

export const GET = mppx.charge({amount: '0.1'})(() =>
  Response.json({
    temperature: 72,
    condition: 'Sunny',
    location: 'San Francisco, CA'
  })
);
When a client calls this route without a payment credential, the middleware responds with 402 Payment Required. With a valid credential, it verifies the payment, settles on Tempo, and returns the data.

Full example

import {PrivyClient} from '@privy-io/node';
import {createViemAccount} from '@privy-io/node/viem';
import {Mppx, tempo} from 'mppx/client';

const privy = new PrivyClient({
  appId: process.env.PRIVY_APP_ID!,
  appSecret: process.env.PRIVY_APP_SECRET!
});

// 1. Create a wallet for the agent
const wallet = await privy.wallets().create({chain_type: 'ethereum'});

// 2. Build a viem account backed by Privy
const account = createViemAccount(privy, {
  walletId: wallet.id,
  address: wallet.address as `0x${string}`
});

// 3. Create the MPP client
const mppx = Mppx.create({
  polyfill: false,
  methods: [tempo.charge({account})]
});

// 4. Make a paid request
const response = await mppx.fetch('https://api.example.com/weather');
const weather = await response.json();

Learn more