Machine Payments Protocol middleware for FastAPI.
Version v0.3 hardens receipt validation, replay protection, session binding, and
adds pluggable storage backends with Redis support for production deployments.
This project is still beta.
pip install fastapi-mppFor production validation, install Tempo support so default cryptographic validation is available:
pip install "fastapi-mpp[tempo]"Optional extras:
pip install "fastapi-mpp[dotenv]"
pip install "fastapi-mpp[redis]"
pip install "fastapi-mpp[stripe]"
pip install "fastapi-mpp[all]"RedisStoreis designed for Redis6.2+whereGETDELis available for atomic one-time challenge consumption.- For older Redis versions, the library falls back to an atomic Lua script, but
Redis
6.2+is strongly recommended for production compatibility and operations.
import os
from fastapi import FastAPI, Request
from mpp_fastapi.core import MPP
from mpp_fastapi.stores import RedisStore
app = FastAPI()
# Production mode (default): requires receipt_validator or fastapi-mpp[tempo].
mpp = MPP(
store=RedisStore(
redis_url=os.getenv("MPP_REDIS_URL", "redis://localhost:6379/0"),
)
)
@app.get("/premium")
@mpp.charge(amount="0.05", currency="USD", description="Premium data")
async def premium(request: Request):
return {"data": "paid content"}- Client calls endpoint without credential.
- Server responds
402 Payment Requiredwith:WWW-Authenticate: Payment challenge="<base64url(JSON)>", realm="MyAPI", expires="..."- challenge body containing
challenge_id,intent,amount,currency,expires_at,hints
- Wallet pays and retries with:
Authorization: Payment credential="<base64url(receipt-json)>"
- Server validates receipt (fail-closed in production), applies replay checks, and returns success with:
Payment-Receipt: <base64url(receipt-json)>- optional session headers when session mode is enabled.
Legacy compatibility can be kept with allow_legacy_headers=True:
X-MPP-ReceiptX-MPP-Session-Id
from fastapi import FastAPI, Request
from mpp_fastapi.core import MPP
from mpp_fastapi.types import MPPChargeOptions
app = FastAPI()
mpp = MPP()
session_options = MPPChargeOptions(
amount="0.01",
currency="USD",
description="Session-metered call",
session=True,
max_amount="0.50",
require_idempotency_key=True,
)
@app.post("/agent/infer")
@mpp.charge(options=session_options)
async def infer(request: Request):
return {"result": "paid inference"}Sessions are HMAC-signed opaque tokens bound to:
- route scope
- optional payer source
- currency/provider
- issued-at and expiry (default 15 minutes)
- max budget tracked in store
uv venv
source .venv/bin/activate
uv pip install -e ".[dev]"
uvicorn examples.simple_app:app --reloadThen test:
curl -i http://127.0.0.1:8000/free
curl -i http://127.0.0.1:8000/premiumExpected behavior demo:
GET /free -> 200 OK
GET /premium (without Authorization) -> 402 Payment Required
GET /premium (with Authorization: Payment credential="...") -> 200 OK
Read SECURITY.md before production usage.
- Beta warning: use with caution.
- In-memory replay/session/rate-limit stores are suitable for single-process deployments only.
- Production mode is fail-closed when receipt validation is not configured.
- HTTPS is enforced in production mode.
Incoming:
Authorization: Payment credential="..."(preferred)Payment-Receipt(supported)Payment-Session(session spends)X-MPP-ReceiptandX-MPP-Session-Idin legacy modeIdempotency-Keyfor safer retries
Response on 402:
WWW-Authenticate: Payment challenge="...", realm="...", expires="..."- JSON challenge payload
Response on success:
Payment-ReceiptPayment-Sessionwhen session authorization is established
- Storage is abstracted via
BaseStore; defaultInMemoryStoreis single-process only. RedisStoreis available for multi-worker production deployments.- Header size limit is enforced (
8KB) for authorization and receipt headers. - A basic in-memory challenge rate limiter is enabled (default
10challenges/IP/minute).
- Redis-backed replay/session/rate-limit stores
- Full conformance with evolving HTTP Payment auth draft semantics
- Advanced rate limiting and abuse controls
- Payment provider adapters and richer telemetry
- Fork the repository.
- Create a feature branch.
- Add tests for behavior changes.
- Run:
uv pip install -e ".[dev]"
pytest
ruff check .
mypy src- Open a PR with clear before/after behavior.
Inspired by the MPP ecosystem work and early protocol specs from Tempo and Stripe collaborators. Please refer to official protocol repos/specs for normative behavior and updates.
MIT