402tools402 docs
docs · live

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.