Skip to main content

routeweiler

Routeweiler

Build status PyPI version PyPI downloads Python versions Coverage License: Apache-2.0 Status: Beta Code style: Ruff Type-checked with mypy

The neutral micropayment router for autonomous agents. A single async HTTP client — await routeweiler.get(url) — that transparently handles 402 Payment Required across x402 (EVM), L402 (Lightning), MPP-Tempo (stablecoin), and MPP-SPT (Stripe).

Documentation: docs.routeweiler.com

Routeweiler flow diagram

Install

pip install routeweiler

Python 3.11+ required.

Quick start

import asyncio
import os
from eth_account import Account
from routeweiler import Routeweiler, Funding

signer = Account.from_key(os.environ["PRIVATE_KEY"])

async def main():
    async with Routeweiler(funding=[Funding.base_usdc(wallet=signer)]) as client:
        response = await client.get("https://api.example.com/data")
        print(response.json())

asyncio.run(main())

Supported rails

Rail Method Funding source Networks
x402 EVM signed transaction EvmFundingSource Base, Base-Sepolia
L402 BOLT-11 Lightning invoice LightningFundingSource Bitcoin, Regtest
MPP-Tempo Tempo 0x76 stablecoin tx TempoFundingSource Moderato testnet
MPP-SPT Stripe Shared Payment Token StripeFundingSource USD, EUR, GBP

Funding sources

Each rail accepts a typed FundingSource holding the credentials it uses to sign or authorise payments. Key material stays in your process; Routeweiler never transmits private keys.

x402 — EvmFundingSource

An eth_account.LocalAccount that signs EIP-3009 transferWithAuthorization for USDC on Base.

from eth_account import Account
from routeweiler import Funding

wallet = Account.from_key(os.environ["PRIVATE_KEY"])  # 64-char hex secp256k1 key, 0x prefix optional
funding = Funding.base_usdc(wallet=wallet)            # mainnet (chain 8453)
# funding = Funding.base_sepolia_usdc(wallet=wallet)  # testnet (chain 84532)

L402 — LightningFundingSource

Wraps an LND gRPC client. Bring a running LND node and pass its admin macaroon + TLS cert; Routeweiler pays BOLT-11 invoices through it.

from routeweiler.funding.lightning import LightningFundingSource, LndClient

lnd = LndClient(
    grpc_host="localhost",
    grpc_port=10009,
    macaroon_path="/path/to/admin.macaroon",
    tls_cert_path="/path/to/tls.cert",
)
funding = await LightningFundingSource.create(lnd, network="bitcoin")
# network: "bitcoin" | "bitcoin-testnet" | "bitcoin-regtest" | "bitcoin-signet"

MPP-Tempo — TempoFundingSource

Signs Tempo's type-0x76 charge transactions with an eth_account.LocalAccount — same key shape as x402, different chain. The same wallet can fund both rails.

from eth_account import Account
from routeweiler import Funding

wallet = Account.from_key(os.environ["PRIVATE_KEY"])  # 64-char hex secp256k1 key, 0x prefix optional
funding = Funding.tempo_usdc(wallet=wallet)              # mainnet, USDC
# funding = Funding.tempo_pathusd_moderato(wallet=wallet)  # testnet, pathUSD

MPP-SPT — StripeFundingSource

No on-device signing. Stripe holds the card; you supply a secret API key, a customer id, and a saved payment method id. Routeweiler asks Stripe to mint a Shared Payment Token at pay-time.

from routeweiler import Funding

funding = Funding.stripe(
    api_key=os.environ["STRIPE_API_KEY"],   # sk_live_... / sk_test_...
    customer="cus_ABC123",                  # buyer's Stripe customer id
    payment_method="pm_XYZ789",             # saved card / bank
    currency="usd",                         # ISO-4217: usd | eur | gbp | ...
)

Pass any combination to Routeweiler(funding=[...]). The router picks the best rail per challenge based on Policy and what the server accepts.

SQLite trace recorder

Enable local tracing with TraceSink.sqlite. Every call (paid or free) produces exactly one TraceEvent row, including the on-chain tx hash and the payment outcome:

from routeweiler import Routeweiler, Funding, TraceSink

async with Routeweiler(
    funding=[Funding.base_usdc(wallet=signer)],
    trace_sink=TraceSink.sqlite("./routeweiler.db"),
) as client:
    response = await client.get("https://api.example.com/data")

# Inspect with the sqlite3 CLI:
# sqlite3 ./routeweiler.db \
#   'SELECT request_id, selected_rail, http_status FROM trace_events;'

Budget envelopes

Enforce per-session or per-agent spend caps with local SQLite budget envelopes. Without a budget_envelope, tracing still works but no cap is enforced.

from routeweiler import BudgetEnvelope, Funding, Routeweiler, TraceSink

async with Routeweiler(
    funding=[Funding.base_usdc(wallet=signer)],
    trace_sink=TraceSink.sqlite("routeweiler.db"),
    budget_envelope=BudgetEnvelope(
        id="session-abc",
        cap_minor_units=500,           # 5.00 USD (in cents)
        cap_currency="usd",
        allowed_rails=["x402", "l402"],
        ttl_seconds=3_600,             # 1 hour
    ),
) as client:
    response = await client.get("https://api.example.com/data")

budget_envelope accepts three forms:

  • None (default) — no cap enforcement.
  • str — ID of a pre-existing envelope; raises EnvelopeNotFoundError at construction time if the row is missing.
  • BudgetEnvelope — declarative spec; If an envelope with the same id already exists it is reused unchanged.

Envelopes track reserved and settled amounts with Ed25519-signed draw receipts. BudgetExceededError is raised if a payment would breach the cap.

Policy

Control which rails are used, set per-call spend limits, or deny specific URLs:

from routeweiler import Policy, PolicyRule, RuleMatch, Routeweiler

async with Routeweiler(
    funding=[...],
    policy=Policy(
        currency="usd",          # reference currency for max_per_call_minor_units
        rules=[
            PolicyRule(
                name="deny analytics",
                when=RuleMatch(url_matches="*.tracking.io"),
                deny=True,
            ),
            PolicyRule(
                name="cap per call",
                when=RuleMatch(url_matches="*"),
                max_per_call_minor_units=500,  # 5 USD cents
            ),
        ]
    ),
) as client:
    ...

max_per_call_minor_units requires a reference currency to compare rail-native quotes against. Set Policy(currency="usd") (or any supported currency) when no budget_envelope is configured. The envelope's cap_currency takes precedence when both are present. If neither is provided and a rule uses max_per_call_minor_units, Routeweiler raises ValueError at construction time.

Releases

Releases follow SemVer. Pre-1.0 minors (0.1.0 → 0.2.0) may include breaking changes.

Tag format Channel Install
v0.2.0 Stable pip install routeweiler
v0.2.0b1 Beta pip install --pre routeweiler

A release is a git tag, not a merge. Merges to main run CI but don't publish. Pushing python/vX.Y.Z (or python/vX.Y.ZbN) to the monorepo triggers the release workflow, which builds the wheel + sdist, mirrors the tag as vX.Y.Z to this repo, attaches artefacts to the GitHub Release here, and — once the package is public — publishes to PyPI.

License

Apache 2.0 — see LICENSE.

Metadata

Release files for routeweiler 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for routeweiler 0.1.2
File Size Uploaded
routeweiler-0.1.2.tar.gz 199.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for routeweiler 0.1.2
File Interpreter ABI Platform
routeweiler-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 323.1 kB

Release files / routeweiler-0.1.2.tar.gz

Download URL routeweiler-0.1.2.tar.gz
Size 199.2 kB
Tags Source
SHA-256 checksum
How to use checksums
08763c1984806ab65a4a68e8b40f77b23de83f65d596b9d9f0ac2804d4db1b15
BLAKE2b-256 checksum
How to use checksums
668eaeaa3dd8f0275348951f5c9ed1f359aab7fb8a76fb6125a4b3a344122fdd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 May 25, 2026.

Transparency log

Release files / routeweiler-0.1.2-py3-none-any.whl

Download URL routeweiler-0.1.2-py3-none-any.whl
Size 123.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
167319793f20cabbc450721916f20cb0b33ec2bc343163080136dd29f0182134
BLAKE2b-256 checksum
How to use checksums
9c1e743727d8f8125ec8499a2761734418b30c3950d3633d261f82832ecea796
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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 May 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page