InFlow Python SDK
Python integration for InFlow payments using the Machine Payments Protocol (MPP)
and x402. The Python distribution and import namespace are both inflowpay.
Python 3.11 or newer is required.
Start with the runnable Sandbox examples for MPP and x402 Buyers and Sellers, including account setup, commands, and expected results.
Working with the repository
Install uv, then run:
make sync
make verify
make sync installs the exact development dependencies from uv.lock, including
optional integrations. make verify checks formatting, lint, strict typing, tests,
and complete line and branch coverage for each source file. It also builds the
source distribution and wheel, checks package metadata, and installs the wheel
outside the checkout to verify imports and the py.typed marker.
The consumer check exercises both the base installation and the combined optional
dependencies without issuing payments or contacting InFlow.
Shared conformance checks exercise the public SDK against pinned InFlow contract fixtures and produce reports for Python 3.11–3.14.
Protocol and framework dependencies are optional. The mpp and x402 extras
select payment libraries; evm and svm select x402 external-wallet dependencies;
mcp selects MCP dependencies for both protocols; fastapi selects the optional
web framework. The base package does not install those extras.
Runtime dependencies use compatible version ranges; uv.lock records the exact
versions used in development and CI. MCP dependencies use the 1.x line required by
pympp. No framework or blockchain package is imported by import inflowpay.
Client configuration and lifetime
ClientOptions selects production (the default, https://api.inflowpay.ai) or
sandbox (https://sandbox.inflowpay.ai). base_url overrides that destination.
Use api_key for API-key authentication or an asynchronous access_token callback
for Bearer authentication, not both. Omit both for anonymous requests. The callback
runs for each HTTP attempt, allowing your application to refresh a token; its errors
propagate without being retried. It must support concurrent calls and cancellation.
The shared runtime owns its HTTPX client. It also owns any AsyncBaseTransport
supplied through ClientOptions.transport: closing the client closes that transport.
Give each client its own transport rather than sharing one between clients. Use
async with to close an SDK client when leaving its scope, or explicitly await
aclose() after all operations finish. An existing application-owned HTTPX client
cannot be supplied. Custom transports must honor task cancellation and perform one
request without following redirects or retrying it themselves.
The request timeout is 30 seconds by default and covers sending the HTTP request and reading its response. Like the Node SDK, token retrieval occurs before this request timeout; your token provider controls its own timeout. Cancelling the caller's task interrupts token retrieval, requests, retry waits, and polling.
Retries, polling, and approval cancellation
The shared transport performs one attempt unless the protocol operation explicitly permits retries. Permitted retries are capped at three additional attempts, for network failures, timeouts, and HTTP 429, 502, 503, or 504. They use exponential delays starting at 200 milliseconds, with jitter. Redirects are returned as errors; request credentials are not forwarded to another destination, and response cookies are not sent on later requests.
Polling has a separate overall deadline. The protocol flow decides which states are terminal and which failures permit another poll. An approved request is not, by itself, evidence of a completed payment.
Approval cleanup performs one cancellation request with a five-second limit. It waits independently of the cancelled payment operation, so cancelling that operation does not immediately abort its cleanup request. Cleanup failures do not replace the original payment failure. This deliberately differs from Node's fire-and-forget cleanup: Python waits for completion or the limit before returning, matching the Go SDK's bounded cleanup. No background cancellation queue is created. If the process is forcibly terminated, delivery cannot be guaranteed. Cancelling an approval does not reverse a settled payment or issue a refund.
InflowApiError exposes code, http_status, endpoint, request_id, body, and
response headers. Server error messages are preserved. Credential fields and the
credential used for the failed attempt are redacted from error responses; sensitive
response headers are omitted. Transport errors use TIMEOUT or NETWORK_ERROR with
http_status=0. Python task cancellation remains asyncio.CancelledError.
MPP Buyer
Install inflowpay[mpp]. The Buyer obtains payment credentials from InFlow; it does
not sign transfers locally. Use an InFlow API key or Bearer-token provider belonging
to the paying account. The platform determines which payments that account may make.
import os
import httpx
from inflowpay import ClientOptions
from inflowpay.mpp.buyer import BuyerMethod, payment_transport
async def buy(url: str) -> bytes:
async with BuyerMethod(
ClientOptions(environment="sandbox", api_key=os.environ["INFLOW_API_KEY"])
) as method:
async with httpx.AsyncClient(transport=payment_transport([method])) as http:
response = await http.get(url)
response.raise_for_status()
return response.content
The first request reaches the seller without a payment credential. If the seller returns a compatible MPP challenge, the method asks InFlow to create the payment, waits for approval when required, and returns the platform's credential. pympp then sends that credential to the seller. Your InFlow API key goes only to InFlow, not to the seller. Keep redirects disabled on the HTTPX client, its default setting.
Select a payment method
BuyerMethod implements pympp's asynchronous method interface. Pass it to
payment_transport for HTTP requests or to pympp's PaymentRuntime for an existing
integration. You can also call await method.create_credential(challenge) with a
pympp Challenge; the result is a pympp Credential whose to_authorization()
retains the platform's payload, payer source, and additional wire fields.
| Constructor settings | Behavior |
|---|---|
Defaults: method="inflow", intent="charge" |
Pay an InFlow charge using the rail advertised by the seller. |
instrument_id="..." |
Select the funding instrument for an InFlow instrument-rail charge. |
method="tempo" |
Ask InFlow to produce a Tempo charge credential. No local wallet is required. |
intent="subscription" |
Purchase a subscription through the create-and-approve flow. |
intent="subscription", subscription_id="..." |
Authorize access using that existing subscription and the current seller challenge; do not purchase another subscription. |
Instrument and subscription identifiers are UUID strings. They are fixed when the method is constructed. Unlike Node's per-call context, pympp passes only a challenge to a method. Use separate method instances for different selections; do not change one instance's settings between concurrent requests. Do not register several methods with the same method/intent expecting pympp to choose a funding instrument: pympp selects the first matching method. Select the intended instance in your application.
Waiting, errors, and shutdown
poll_interval defaults to five seconds; the platform's retryAfterSeconds takes
precedence. pending_timeout defaults to 900 seconds and starts after creation
returns. Both settings use seconds and permit zero. A zero pending timeout accepts
an immediately ready response but does not wait for a pending payment. The pending
budget includes both waits and polling requests. Creation and subscription
authorization use the separate request timeout from ClientOptions.
Transaction creation, polling, and subscription authorization each make one HTTP
attempt; a lost creation response is not proof that no payment was initiated.
The method raises MppPaymentFailedError with the platform's problem,
MppPaymentExpiredError with transaction_id, MppPaymentTimeoutError with
transaction_id and timeout, or MppMalformedCredentialError for unusable
responses. HTTP failures remain InflowApiError; application token-provider
exceptions propagate. Do not automatically restart a payment after a failure.
Cancel the caller's task to stop one operation, or await method.cleanup() to stop
all active operations on that instance. Cancellation remains asyncio.CancelledError.
The instance remains reusable after cleanup. aclose() stops active operations and
closes the owned platform transport; the method cannot be reused after closing.
The seller HTTP transport has its own lifetime and does not close your Buyer methods.
The nested context managers above close both in the correct order.
When a failed or cancelled purchase has returned an approval identifier, the method
attempts bounded approval cancellation as described above. If creation is interrupted
before that identifier arrives, it cannot cancel an unknown approval; server expiry
is the backstop. Cancelling subscription authorization never cancels the subscription.
await method.cancel_approval(approval_id) also exposes bounded, best-effort approval
cancellation when your application already knows the identifier.
Automatic HTTP payment attempts
payment_transport configures pympp with max_payment_retries=1: one initial
request, at most one credential creation, and one paid retry. A further HTTP 402 is
returned to your application. Inspect that response instead of blindly issuing
another payment. Approval polling is separate and is not limited to one poll.
pympp defaults to three paid retries and creates a credential on each retry. Node's mppx transport can reuse a credential for the same unresolved challenge; this Python helper does not add such a cache. Applications using pympp's transport directly must set their retry policy explicitly. The InFlow helper's limit does not affect other transports that you construct.
If the paid retry loses its response or is cancelled, pympp raises
PaymentOutcomeUnknownError: the seller may already have received the credential.
That is different from cancelling an approval while waiting for InFlow. Retain the
exception's credential and request information for reconciliation; do not treat the
unknown outcome as permission to pay again.
MCP tools
Install inflowpay[mpp,mcp] and pass the same Buyer method to pympp's
McpClient. It wraps an initialized MCP session; your application owns that
session and its connection.
from mpp.extensions.mcp import McpClient
# Inside the Buyer method and initialized session's lifetimes:
client = McpClient(session, methods=[method])
result = await client.call_tool("premium_tool", {"query": "example"})
pympp handles the payment-required tool error, obtains a credential from the
Buyer method, and retries the tool once with payment metadata. InFlow charge,
Tempo charge, and InFlow subscription methods use the same platform flow as HTTP.
The subscription method's configured subscription_id determines whether it
purchases a subscription or authorizes an existing one. The HTTP helper's retry
setting does not configure MCP; pympp's MCP wrapper performs its own single retry.
See the MCP receipt limitation below before relying on receipt metadata.
MPP Seller
Install inflowpay[mpp,fastapi] for a FastAPI service, or inflowpay[mpp] for the
framework-independent payment hooks. Create a Seller account and an API key
in the Sandbox dashboard for testing or the
production dashboard for live payments. A Developer
account key does not authorize Seller configuration, validation, or broadcast.
await Seller.create(options) loads Seller configuration before returning. A
failed or cancelled setup closes its HTTP transport; retry setup with fresh
options/transport as needed. Successful configuration stays fixed for that Seller
instance. Unlike Node's asynchronous request hook, pympp's request transformation
is synchronous, so configuration is loaded during application startup rather than
inside a route. There is no background initialization or periodic refresh.
Use pympp's standalone pay decorator with the Seller as its charge intent:
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from mpp import Credential, Receipt
from mpp.server.decorator import pay
from starlette.responses import JSONResponse
from inflowpay import ClientOptions
from inflowpay.mpp.seller import Seller
@asynccontextmanager
async def application():
async with await Seller.create(
ClientOptions(environment="sandbox", api_key=os.environ["INFLOW_API_KEY"])
) as seller:
app = FastAPI()
@app.get("/report")
@pay(
intent=seller,
method=seller.method,
request=seller.charge_request({"amount": "0.50", "currency": "USDC"}),
realm="api.example.com",
secret_key=os.environ["MPP_SECRET_KEY"],
)
async def report(
request: Request, credential: Credential, receipt: Receipt
) -> JSONResponse:
return JSONResponse(
{"report": "Paid content"},
headers={"Payment-Receipt": receipt.to_payment_receipt()},
)
yield app
Run your ASGI server inside async with application() as app so the Seller stays
open until the server finishes serving requests. Keep MPP_SECRET_KEY stable and
private across replicas serving the same challenges. It authenticates challenges
locally and is separate from the InFlow API key. Set requires_auth=True on pay
when application authentication uses Authorization; pympp then uses
Payment-Authorization for the payment credential. The decorator does not implement
your application's authentication or access-control policy.
Prices and payment verification
For method="inflow", charge_request preserves the decimal price, sets the
recipient from authenticated Seller configuration, and selects an advertised rail
for the currency. When several rails are advertised, provide methodDetails.rail.
If the selected rail requires an instrument, provide methodDetails.instrumentId.
An unsupported currency, ambiguous selection, or missing required instrument raises
MppSellerConfigurationError; the SDK does not invent a payment option.
For method="tempo", supply the token currency address, recipient address, and
integer base-unit amount. For example, "500000" is half a token with six decimal
places. methodDetails defaults to feePayer=False and supportedModes=["pull"];
explicit request values override those defaults. Do not supply decimals to
charge_request: it expects the payment method's wire amount, not a conversion hint.
Request preparation copies your data rather than modifying it.
pympp authenticates the challenge and checks the route's request before invoking
the Seller hooks. validate checks the credential with InFlow without settling it;
broadcast performs the terminal operation. Do not call these hooks with an
unverified credential instead of using pympp's verification entry point. Successful
validation alone does not authorize delivery of paid content.
Configuration, validation, and broadcast use the shared transient retry policy. When the platform advertises idempotency support, a broadcast call generates one key and reuses it across its HTTP retries; separate calls have separate keys. The platform owns payment replay protection. Do not retry an entire payment flow merely because its response was lost. Cancellation stops an in-flight request but does not reverse a payment that reached the platform.
Payment rejections raise MppCredentialProblemError, retaining the platform
problem; pympp renders it as a payment error response. Invalid lifecycle responses
produce a fixed verification-failed problem. HTTP failures remain InflowApiError.
Receipts preserve the platform's timestamp precision and additional wire fields.
The endpoint must explicitly attach the Payment-Receipt header, as above.
Seller subscriptions and multiple InFlow offers on one endpoint are excluded for the upstream reasons documented below. Buyer subscription support is independent.
Upstream MPP compatibility
Wire values and request validation
Install inflowpay[mpp] to use inflowpay.mpp. Its WireObject is a JSON dictionary
using the protocol's field names, such as methodDetails and subscriptionId.
Amounts are decimal strings, not floating-point values.
from inflowpay.mpp import encode, parse_challenge_headers, validate_request
request = validate_request("inflow", "charge", {"amount": "1.50", "currency": "USD"})
encoded_request = encode(request)
# Pass one WWW-Authenticate value or a list of repeated values.
challenges = parse_challenge_headers(
'Payment id="example", realm="seller.example", method="inflow", '
f'intent="charge", request="{encoded_request}"'
)
validate_request checks InFlow charge/subscription and Tempo charge request shapes;
validate_payload checks InFlow or Tempo credential payload shapes. Both return a
deep copy. These checks do not establish supported currencies, account permissions,
signature validity, or settlement. Those require the payment workflow and platform.
decode_credential and decode_receipt retain the complete decoded JSON object,
including extension fields. decode also accepts other JSON values. Malformed wire
values raise MppCodecError. Challenge header parsing supports combined and repeated
Payment challenges, quoted commas and escapes, and rejects duplicate parameters and
control characters. Unknown header parameters are ignored, matching the Node SDK.
canonicalize and encode omit null object members but retain null array elements,
using RFC 8785 JSON ordering and number formatting. Monetary strings are unchanged.
The canonical JSON encoder rejects nonfinite numbers and integers outside the JSON
safe-integer range; use strings for exact large values. Decoding does not canonicalize.
Use to_pympp_challenge to pass an InFlow wire challenge to pympp and
from_pympp_challenge to read it back. The conversion preserves the original encoded
request and opaque bytes, and retains extension fields when converting back from the
returned object. It does not change the caller's dictionary. Keep the returned object
intact: rebuilding it as a plain pympp Challenge discards the retained extensions.
For a challenge created directly by pympp, only information present in that object
can be recovered. Receipt and credential decoding use InFlow wire dictionaries
instead of pympp's fixed-field models, which can discard fields.
These are codecs and shape checks, not proof of payment. pympp owns challenge authentication and transport; the InFlow platform owns payment verification and settlement.
Seller route limitations
InFlow charges use decimal amounts: "0.50" means half a unit of the specified
currency. pympp 0.11.0's high-level Mpp.pay and Mpp.compose helpers convert
prices to integer token units instead. Do not pass InFlow decimal prices through
those helpers. The standalone mpp.server.pay decorator accepts a complete
request dictionary and preserves its amount when no decimals field is supplied.
The endpoint handler must attach the returned receipt to its response using
receipt.to_payment_receipt() as the Payment-Receipt header value.
Multiple InFlow offers on one endpoint and Seller subscription routes are not
supported by this integration. Upstream composition does not accept the standalone
decorated handlers, and its fixed route options do not expose subscription terms
such as periodUnit, periodCount, and subscriptionExpires. These are separate
limitations; neither changes the Buyer subscription support described above.
Upstream issue #268 tracks parity with mppx's method-specific amount handling. Upstream issue #269 separately tracks subscription request fields in Seller routes and composition. The integration does not reverse upstream's token conversion or implement a separate offer-selection system to bypass these limitations.
MCP payment receipts
pympp provides Python support for the Machine
Payments Protocol (MPP), including payments for Model Context Protocol (MCP) tools.
Its MCPReceipt conversion in version 0.11.0 drops externalId, subscriptionId,
and extra from a core payment receipt. Its MCP receipt parser also discards these
fields and additional top-level fields supplied by a server.
For an integrator, this means a successful MCP tool payment can return a receipt
without the subscription or external reference needed to associate it with an
application record. Missing receipt metadata is not evidence that the payment
failed; do not repeat a payment to recover these identifiers. The MCP conversion
does not affect HTTP Payment-Receipt serialization.
Upstream PR #267 preserves these
fields and additional receipt extensions through MCP serialization, parsing, and
core conversions. It also preserves the payment method when converting an MCP
receipt back to a core receipt. Until that fix is included in the version of
pympp you install, do not rely on its MCP receipt objects to retain those fields.
x402 models and payment identifiers
Install inflowpay[x402] to use inflowpay.x402. Its PaymentRequirements,
PaymentRequired, PaymentPayload, verification, settlement, and capability
models are the actual classes from the upstream Python x402 SDK, not alternative
models that need converting before passing them to upstream code. They accept
InFlow's balance scheme and inflow:1 network as well as blockchain schemes
and networks. Model availability does not imply that every scheme is supported
by a particular buyer or facilitator.
For example, declare an optional payment identifier on a payment request and construct the matching entry for its payment payload:
from inflowpay.x402 import (
PAYMENT_IDENTIFIER,
declare_payment_identifier,
generate_payment_id,
payment_identifier_entry,
)
declaration = declare_payment_identifier()
request_extensions = {PAYMENT_IDENTIFIER: declaration}
payment_id = generate_payment_id()
entry = payment_identifier_entry(declaration, payment_id)
assert entry is not None
payload_extensions = {PAYMENT_IDENTIFIER: entry}
The identifier is stored in extensions["payment-identifier"]["info"]["id"].
It must contain 16–128 ASCII letters, digits, underscores, or hyphens.
generate_payment_id() uses a pay_ prefix and 16 random bytes encoded as
32 hexadecimal characters. Generate one identifier for a payment and reuse it
when retrying that same payment; do not reuse it for a different payment.
read_payment_identifier() returns None for an invalid declaration.
payment_identifier_entry() returns None for an invalid declaration or identifier.
Both preserve extra fields in the declaration's info and schema and return
independent copies. The declaration uses required: false; supplying an
identifier remains useful for identifying retries even when it is optional.
Amounts in these models are strings in the payment method's smallest units.
InFlow balance amounts use 18 decimal places; blockchain assets use their own
decimal scale. normalize_decimal_string() removes insignificant zeros without
floating-point conversion, rounding, or unit conversion. It is not a price
validator: strings outside plain decimal notation are returned unchanged.
Use model_dump(by_alias=True, exclude_none=True) when producing JSON-compatible
wire dictionaries from upstream models. Upstream fills omitted requirement
extra with {}; additional values inside extra, payload, and extensions
are preserved. These typed models do not preserve arbitrary unknown top-level
fields. Parsing a model or creating an identifier does not verify or settle a payment.
x402 Buyer
Install inflowpay[x402] to pay with an InFlow account. Buyer.create() loads
the account's supported payment methods before returning. Supply your buyer API
key or access-token provider through ClientOptions; those credentials are sent
to InFlow, not to the merchant.
import asyncio
import os
import httpx
from x402.http.clients.httpx import x402AsyncTransport
from inflowpay import ClientOptions
from inflowpay.x402.buyer import Buyer
async def main():
async with (
await Buyer.create(
ClientOptions(api_key=os.environ["INFLOW_API_KEY"], environment="sandbox")
) as buyer,
httpx.AsyncClient(transport=x402AsyncTransport(buyer), follow_redirects=False) as http,
):
response = await http.get(os.environ["PAID_RESOURCE_URL"])
response.raise_for_status()
print(response.text)
asyncio.run(main())
The upstream transport reads the merchant's payment requirements, asks the Buyer
for a payment payload, and retries the resource request with that payload. An
InFlow-managed payment can wait for the account owner to approve it. The default
approval wait is 15 minutes, polling every 5 seconds; configure pending_timeout
and poll_interval in seconds on Buyer.create().
Show an approval before waiting
Use await buyer.prepare(requirement, resource) when your application needs the
approval_id and transaction_id before waiting. Pass a selected upstream
PaymentRequirements and ResourceInfo. Then call await payment.await_payload()
to obtain encoded_payload, payment_payload, and transaction_id, or
await payment.cancel() to request approval cancellation.
Repeated waits share the completed payload and run completion hooks once. A
timeout before receiving a payload allows another wait on the same handle without
creating a second approval. Cancelling an individual waiting task does not cancel
the server approval; neither does closing the Buyer. The application owns that
decision in the two-phase flow. The automatic create_payment_payload() flow
requests cancellation if it fails before receiving a signed payload.
Payment selection and spending controls
inflowpay.x402.buyer.Buyer supports two signing paths. It requests an
InFlow-managed payment when InFlow supports the offered payment requirement.
Otherwise, it passes the request to an external-wallet scheme registered with
the upstream x402 client.
Use register_policy() to filter payment requirements for either path. For
InFlow-managed payments, the account's server-side policies and approval process
also apply. The upstream set_spend_controls() settings apply only to
external-wallet payments; they do not cap an InFlow-managed payment. This is the
same separation used by the InFlow Node SDK. If your application needs a local
amount limit for managed payments, enforce it in a registered payment policy.
The explicit prepare(requirement, resource) method uses the requirement you
provide, rather than selecting among offers or running selection policies. Apply
your application's selection policy before calling it. InFlow's server-side
policies and approvals still apply.
External wallets
Install inflowpay[evm] or inflowpay[svm] and register an upstream signing scheme
with buyer.register(network, scheme) to allow external-wallet payments. The
Buyer first selects a supported InFlow payment; otherwise it delegates to the
registered upstream schemes. prefer controls the managed scheme order and
defaults to ("balance", "exact"). Managed signing does not accept Permit2
requirements; those use an external wallet.
InFlow-managed payment requests use asynchronous network calls. External-wallet
payments run through the upstream Python x402 signing implementation. Its Solana
signer performs synchronous network requests for mint metadata and, when needed,
a recent blockhash. Those requests block other tasks on the same event loop even
when the caller awaits create_payment_payload(). The upstream TypeScript Solana
signer awaits these network requests instead.
The Python Buyer preserves upstream signing behavior; it does not move wallet signers into background threads. Applications using external wallets should account for this blocking work when sharing an event loop with other requests. See upstream issue #3649 for the reproduction and comparison with the TypeScript implementation.
EIP-7702 sponsorship
inflowpay.x402.eip7702.SponsorshipExtension supports external EVM wallets when
the merchant advertises inflowEip7702GasSponsoring for an exact Permit2 payment.
Install the evm extra. Construct the extension with anonymous ClientOptions,
a SponsorshipSigner, and an asynchronous consent callback, then register it with
buyer.register_extension(extension). Keep the extension's asynchronous context
manager open while creating payments; it owns a separate HTTP client.
The signer provides its address and three asynchronous methods: allowance()
reads the token allowance; sign_message() signs the supplied operation hash
using Ethereum's personal-message prefix; and sign_authorization() signs the
supplied EIP-7702 authorization. Both signing methods return 65-byte signatures
with recovery ID 27 or 28. The signer must belong to the wallet registered with
the upstream payment scheme.
Sponsorship is skipped when the existing Permit2 allowance covers the payment.
Otherwise, the extension requests preparation from InFlow and verifies the
returned contracts, payment calls, operation hash, and expiry before signing.
If account delegation is required, the consent callback must return True only
after the owner agrees: delegation persists even if the payment fails. The
extension returns signed data; it does not broadcast a transaction. Availability
depends on the InFlow environment's sponsorship endpoint and supported networks.
Accept x402 payments with FastAPI
Create an InFlow Seller account and an API key in its dashboard:
Sandbox for testing or
Production for live payments. Use the matching
environment in ClientOptions.
pip install 'inflowpay[x402,fastapi]' uvicorn
export INFLOW_API_KEY='your-seller-api-key'
Seller reads your configured wallets, assets, and payment methods and converts
prices into upstream x402 route options. Facilitator sends verification and
settlement requests to InFlow. The upstream FastAPI middleware challenges the
buyer, verifies the payment before calling your handler, and settles after a
successful handler response.
import asyncio
import os
import uvicorn
from fastapi import FastAPI
from x402 import x402ResourceServer
from x402.http.middleware.fastapi import payment_middleware
from x402.http.types import RouteConfig
from inflowpay import ClientOptions
from inflowpay.x402.facilitator import Facilitator
from inflowpay.x402.seller import Seller
async def serve() -> None:
options = ClientOptions(environment="sandbox", api_key=os.environ["INFLOW_API_KEY"])
async with (
await Seller.create(options) as seller,
await Facilitator.create(options) as facilitator,
):
resource_server = x402ResourceServer(facilitator)
for registration in await seller.scheme_registrations():
resource_server.register(registration["network"], registration["server"])
routes = {
"GET /report": RouteConfig(accepts=await seller.offers("$0.01")),
}
app = FastAPI()
app.middleware("http")(payment_middleware(routes, resource_server))
@app.get("/report")
async def report() -> dict[str, str]:
return {"report": "Your paid report"}
await uvicorn.Server(uvicorn.Config(app, host="127.0.0.1", port=8000)).serve()
asyncio.run(serve())
Both clients own their HTTP connections and close them when their context exits.
Seller.create() requires an API key and preloads configuration and capabilities.
Configuration reads are cached for one hour; await seller.config(refresh=True)
forces a refresh. await seller.get_supported(refresh=True) refreshes the Seller's
capability lookup, not a running Facilitator or resource server. Values returned
by these methods can be modified without changing the client's cache.
Prices, payment methods, and metering
seller.offers() accepts "$0.01", "0.01 USDC", or "0.01" with
currency="USDC". Dollar prices select all stablecoin currencies in your Seller
configuration. An explicit currency overrides the currency in the price string.
Conversion uses decimal digits rather than floating-point arithmetic and rejects
prices that cannot be represented in an asset's smallest unit. Price strings
allow up to eight fractional digits.
Use schemes=["balance"] or networks=["eip155:8453"] to restrict offers.
Both filters apply when supplied together. Routes default to fixed-price offers
and a 300-second payment timeout; max_timeout_seconds changes the timeout.
An empty offers list means the selected configuration and filters produced no
payment option. Check it before exposing the route.
Metered upto payments require inflowpay[evm] and explicit schemes=["upto"]
on both seller.offers() and seller.scheme_registrations(). Your environment
must advertise metered Permit2 support for the asset. The route price is the
maximum authorized charge. In your FastAPI handler, call the upstream helper
set_settlement_overrides(response, {"amount": "250000"}) to settle the actual
amount in atomic asset units. The buyer's signed maximum remains unchanged.
Without an override, settlement uses the route's maximum amount.
await seller.route("0.01 USDC", schemes=["exact"], permit2=True) selects
compatible Permit2 offers and adds a sponsorship declaration only when the token
and facilitator advertise support. It prefers EIP-2612 over InFlow EIP-7702.
Install inflowpay[evm] for EIP-2612 declarations. Balance offers are unaffected
by permit2=True; filter to exact for an on-chain-only route. InFlow-managed
buyers cannot sign Permit2 payments; these offers require external wallets.
Settlement and framework behavior
The facilitator preserves a valid buyer payment identifier or derives one from
the signed payment material. Verification and settlement use the same identifier
without modifying the caller's payload. HTTP 412 permit2_allowance_required
is returned as an invalid verification result. Settlement retries only an HTTP
409 idempotency_pending response, at most five attempts and at most five seconds
between attempts. Other HTTP or transport failures are not retried automatically:
the payment may already have completed. Cancelling the task stops pending waits.
The upstream FastAPI middleware buffers the handler's successful response before settlement; it does not stream the response to the buyer during settlement. A rejected verification prevents the handler from running. A handler error prevents after-handler settlement; a settlement failure replaces the successful handler response with a payment failure. Avoid irreversible business side effects in a handler without your own reconciliation design. InFlow does not roll back the handler's work.
The clients are framework-independent and do not import FastAPI. Use them with
other upstream asynchronous adapters where appropriate. For anonymous external
on-chain facilitation, explicitly use
await Facilitator.create(ClientOptions(environment="sandbox"), anonymous=True).
Anonymous setup rejects credentials; Seller configuration and InFlow balance
settlement require a Seller account.
x402 facilitator capabilities
Facilitator capabilities describe the payment schemes, networks, and extensions the facilitator supports. InFlow fetches these capabilities asynchronously during adapter creation. Setup fails if that request fails, rather than returning an adapter without capability data.
The upstream Python x402 resource server calls a synchronous get_supported()
method during initialization. The InFlow adapter answers from its preloaded
capabilities without making a network request. Payment verification and settlement
remain asynchronous.
Capabilities stay fixed for the lifetime of the adapter and its resource server.
To load capability changes, recreate both instances or restart the application.
Refreshing only the adapter's data or calling initialize() again on the same
upstream resource server is insufficient: x402 Python 2.25.0 retains existing
capability mappings, including networks removed from the facilitator's response.
This differs from the InFlow Node facilitator's one-hour capability cache. Node refreshes that cache when it is queried after expiry; it does not automatically reinitialize the application's resource server every hour. In Python, there is no timed capability refresh or background polling.
Cross-language verification
The Python–Node interoperability suite exercises Buyers and Sellers from both SDKs over local HTTP, including payment rejection and settlement failure. It uses a synthetic InFlow platform and does not make live payments.
Maintainers can follow the release instructions for versioning, Trusted Publishing setup, dry-runs, and publication.
Metadata
Release files for inflowpay 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| inflowpay-0.1.0.tar.gz | 57.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| inflowpay-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 108.8 kB
Release files / inflowpay-0.1.0.tar.gz
| Download URL | inflowpay-0.1.0.tar.gz |
|---|---|
| Size | 57.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4126af066facd3fbf6c546632c9a0124a7453853bae7a4bb3818f914a131fa64
|
|
BLAKE2b-256 checksum How to use checksums |
139f0a03d0257bda3cab7a9dea2aa183ecad803eaa8b44eb396cd6a1873481fc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency logRelease files / inflowpay-0.1.0-py3-none-any.whl
| Download URL | inflowpay-0.1.0-py3-none-any.whl |
|---|---|
| Size | 50.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c248c5783a628ba3c38f5f40e3eba787dabb63df3351db93329edd40ec9451ac
|
|
BLAKE2b-256 checksum How to use checksums |
0680bf0ad7417a4b99ee5de9088deb4eb10a2273a6654788ef3d5eea68bbc1c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.
Transparency log