MPP - Machine Payments Protocol
MPP is an open protocol (co-authored by Tempo and Stripe) that standardizes HTTP 402 Payment Required for machine-to-machine payments. Clients pay in the same HTTP request - no accounts, API keys, or checkout flows needed.
The core protocol spec is submitted to the IETF as the Payment HTTP Authentication Scheme.
Tempo token addresses
Public Tempo token addresses referenced throughout this skill. Use the placeholder names in code; full addresses are in the Tempo documentation.
| Token | Network | Placeholder |
|---|---|---|
| USDC.e (mainnet) | mainnet | <USDC_TEMPO_MAINNET> |
| pathUSD (testnet) | testnet | <PATHUSD_TESTNET> |
When to Use
- Building a paid API that charges per request
- Adding a paywall to endpoints or content
- Enabling AI agents to pay for services autonomously
- MCP tool calls that require payment
- Pay-per-token streaming (LLM inference, content generation)
- Session-based metered billing (pay-as-you-go)
- Accepting stablecoins (Tempo), cards (Stripe), or Bitcoin (Lightning) for API access
- Building a payments proxy to gate existing APIs (OpenAI, Anthropic, etc.)
Core Architecture
Three primitives power every MPP payment:
- Challenge - server-issued payment requirement (in
WWW-Authenticate: Paymentheader) - Credential - client-submitted payment proof (in
Authorization: Paymentheader) - Receipt - server confirmation of successful payment (in
Payment-Receiptheader)
Client Server
│ (1) GET /resource │
├──────────────────────────────────────────────>│
│ (2) 402 + WWW-Authenticate: Payment │
│<──────────────────────────────────────────────┤
│ (3) Sign payment proof │
│ (4) GET /resource + Authorization: Payment │
├──────────────────────────────────────────────>│
│ (5) Verify + settle │
│ (6) 200 OK + Payment-Receipt │
│<──────────────────────────────────────────────┤
Payment Methods & Intents
MPP is payment-method agnostic. Each method defines its own settlement rail:
| Method | Rail | SDK Package | Status |
|---|---|---|---|
| Tempo | TIP-20 stablecoins on Tempo chain | mppx (built-in) | Production |
| Stripe | Cards, wallets via Shared Payment Tokens | mppx (built-in) | Production |
| Lightning | Bitcoin over Lightning Network | @buildonspark/lightning-mpp-sdk | Production |
| Stellar | SEP-41 tokens on Stellar | @stellar/mpp | Production |
| Card | Encrypted network tokens (Visa) | mpp-card | Production |
| Custom | Any rail | Method.from() + Method.toClient/toServer | Extensible |
Two payment intents:
| Intent | Pattern | Best For |
|---|---|---|
| charge | One-time payment per request | API calls, content access, fixed-price endpoints |
| session | Pay-as-you-go over payment channels | LLM streaming, metered billing, high-frequency APIs |
Quick Start: Server (TypeScript)
import { Mppx, tempo } from 'mppx/server'
const mppx = Mppx.create({
methods: [tempo({
currency: '<PATHUSD_TESTNET>', // pathUSD testnet
recipient: '0xYourAddress',
})],
})
export async function handler(request: Request) {
const result = await mppx.charge({ amount: '0.01' })(request)
if (result.status === 402) return result.challenge
return result.withReceipt(Response.json({ data: '...' }))
}
Install: npm install mppx viem
Quick Start: Client (TypeScript)
import { privateKeyToAccount } from 'viem/accounts'
import { Mppx, tempo } from 'mppx/client'
// Polyfills globalThis.fetch to handle 402 automatically
Mppx.create({
methods: [tempo({ account: privateKeyToAccount('0x...') })],
})
const res = await fetch('https://api.example.com/paid')
// Payment happens transparently when server returns 402
Quick Start: Server (Python)
from fastapi import FastAPI, Request
from mpp.server import Mpp
from mpp.methods.tempo import tempo, ChargeIntent
app = FastAPI()
mpp = Mpp.create(method=tempo(
currency="<PATHUSD_TESTNET>",
recipient="0xYourAddress", intents={"charge": ChargeIntent()},
))
@app.get("/resource")
async def get_resource(request: Request):
result = await mpp.charge(authorization=request.headers.get("Authorization"), amount="0.50")
if isinstance(result, Challenge):
return JSONResponse(status_code=402, content={"error": "Payment required"},
headers={"WWW-Authenticate": result.to_www_authenticate(mpp.realm)})
return {"data": "paid content"}
Install: pip install "pympp[tempo]". See references/python-sdk.md for full patterns.
Quick Start: Server (Rust)
Install: cargo add mpp --features tempo,server. See references/rust-sdk.md for full patterns.
Framework Middleware (TypeScript)
Each framework has its own import (mppx/nextjs, mppx/hono, mppx/express, mppx/elysia):
// Next.js
import { Mppx, tempo } from 'mppx/nextjs'
const mppx = Mppx.create({ methods: [tempo({ currency: '0x20c0...', recipient: '0x...' })] })
export const GET = mppx.charge({ amount: '0.1' })(() => Response.json({ data: '...' }))
// Hono
import { Mppx, tempo } from 'mppx/hono'
app.get('/resource', mppx.charge({ amount: '0.1' }), (c) => c.json({ data: '...' }))
See references/typescript-sdk.md for Express and Elysia examples.
Sessions: Pay-as-You-Go Streaming
Sessions open a payment channel once, then use off-chain vouchers for each request - no blockchain transaction per request. Sub-100ms latency, near-zero per-request fees.
// Server - session endpoint
const result = await mppx.session({
amount: '0.001',
unitType: 'token',
})(request)
if (result.status === 402) return result.challenge
return result.withReceipt(Response.json({ data: '...' }))
// Server - SSE streaming with per-word billing
const mppx = Mppx.create({
methods: [tempo({ currency: '0x20c0...', recipient: '0x...', sse: true })],
})
export const GET = mppx.session({ amount: '0.001', unitType: 'word' })(
async () => {
const words = ['hello', 'world']
return async function* (stream) {
for (const word of words) {
await stream.charge()
yield word
}
}
}
)
// Client - session with auto-managed channel
import { Mppx, tempo } from 'mppx/client'
Mppx.create({
methods: [tempo({ account, maxDeposit: '1' })], // Lock up to 1 pathUSD
})
const res = await fetch('http://localhost:3000/api/resource')
// 1st request: opens channel on-chain
// 2nd+ requests: off-chain vouchers (no on-chain tx)
WebSocket transport: Sessions also support WebSocket streaming via Ws.serve(). The WebSocket protocol uses typed messages (mpp: 'authorization', mpp: 'message', mpp: 'payment-close-request', etc.) for payment negotiation alongside data delivery. See references/sessions.md for the full session lifecycle, escrow contracts, SSE patterns, and WebSocket integration.
Multi-Method Support
Accept Tempo stablecoins, Stripe cards, and Lightning Bitcoin on a single endpoint:
import Stripe from 'stripe'
import { Mppx, tempo, stripe } from 'mppx/server'
import { spark } from '@buildonspark/lightning-mpp-sdk/server'
const mppx = Mppx.create({
methods: [
tempo({ currency: '0x20c0...', recipient: '0x...' }),
stripe.charge({ client: new Stripe(key), networkId: 'internal', paymentMethodTypes: ['card'] }),
spark.charge({ mnemonic: process.env.MNEMONIC! }),
],
})
Use compose() to present multiple methods in a single