Python
The Python flow uses eth-account for
signing, web3.py for the on-chain transfer,
and requests for the HTTP layer. No tools402 SDK install required.
#Install
bash
pip install eth-account web3 requests#Minimal pay-an-endpoint client
python
import base64
import json
import os
from typing import TypedDict
import requests
from eth_account import Account
from web3 import Web3
PRIVATE_KEY = os.environ["PRIVATE_KEY"]
account = Account.from_key(PRIVATE_KEY)
USDC = {
"base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"polygon": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
}
RPC = {
"base": "https://mainnet.base.org",
"polygon": "https://polygon-rpc.com",
}
TRANSFER_ABI = [{
"name": "transfer", "type": "function",
"inputs": [{"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}],
"outputs": [{"name": "ok", "type": "bool"}],
}]
class QuoteEntry(TypedDict):
scheme: str
network: str
asset: str
payTo: str
maxAmountRequired: str
maxTimeoutSeconds: int
def pay(endpoint: str, files=None, data=None, chain: str = "base") -> requests.Response:
# 1 · ask
r1 = requests.post(endpoint, files=files, data=data)
if r1.status_code != 402:
return r1
accepts = r1.json()["accepts"]
quote: QuoteEntry = next(
a for a in accepts if a["network"] == chain and a["scheme"] == "exact"
)
# 2 · pay USDC on-chain
w3 = Web3(Web3.HTTPProvider(RPC[chain]))
usdc = w3.eth.contract(address=Web3.to_checksum_address(quote["asset"]), abi=TRANSFER_ABI)
tx = usdc.functions.transfer(
Web3.to_checksum_address(quote["payTo"]),
int(quote["maxAmountRequired"]),
).build_transaction({
"from": account.address,
"nonce": w3.eth.get_transaction_count(account.address),
"gas": 80_000,
"maxFeePerGas": w3.eth.gas_price,
"maxPriorityFeePerGas": w3.to_wei(0.1, "gwei"),
"chainId": w3.eth.chain_id,
})
signed = account.sign_transaction(tx)
tx_hash = w3.eth.send_raw_transaction(signed.rawTransaction).hex()
# Wait one block for confirmation
w3.eth.wait_for_transaction_receipt(tx_hash, timeout=30)
# 3 · build X-Payment receipt (base64url JSON)
receipt = json.dumps({
"x402Version": 1, "scheme": "exact", "network": chain,
"payload": {"tx_hash": tx_hash},
})
x_payment = base64.urlsafe_b64encode(receipt.encode()).rstrip(b"=").decode()
# 4 · retry with receipt
return requests.post(endpoint, files=files, data=data,
headers={"X-Payment": x_payment})
# Usage
with open("receipt.pdf", "rb") as f:
r = pay("https://api.tools402.dev/v1/pdf-md", files={"file": f})
print(r.json()) # { "markdown": "# Receipt …" }#Pick a chain dynamically
python
def best_chain(accepts: list[QuoteEntry], have_usdc_on: list[str]) -> str:
# cheapest gas first, fall back to base
preference = ["polygon", "solana", "base"]
for chain in preference:
if chain in have_usdc_on and any(a["network"] == chain for a in accepts):
return chain
return "base"#Solana
Python on Solana uses solders or
solana-py instead of web3.py. The
scheme is spl-transfer, and the buyer partial-signs the transfer. See
/chains/solana for the full Solana flow.
#Common errors
Same as TypeScript — see /buy-side/typescript or /reference/errors for the full table.
#Why no pip install tools402
tools402 deliberately does not publish a Python SDK on PyPI today. The client is small enough (~60 lines) that copy-paste is faster than learning a new SDK API. When tools402 reaches commercial scale, an official Python SDK will land — until then, this page is the canonical reference.