Портал розробників
Тема

Python SDK Reference#

Python SDK Reference (for exact, aggr_deferred)#

Package: okxweb3-app-x402. pip install okxweb3-app-x402. Fully async (async def), with *Sync synchronous variants also provided. This document focuses on seller (resource server) + facilitator capabilities.

Packages#

ModuleImport pathDescription
Corex402x402ResourceServer / x402Facilitator / x402Client (and *Sync), schema types, errors
HTTPx402.httpOKXFacilitatorClient / OKXFacilitatorConfig / OKXAuthConfig, x402HTTPResourceServer, PaymentOption / RouteConfig, header utilities
FastAPIx402.http.middleware.fastapiPaymentMiddlewareASGI, payment_middleware
Flaskx402.http.middleware.flaskPaymentMiddleware, payment_middleware
EVM mechanismx402.mechanisms.evm.exact.serverExactEvmScheme (exact scheme, server-side)
EVM mechanismx402.mechanisms.evm.deferred.serverAggrDeferredEvmScheme (aggr_deferred scheme, server-side; server-side logic identical to exact)
Adapterx402.adaptersX402Adapter (Payment Router integration)

Core components#

x402ResourceServer#

Server-side component: protects resources, builds payment requirements, and forwards verify/settle to the facilitator.

python
from x402 import x402ResourceServer

class x402ResourceServer:
    def __init__(
        self,
        facilitator_clients: FacilitatorClient | list[FacilitatorClient] | None = None,
    ) -> None: ...

    def register(self, network: str, scheme) -> Self: ...
    def initialize(self) -> None: ...

    def on_before_verify(self, hook) -> Self
    def on_after_verify(self, hook) -> Self
    def on_verify_failure(self, hook) -> Self
    def on_before_settle(self, hook) -> Self
    def on_after_settle(self, hook) -> Self
    def on_settle_failure(self, hook) -> Self

network uses CAIP-2 identifiers (X Layer = "eip155:196").

x402Facilitator / FacilitatorClient#

python
from x402 import x402Facilitator

class x402Facilitator:
    def register(self, networks: list[str], scheme) -> Self: ...
    async def verify(self, payload, requirements) -> VerifyResponse: ...
    async def settle(self, payload, requirements) -> SettleResponse: ...

x402Client#

python
from x402 import x402Client

class x402Client:
    def __init__(self, payment_requirements_selector=None) -> None: ...
    def register(self, network: str, client_scheme) -> Self: ...
    async def create_payment_payload(self, payment_required) -> PaymentPayload: ...

Selector helpers: default_payment_selector, prefer_network, prefer_scheme, max_amount.


OKX Facilitator client (x402.http)#

Integrates with OKX /api/v6/pay/x402/* (verify / settle / supported), HMAC-SHA256 authentication. The synchronous variant is OKXFacilitatorClientSync.

python
from x402.http import OKXAuthConfig, OKXFacilitatorClient, OKXFacilitatorConfig

@dataclass
class OKXAuthConfig:
    api_key: str
    secret_key: str
    passphrase: str

@dataclass
class OKXFacilitatorConfig:
    auth: OKXAuthConfig
    base_url: str = "https://web3.okx.com"
    sync_settle: bool = True
    timeout: float = 30.0
    http_client: Any = None

class OKXFacilitatorClient:
    def __init__(self, config: OKXFacilitatorConfig) -> None: ...
    async def verify(self, payload, requirements) -> VerifyResponse: ...
    async def settle(self, payload, requirements) -> SettleResponse: ...
    def get_supported(self) -> SupportedResponse: ...
    async def aclose(self) -> None: ...

base_url defaults to the constant OKX_DEFAULT_BASE_URL. sync_settle=True means synchronous settlement; False means asynchronous settlement (polled via GET /settle/status).

Authentication headers: OK-ACCESS-KEY / OK-ACCESS-SIGN / OK-ACCESS-TIMESTAMP / OK-ACCESS-PASSPHRASE; signature = Base64(HMAC-SHA256(secretKey, timestamp + METHOD + path + body)).


EVM payment schemes#

python
from x402.mechanisms.evm.exact.server import ExactEvmScheme
from x402.mechanisms.evm.deferred.server import AggrDeferredEvmScheme

class ExactEvmScheme:
    scheme = "exact"
    def __init__(self) -> None: ...
    def register_money_parser(self, parser) -> "ExactEvmScheme": ...
    def parse_price(self, price, network) -> AssetAmount: ...
    def enhance_payment_requirements(self, requirements, supported_kind, extension_keys) -> PaymentRequirements: ...

class AggrDeferredEvmScheme:
    scheme = "aggr_deferred"
    def __init__(self) -> None: ...

register_money_parser is used to customize USD→atomic amount conversion. The server side of AggrDeferredEvmScheme delegates to ExactEvmScheme; only the facilitator settlement mode differs.

Registration: server.register("eip155:196", ExactEvmScheme()).


Route configuration types (x402.http)#

python
from x402.http import PaymentOption, RouteConfig

@dataclass
class PaymentOption:
    scheme: str
    pay_to: str | DynamicPayTo
    price: Price | DynamicPrice
    network: Network
    max_timeout_seconds: int | None = None
    extra: dict[str, Any] | None = None

@dataclass
class RouteConfig:
    accepts: PaymentOption | list[PaymentOption]
    resource: str | None = None
    description: str | None = None
    mime_type: str | None = None
    custom_paywall_html: str | None = None
    unpaid_response_body: UnpaidResponseBody | None = None
    settlement_failed_response_body: SettlementFailedResponseBody | None = None
    extensions: dict[str, Any] | None = None
    hook_timeout_seconds: float | None = None

RoutesConfig = dict[str, RouteConfig] | RouteConfig

PaymentOption field notes: scheme is "exact" | "aggr_deferred"; pay_to is the recipient address; price such as "$0.00001"; network such as "eip155:196". The key of RoutesConfig is shaped like "GET /resource".


HTTP middleware#

FastAPI#

python
from x402.http.middleware.fastapi import PaymentMiddlewareASGI

app.add_middleware(
    PaymentMiddlewareASGI,
    routes=routes,
    server=server,
)

Or functional style: payment_middleware(routes, server, paywall_config=None, paywall_provider=None, sync_facilitator_on_start=True).

Flask#

python
from x402.http.middleware.flask import PaymentMiddleware, payment_middleware

Middleware behavior: no payment header → 402 Payment Required (the PAYMENT-REQUIRED header carries the requirements); with PAYMENT-SIGNATURE / X-PAYMENT → verify (+ settle), and on success the request passes through and the PAYMENT-RESPONSE header is returned.


Response schemas (x402.schemas / top-level exports)#

python
class VerifyResponse(BaseX402Model):
    is_valid: bool
    invalid_reason: str | None = None
    invalid_message: str | None = None
    payer: str | None = None

class SettleResponse(BaseX402Model):
    success: bool
    error_reason: str | None = None
    error_message: str | None = None
    status: str | None = None
    payer: str | None = None
    transaction: str
    network: Network

class SettleStatusResponse(BaseX402Model):
    success: bool
    status: str | None = None
    error_reason / error_message / payer / transaction / network: ...

class SupportedKind(BaseX402Model):
    x402_version: int
    scheme: str
    network: Network
    extra: dict[str, Any] | None = None

class SupportedResponse(BaseX402Model):
    kinds: list[SupportedKind]
    extensions: list[str] = []
    signers: dict[str, list[str]] = {}

SettleStatusResponse is used for GET /settle/status polling; its status is "pending" | "success" | "failed".

Other schemas: PaymentRequirements / PaymentRequired / PaymentPayload (and *V1), AssetAmount, ResourceInfo, Network / Money / Price, and the constant X402_VERSION.


Header constants and utilities (x402.http)#

python
PAYMENT_SIGNATURE_HEADER = "PAYMENT-SIGNATURE"
PAYMENT_REQUIRED_HEADER = "PAYMENT-REQUIRED"
PAYMENT_RESPONSE_HEADER = "PAYMENT-RESPONSE"
X_PAYMENT_HEADER = "X-PAYMENT"
X_PAYMENT_RESPONSE_HEADER = "X-PAYMENT-RESPONSE"
HTTP_STATUS_PAYMENT_REQUIRED = 402

encode/decode_payment_signature_header(...)
encode/decode_payment_required_header(...)
encode/decode_payment_response_header(...)
detect_payment_required_version(...)
safe_base64_encode / safe_base64_decode

PAYMENT-SIGNATURE is the v2 client → server header; X-PAYMENT is the v1 header; PAYMENT-REQUIRED is the server 402 → client header.


X402Adapter (x402.adapters) — Payment Router integration#

python
from x402.adapters import X402Adapter
from x402.http import x402HTTPResourceServer

class X402Adapter:
    def __init__(self, server: x402HTTPResourceServer, priority: int = 20) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def priority(self) -> int: ...
    def detect(self, request) -> bool: ...
    async def get_challenge(self, request, cfg) -> dict[str, str]: ...
    async def handle(self, request, cfg) -> Any: ...

name returns "x402"; priority defaults to 20 (MPP=10 has higher priority); detect decides by checking the PAYMENT-SIGNATURE / X-Payment headers.

Constructing x402HTTPResourceServer:

python
from x402.server import x402ResourceServer
from x402.http import x402HTTPResourceServer

resource = x402ResourceServer(facilitator)
resource.register("eip155:196", ExactEvmScheme())
resource.register("eip155:196", AggrDeferredEvmScheme())
http_server = x402HTTPResourceServer(resource, routes={})
x402_adapter = X402Adapter(http_server)

For combining both schemes on a single route, see supporting exact + charge simultaneously.


Python SDK Reference (for charge, session)#

Packages#

PackageImport pathDescription
mpp (pympp)mppProtocol-agnostic core layer: the Mpp coordinator, Challenge / Credential / Receipt, header parsing, the Store protocol, error types
mpp_evmmpp_evmEVM payment methods: EvmMethod, ChargeIntent / SessionIntent, EIP-712 signing/verification, Voucher, Authorization, constants
mpp_evm.saclientmpp_evm.saclientSA-API client: the SAClient protocol and the OKXSAClient implementation, HMAC-SHA256 authentication
mpp_evm.storempp_evmkey-value store: FileStore, InMemoryChannelStore, ChannelState
mpp_evm.signermpp_evm.signerthe Signer protocol, PrivateKeySigner (based on eth_account)
mpp_evm.adaptersmpp_evmPayment Router adapter: MppAdapter, MppRouteConfig
HTTP integrationmpp.server.mpp.Mpp.pay()FastAPI decorator (verify-or-challenge), or the paymentrouter multi-protocol gateway

The Python MPP SDK currently provides seller (server-side) capabilities, fully async (async def). EvmMethod.create_credential raises NotImplementedError — buyer credential generation requires an EVM wallet/signer.

The architecture differs slightly from Go: the protocol-agnostic core lives on Mpp in pympp (the mpp package), the EVM-specific implementation lives in mpp_evm, and the two are combined via EvmMethod(intents={...}); route protection uses the @mpp.pay() decorator rather than framework middleware.


Core constants (mpp_evm)#

python
from mpp_evm import (
    X_LAYER_CHAIN_ID,
    DEFAULT_ESCROW_CONTRACT,
    DEFAULT_DOMAIN_NAME,
    DEFAULT_DOMAIN_VERSION,
    METHOD_NAME_EVM,
    ACTION_OPEN,
    ACTION_TOP_UP,
    ACTION_VOUCHER,
    ACTION_CLOSE,
    ACTION_SETTLE,
    STATUS_OPEN,
    STATUS_CLOSED,
)

Constant values: X_LAYER_CHAIN_ID = 196 (X Layer); DEFAULT_ESCROW_CONTRACT is the default Escrow contract address; DEFAULT_DOMAIN_NAME = "EVM Payment Channel"; DEFAULT_DOMAIN_VERSION = "1"; METHOD_NAME_EVM = "evm"; action constants ACTION_OPEN = "open" / ACTION_TOP_UP = "topUp" / ACTION_VOUCHER = "voucher" / ACTION_CLOSE = "close" / ACTION_SETTLE = "settle"; status constants STATUS_OPEN = "open" / STATUS_CLOSED = "closed".


mpp core layer (pympp)#

Mpp#

The server-side payment coordinator, bound to method + realm + secret_key, performing stateless challenge verification.

python
from mpp.server.mpp import Mpp

class Mpp:
    def __init__(
        self,
        method: Method,
        realm: str,
        secret_key: str,
        defaults: dict[str, Any] | None = None,
        store: Store | None = None,
    ) -> None: ...

    @classmethod
    def create(cls, method, realm=None, secret_key=None, store=None) -> "Mpp": ...

Parameters: method is the payment method (such as EvmMethod); realm is the WWW-Authenticate protection realm; secret_key is the HMAC key used to issue and verify challenge IDs; store is an optional replay-protection store that is automatically injected into the intent. When realm / secret_key are omitted, they are auto-detected from environment variables (MPP_REALM / MPP_SECRET_KEY).

charge() low-level API
python
async def charge(
    self,
    authorization: str | None,
    amount: str,
    *,
    currency: str | None = None,
    recipient: str | None = None,
    expires: str | None = None,
    description: str | None = None,
    memo: str | None = None,
    splits: list[dict[str, str]] | None = None,
    fee_payer: bool = False,
    chain_id: int | None = None,
    extra: dict[str, str] | None = None,
) -> Challenge | tuple[Credential, Receipt]:
    ...

Returns a Challenge (payment required) or (Credential, Receipt) (verification passed). Parameter notes: authorization is the Authorization header value (None means no credential was provided); amount is a human-readable amount (such as "0.50"), converted to base units using method.decimals; currency / recipient override the method defaults; expires is an ISO 8601 time, defaulting to now+5min; memo is a 32-byte hex memo; splits are split items; fee_payer and splits are mutually exclusive. A splits element is shaped like {"amount": "20", "recipient": "0x...", "memo": "partner-a"}.

pay() decorator (recommended)

Wraps the verify-or-challenge flow for a protected endpoint. The handler must inject using the credential and receipt parameter names. amount is a human-readable amount; intent is "charge" or "session".

python
def pay(
    self,
    amount: str,
    *,
    intent: str = "charge",
    currency: str | None = None,
    recipient: str | None = None,
    description: str | None = None,
    expires_in: timedelta | None = None,
    chain_id: int | None = None,
    extra: dict[str, str] | None = None,
) -> Callable: ...

Note: the pay() decorator does not support splits. When splits are needed, use the low-level Mpp.charge(..., splits=[...]).

Event hooks
python
def on(self, name: str, handler: EventHandler) -> Unsubscribe
def on_challenge_created(self, handler) -> Unsubscribe
def on_payment_success(self, handler) -> Unsubscribe
def on_payment_failed(self, handler) -> Unsubscribe

Challenge / Credential / Receipt#

python
from mpp import Challenge, Credential, Receipt

class Challenge:
    def to_www_authenticate(self, realm: str) -> str: ...

class Receipt:
    status: Literal["success"]
    timestamp: datetime
    reference: str
    method: str = "tempo"
    external_id: str | None = None
    extra: dict[str, Any] | None = None

    @classmethod
    def from_payment_receipt(cls, header: str) -> "Receipt": ...
    def to_payment_receipt(self) -> str: ...
    @classmethod
    def success(cls, reference, timestamp=None, method="tempo", external_id=None) -> "Receipt": ...

Challenge.to_www_authenticate produces the WWW-Authenticate header value; Receipt.to_payment_receipt produces the Payment-Receipt header value.

Error types (mpp.errors)#

PaymentError and its subclasses: BadRequestError, InvalidChallengeError, InvalidPayloadError, MalformedCredentialError, PaymentActionRequiredError, PaymentExpiredError, PaymentInsufficientError, PaymentMethodUnsupportedError, PaymentRequiredError, VerificationFailedError.


EvmMethod (mpp_evm)#

Combines the charge + session intents into a single method registration entry (implementing the pympp Method protocol).

python
from mpp_evm import EvmMethod

class EvmMethod:
    name: str = "evm"
    def __init__(self, *, intents: dict[str, Any] | None = None) -> None: ...

    @property
    def intents(self) -> dict[str, Any]: ...
    async def create_credential(self, challenge) -> Any: ...

In use, attach currency / recipient / decimals / chain_id directly onto the instance, to be read by Mpp.charge / pay:

python
method = EvmMethod(intents={"charge": charge_intent, "session": session_intent})
method.currency = "0x...token"
method.recipient = "0x...payee"
method.decimals = 6
method.chain_id = 196

Charge — ChargeIntent#

Implements the pympp Intent protocol, passing the credential through to the SA-API.

python
from mpp_evm.charge.intent import ChargeIntent

class ChargeIntent:
    name: str = "charge"
    def __init__(
        self,
        *,
        sa_client: SAClient,
        chain_id: int = X_LAYER_CHAIN_ID,
        recipient: str = "",
        fee_payer: bool = False,
    ) -> None: ...

    def challenge_method_details(self) -> dict[str, Any]: ...
    async def verify(self, credential: Credential, request: dict) -> Receipt: ...

When fee_payer=True, the seller actively broadcasts the EIP-3009 (transaction mode).

payload["type"] routing:

  • "transaction"SAClient.settle (the SA-API broadcasts transferWithAuthorization on-chain)
  • "hash"SAClient.verify_hash (the client has already broadcast; the SA-API verifies the tx hash)

There is also an EvmChargeMethod (dataclass) wrapper with the same fields; ChargeIntent is the common entry point.


Session — SessionIntent#

Implements the pympp Intent protocol. Maintains local channel state, local voucher signature verification + cumulative deduction, and merchant-initiated settle/close.

Construction#

python
from mpp_evm.session.intent import SessionIntent

class SessionIntent:
    name: str = "session"
    def __init__(
        self,
        *,
        sa_client: SAClient,
        recipient: str,
        signer: Signer | None = None,
        store: ChannelStore | None = None,
        chain_id: int = X_LAYER_CHAIN_ID,
        escrow_contract: str = DEFAULT_ESCROW_CONTRACT,
        per_request_cost: int | None = None,
        min_voucher_delta: int | None = None,
        nonce_provider: NonceProvider | None = None,
        deadline: int | None = None,
        domain_name: str = DEFAULT_DOMAIN_NAME,
        domain_version: str = DEFAULT_DOMAIN_VERSION,
        fee_payer: bool = False,
    ) -> None: ...

Parameter notes: sa_client is required; recipient is required, the Payee wallet address; signer is required for settle/close, passing None disables settle/close; store defaults to InMemoryChannelStore(); per_request_cost is the per-request deduction (base units); min_voucher_delta is the minimum increment between consecutive vouchers, defaulting to 0; nonce_provider defaults to UuidNonceProvider; deadline defaults to U256 MAX (never expires).

Interface and business methods#

python
def challenge_method_details(self) -> dict[str, Any]: ...
async def verify(self, credential, request: dict) -> Any: ...
def respond(self, credential, receipt) -> Any: ...

async def settle_channel(self, channel_id: str) -> dict: ...
async def close_channel(self, channel_id: str) -> dict: ...

Merchant-initiated settlement: settle_channel reads the highest voucher → signs a SettleAuthorization → calls SA settle; close_channel signs a CloseAuthorization → calls SA close → deletes the local store.

Session action routing (inside verify)#

payload.actionBehavior
"open"Verify → SA session/open → write local store
"topUp"Verify → SA session/topUp → add to local deposit
"voucher"Local signature verification + raise highest → deduct per_request_cost
"close"Verify voucher sig → sign CloseAuthorization → SA session/close
"settle"Sign SettleAuthorization → SA session/settle

Signer (mpp_evm.signer)#

python
from mpp_evm.signer import Signer, PrivateKeySigner

@runtime_checkable
class Signer(Protocol):
    def sign(self, msg_hash: bytes) -> bytes: ...
    @property
    def address(self) -> str: ...

class PrivateKeySigner:
    def __init__(self, account: LocalAccount) -> None: ...
    @classmethod
    def from_hex(cls, hex_key: str) -> "PrivateKeySigner": ...

sign takes a 32-byte hash and outputs a 65-byte r||s||v (v∈{27,28}); address is the 0x-prefixed checksummed address. PrivateKeySigner.from_hex accepts a private key with or without the 0x prefix.


saclient#

SAClient protocol#

python
from mpp_evm.saclient.client import SAClient

@runtime_checkable
class SAClient(Protocol):
    async def settle(self, req: ChargeSettleRequest) -> ChargeReceipt: ...
    async def verify_hash(self, req: ChargeVerifyHashRequest) -> ChargeReceipt: ...
    async def session_open(self, req: SessionOpenRequest) -> SessionReceipt: ...
    async def session_top_up(self, req: SessionTopUpRequest) -> SessionReceipt: ...
    async def session_settle(self, req: SessionSettleRequest) -> SessionReceipt: ...
    async def session_close(self, req: SessionCloseRequest) -> SessionReceipt: ...
    async def session_status(self, channel_id: str) -> SessionStatus: ...

Method grouping: settle / verify_hash are Charge (client-facing, passing the credential through); session_open / session_top_up are Session client-facing; session_settle / session_close are Session merchant-facing (the server constructs the request); session_status is a Session read-only query.

OKXSAClient#

python
from mpp_evm.saclient.client import OKXSAClient

class OKXSAClient:
    def __init__(
        self,
        *,
        base_url: str,
        api_key: str,
        secret_key: str,
        passphrase: str,
        http_client: httpx.AsyncClient | None = None,
        timeout: float = 30.0,
    ) -> None: ...

Endpoints called#

SAClient methodOKX path
settle()POST /api/v6/pay/mpp/charge/settle
verify_hash()POST /api/v6/pay/mpp/charge/verifyHash
session_open()POST /api/v6/pay/mpp/session/open
session_top_up()POST /api/v6/pay/mpp/session/topUp
session_settle()POST /api/v6/pay/mpp/session/settle
session_close()POST /api/v6/pay/mpp/session/close
session_status()GET /api/v6/pay/mpp/session/status?channelId=...

OKX responses are wrapped in {"code": 0, "data": {...}, "msg": ""}, which the client unwraps automatically. HMAC authentication headers: OK-ACCESS-KEY / OK-ACCESS-SIGN / OK-ACCESS-TIMESTAMP / OK-ACCESS-PASSPHRASE, signature = Base64(HMAC-SHA256(secretKey, timestamp + METHOD + requestPath + body)).


store#

ChannelStore protocol#

python
@runtime_checkable
class ChannelStore(Protocol):
    async def get(self, key: str) -> Any | None: ...
    async def put(self, key: str, value: Any) -> None: ...
    async def delete(self, key: str) -> None: ...

ChannelState#

python
@dataclass
class ChannelState:
    channel_id: str
    chain_id: int
    escrow_contract: str
    payer: str
    payee: str
    token: str = ""
    authorized_signer: str = ""
    deposit: str = "0"
    highest_voucher_amount: str = "0"
    highest_voucher_signature: str = ""
    min_voucher_delta: str = "0"
    spent: str = "0"
    units: int = 0
    finalized: bool = False
    close_requested_at: int = 0
    created_at: str = ""

Field notes: deposit is a decimal integer string; highest_voucher_amount and spent are both monotonically non-decreasing, with available = highest_voucher_amount - spent. Provides to_dict() / from_dict() (camelCase JSON, cross-language interoperable).

FileStore#

python
from mpp_evm import FileStore

class FileStore:
    def __init__(self, directory: str) -> None: ...
    async def get(self, key) / put(self, key, value) / delete(self, key)
    async def put_if_absent(self, key, value) -> bool
  • Automatically mkdirs on construction; each key lands at {dir}/{key}.json
  • Writes use tmp + os.replace for atomic persistence, pretty-printed JSON
  • Keys are guarded against path traversal (rejects ../ / absolute paths)
  • Cross-language compatible (Go/Rust/TS share the same directory)

InMemoryChannelStore#

The default implementation when SessionIntent is not given a store. An in-process dict with deep-copy isolation on put/get. Lost on restart, not shared across processes/workers; for production or multi-worker deployments use FileStore or a custom shared backend.


MppAdapter (mpp_evm.adapters) — Payment Router integration#

python
from mpp_evm import MppAdapter, MppRouteConfig

@dataclass
class MppRouteConfig:
    intent: str = ""
    amount: str = ""
    currency: str = ""
    decimals: int = 6
    description: str = ""
    external_id: str = ""
    realm: str = ""
    unit_type: str = ""
    suggested_deposit: str = ""

class MppAdapter:
    def __init__(self, mpp: Mpp, priority: int = 10) -> None: ...
    @property
    def name(self) -> str: ...
    @property
    def priority(self) -> int: ...
    def detect(self, request) -> bool: ...
    async def get_challenge(self, request, cfg) -> dict[str, str]: ...
    async def handle(self, request, cfg) -> Any: ...

MppRouteConfig fields: intent is "charge" or "session"; amount is a human-readable amount; currency is the token contract address; unit_type is the session billing unit (such as "request"); suggested_deposit is the suggested initial session deposit (base units). MppAdapter.name returns "mpp", priority defaults to 10; detect checks for the "Authorization: Payment ..." header.

MppRouteConfig has no splits field; for splits use the low-level Mpp.charge(..., splits=[...]).


HTTP integration#

FastAPI (@mpp.pay() decorator)#

python
from fastapi import FastAPI, Request
from mpp import Credential, Receipt

app = FastAPI()

@app.get("/api/premium")
@mpp.pay(amount="0.00001", intent="charge", description="One premium API call")
async def premium(request: Request, credential: Credential, receipt: Receipt) -> dict:
    return {"data": "premium content", "receipt": {"reference": receipt.reference, "status": receipt.status}}

Note: Receipt is a frozen dataclass (no model_dump); access its fields directly or call receipt.to_payment_receipt().

Behavior:

  1. No Authorization → returns 402 + a WWW-Authenticate challenge
  2. With Authorization → verify; on success, inject credential / receipt and execute the handler, setting the Payment-Receipt header
  3. On failure → the corresponding HTTP error code

Multi-protocol (Payment Router)#

See paymentrouter.PaymentGate + MppAdapter / X402Adapter; for details see supporting exact + charge simultaneously and Python SDK Reference (for exact, aggr_deferred).


SA-API error code mapping#

mpp_evm.errors.map_sa_error(code, msg) maps an SA code to the corresponding EvmPaymentError subclass, defaulting to InternalSAError when there is no match:

SA codeMeaningException type
70000Invalid parameterBadRequestError
70001Chain not in the supported listInternalSAError
70002Payer blacklistedMalformedCredentialError
70003Invalid credentialMalformedCredentialError
70004Signature verification failedInvalidSignatureError
70005Splits total exceeds amountInvalidSplitError
70006Too many split entriesInvalidSplitError
70007Transaction not confirmed on-chainInternalSAError
70008Channel already closedChannelClosedError
70009Invalid challengeInvalidChallengeError
70010channelId does not existChannelNotFoundError
70011Escrow grace period too shortInternalSAError
70012Amount exceeds depositAmountExceedsDepositError
70013Voucher increment too smallDeltaTooSmallError
70014Channel is in CLOSINGChannelClosedError
8000API internal errorInternalSAError

Error types overview: EvmPaymentError (base class), AmountExceedsDepositError, BadRequestError, ChannelClosedError, ChannelNotFoundError, DeltaTooSmallError, InsufficientBalanceError, InternalSAError, InvalidChallengeError, InvalidSignatureError, InvalidSplitError, SignerMismatchError.