Python SDK Reference#
Python SDK Reference (for exact, aggr_deferred)#
Package:
okxweb3-app-x402.pip install okxweb3-app-x402. Fully async (async def), with*Syncsynchronous variants also provided. This document focuses on seller (resource server) + facilitator capabilities.
Packages#
| Module | Import path | Description |
|---|---|---|
| Core | x402 | x402ResourceServer / x402Facilitator / x402Client (and *Sync), schema types, errors |
| HTTP | x402.http | OKXFacilitatorClient / OKXFacilitatorConfig / OKXAuthConfig, x402HTTPResourceServer, PaymentOption / RouteConfig, header utilities |
| FastAPI | x402.http.middleware.fastapi | PaymentMiddlewareASGI, payment_middleware |
| Flask | x402.http.middleware.flask | PaymentMiddleware, payment_middleware |
| EVM mechanism | x402.mechanisms.evm.exact.server | ExactEvmScheme (exact scheme, server-side) |
| EVM mechanism | x402.mechanisms.evm.deferred.server | AggrDeferredEvmScheme (aggr_deferred scheme, server-side; server-side logic identical to exact) |
| Adapter | x402.adapters | X402Adapter (Payment Router integration) |
Core components#
x402ResourceServer#
Server-side component: protects resources, builds payment requirements, and forwards verify/settle to the facilitator.
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#
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#
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.
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#
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)#
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#
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#
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)#
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)#
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#
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:
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#
| Package | Import path | Description |
|---|---|---|
mpp (pympp) | mpp | Protocol-agnostic core layer: the Mpp coordinator, Challenge / Credential / Receipt, header parsing, the Store protocol, error types |
mpp_evm | mpp_evm | EVM payment methods: EvmMethod, ChargeIntent / SessionIntent, EIP-712 signing/verification, Voucher, Authorization, constants |
mpp_evm.saclient | mpp_evm.saclient | SA-API client: the SAClient protocol and the OKXSAClient implementation, HMAC-SHA256 authentication |
mpp_evm.store | mpp_evm | key-value store: FileStore, InMemoryChannelStore, ChannelState |
mpp_evm.signer | mpp_evm.signer | the Signer protocol, PrivateKeySigner (based on eth_account) |
mpp_evm.adapters | mpp_evm | Payment Router adapter: MppAdapter, MppRouteConfig |
| HTTP integration | mpp.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_credentialraisesNotImplementedError— buyer credential generation requires an EVM wallet/signer.The architecture differs slightly from Go: the protocol-agnostic core lives on
Mppin pympp (thempppackage), the EVM-specific implementation lives inmpp_evm, and the two are combined viaEvmMethod(intents={...}); route protection uses the@mpp.pay()decorator rather than framework middleware.
Core constants (mpp_evm)#
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.
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
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".
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 supportsplits. When splits are needed, use the low-levelMpp.charge(..., splits=[...]).
Event hooks
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#
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).
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:
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.
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 broadcaststransferWithAuthorizationon-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;ChargeIntentis 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#
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#
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.action | Behavior |
|---|---|
"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)#
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#
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#
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 method | OKX 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#
@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#
@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#
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.replacefor 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#
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.
MppRouteConfighas nosplitsfield; for splits use the low-levelMpp.charge(..., splits=[...]).
HTTP integration#
FastAPI (@mpp.pay() decorator)#
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:
Receiptis a frozen dataclass (nomodel_dump); access its fields directly or callreceipt.to_payment_receipt().
Behavior:
- No Authorization → returns
402+ aWWW-Authenticatechallenge - With Authorization → verify; on success, inject
credential/receiptand execute the handler, setting thePayment-Receiptheader - 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 code | Meaning | Exception type |
|---|---|---|
| 70000 | Invalid parameter | BadRequestError |
| 70001 | Chain not in the supported list | InternalSAError |
| 70002 | Payer blacklisted | MalformedCredentialError |
| 70003 | Invalid credential | MalformedCredentialError |
| 70004 | Signature verification failed | InvalidSignatureError |
| 70005 | Splits total exceeds amount | InvalidSplitError |
| 70006 | Too many split entries | InvalidSplitError |
| 70007 | Transaction not confirmed on-chain | InternalSAError |
| 70008 | Channel already closed | ChannelClosedError |
| 70009 | Invalid challenge | InvalidChallengeError |
| 70010 | channelId does not exist | ChannelNotFoundError |
| 70011 | Escrow grace period too short | InternalSAError |
| 70012 | Amount exceeds deposit | AmountExceedsDepositError |
| 70013 | Voucher increment too small | DeltaTooSmallError |
| 70014 | Channel is in CLOSING | ChannelClosedError |
| 8000 | API internal error | InternalSAError |
Error types overview: EvmPaymentError (base class), AmountExceedsDepositError, BadRequestError, ChannelClosedError, ChannelNotFoundError, DeltaTooSmallError, InsufficientBalanceError, InternalSAError, InvalidChallengeError, InvalidSignatureError, InvalidSplitError, SignerMismatchError.
- Python SDK Reference (for exact, aggr_deferred)PackagesCore componentsx402ResourceServerx402Facilitator / FacilitatorClientx402ClientOKX Facilitator client (x402.http)EVM payment schemesRoute configuration types (x402.http)HTTP middlewareFastAPIFlaskResponse schemas (x402.schemas / top-level exports)Header constants and utilities (x402.http)X402Adapter (x402.adapters) — Payment Router integrationPython SDK Reference (for charge, session)PackagesCore constants (mpp_evm)mpp core layer (pympp)MppChallenge / Credential / ReceiptError types (mpp.errors)EvmMethod (mpp_evm)Charge — ChargeIntentSession — SessionIntentConstructionInterface and business methodsSession action routing (inside verify)Signer (mpp_evm.signer)saclientSAClient protocolOKXSAClientEndpoints calledstoreChannelStore protocolChannelStateFileStoreInMemoryChannelStoreMppAdapter (mpp_evm.adapters) — Payment Router integrationHTTP integrationFastAPI (@mpp.pay() decorator)Multi-protocol (Payment Router)SA-API error code mapping
