ForgeMesh Pay-per-Crawl for Python
Charge AI crawlers and bots per page with x402 USDC micropayments on Base, paid straight to your wallet. Humans and verified search engines read free. Middleware for FastAPI / Starlette (ASGI), Flask and any WSGI app, and Django, on one framework-free core.
This is the Python edition of the WordPress plugin; behaviour is identical.
- Humans see nothing different. Browsers get the page as always.
- Verified search engines stay free. Googlebot, Bingbot, DuckDuckBot, Applebot and others are checked by reverse DNS, so SEO is untouched. A crawler that only claims to be Googlebot is treated as a script.
- AI crawlers, SEO crawlers and scripts get
402 Payment Requiredwith an x402 challenge (v2PAYMENT-REQUIREDheader + v1 JSON body) and an MPPWWW-Authenticate: Paymentchallenge. They pay USDC on Base and retry; the page is served only after the payment settles on-chain. - You keep 100%. The payer's USDC goes directly to your wallet. No custody, no account, no API key, no revenue share.
- Discovery built in. Serves
/.well-known/x402.json,/llms.txtand/agents.txt.
Install
pip install forgemesh-pay-per-crawl
Python 3.10+. One dependency (pycryptodome, for keccak256). httpx is used for facilitator calls if installed, otherwise the standard library. Start with mode="monitor" (records what bots would have paid, never blocks), then switch to mode="gate".
FastAPI / Starlette
from forgemesh_pay_per_crawl import Config
from forgemesh_pay_per_crawl.asgi import PayPerCrawlMiddleware
app.add_middleware(PayPerCrawlMiddleware,
config=Config(pay_to="0xYourWalletOnBase", mode="gate", price_usd=0.01, base_url="https://example.com"))
Flask (and any WSGI app)
from forgemesh_pay_per_crawl import Config
from forgemesh_pay_per_crawl.wsgi import PayPerCrawlWSGI
app.wsgi_app = PayPerCrawlWSGI(app.wsgi_app,
Config(pay_to="0xYourWalletOnBase", mode="gate", price_usd=0.01, base_url="https://example.com"))
Django
# settings.py
MIDDLEWARE = ["forgemesh_pay_per_crawl.django.PayPerCrawlMiddleware", *MIDDLEWARE] # near the top
PAY_PER_CRAWL = {"pay_to": "0xYourWalletOnBase", "mode": "gate", "price_usd": 0.01, "base_url": "https://example.com"}
Settings
All are Config(...) keyword arguments (or keys of PAY_PER_CRAWL).
| Setting | Default | Meaning |
|---|---|---|
pay_to |
required in gate mode | Your 0x wallet on Base. |
mode |
"monitor" |
"monitor" records only; "gate" answers 402. |
price_usd |
0.01 |
Price per page fetch, paid in USDC. |
facilitator_url |
PayAI (https://facilitator.payai.network, no key) |
Any x402 facilitator that supports Base. facilitator_auth sets an Authorization header if yours needs one. |
class_policy |
search/social/monitor free; seo/ai/automation/bot paid | Per class: "free", "paid" or "block". |
bot_overrides |
{} |
Per bot name, e.g. {"GPTBot": "free", "Bytespider": "block"}. |
allow_paths |
[] |
Path prefixes that are never gated. /robots.txt, /sitemap*, /llms.txt, /agents.txt, /.well-known/, /favicon.ico always are. |
base_url |
from the request | Your public origin. Set it behind a proxy or load balancer. |
trust_proxy_header |
"" |
Header with the real client IP ("CF-Connecting-IP", "X-Forwarded-For") so search-engine verification sees the right address. |
mpp_secret |
random per process | HMAC key for MPP challenge ids. Set a stable value if you run more than one worker. |
gated_methods |
("GET", "HEAD") |
HEAD is gated like GET (402 with no body). |
site_name, site_description, pages |
Used in the discovery files; pages is a list (or callable returning one) of (title, url) for llms.txt. |
|
on_event |
None |
Callback receiving a dict for every classified request (status, bot, class, rail, tx hash, payer). |
Bot classes: human, search, social, monitor, seo, ai, automation, bot. The catalog covers 85 crawlers and tools; see core/catalog.py.
Multiple worker processes: pass lock=FileNonceLock("/var/tmp/fmppc") to Gate(...) (and use PayPerCrawlMiddleware(app, gate=gate)) so a payment authorization cannot be replayed on another worker, and set a stable mpp_secret. The default lock is in-process memory.
Behind Cloudflare
If your site is proxied by Cloudflare, set AI Crawl Control (and any Bot Fight Mode / AI Labyrinth / "Block AI bots" rule) to Allow for the crawlers you want to charge. Cloudflare answers those requests at the edge before they ever reach your app, so the gate would never see them. Set trust_proxy_header="CF-Connecting-IP" and base_url to your public origin.
How a payment works
- A bot requests a page and gets
402with the price, asset (USDC on Base) and your wallet, in thePAYMENT-REQUIREDheader (x402 v2), the JSON body (x402 v1, withinvalidReasonwhen a payment was rejected) andWWW-Authenticate: Payment(MPP). - The bot signs a USDC transfer authorization (EIP-3009) and retries with it in
PAYMENT-SIGNATURE(v2),X-PAYMENT(v1) orAuthorization: Payment(MPP). - The middleware checks recipient, amount and expiry locally, locks the authorization nonce against replay, then posts it to the facilitator (
/verify, then/settle). The facilitator verifies and submits it on-chain; it never holds funds. v1 payments are forwarded in v1 shape. - The page is served only when a transaction hash comes back, with the receipt in
PAYMENT-RESPONSE,X-PAYMENT-RESPONSEand (MPP)Payment-Receipt.
The package never contacts ForgeMesh. Its only outbound requests are to the facilitator you chose, and only when a payment is presented. (The optional Pro reporter below is the one exception, and is off without a key.)
Money flow
The payer's USDC moves wallet to wallet on Base, from the payer to the pay_to address you configured. There is no ForgeMesh account, no custody, no settlement service and no revenue share: the site owner keeps 100%. The facilitator (PayAI by default) charges the seller no fee at the time of writing; check your facilitator's terms.
Using the pieces directly
from forgemesh_pay_per_crawl import Classifier, Config, Gate, Request, X402, build_challenge
Classifier().classify("Mozilla/5.0 (compatible; GPTBot/1.2)").cls # "ai"
gate = Gate(Config(pay_to="0x...", mode="gate"))
decision = gate.handle(Request(method="GET", path="/x", headers={"user-agent": "curl/8"}, host="example.com"))
decision.response # a Response (status, headers, body) to send, or None to serve the page
decision.headers # headers to add to the page you serve (payment receipt, Link)
Gate.handle is blocking (it can call the facilitator); in async code run it in a thread, as the ASGI middleware does. The facilitator transport is injectable (Gate(config, transport=fn) with fn(url, body, headers) -> dict).
Pro (optional)
With a ForgeMesh Pay-per-Crawl Pro license (pro_key="fmppc_...") the package reports daily bot rollups and settlements to the hosted dashboard at proxy.forgemesh.io and pulls the crawler-intel feed into the classifier. Nothing is sent without a key. The rollups are held in memory per process, so with several workers each reports its own counts.
Verified
- Unit and integration tests:
pytest(keccak vectors incl. block-boundary cases, classifier with rDNS, 402 shapes, MPP challenge/credential round trip, facilitator flow, replay, HEAD, file lock, Pro sync, all three adapters, and an integration matrix against live uvicorn/FastAPI and Flask servers with a mock facilitator). - Real settlements on Base mainnet through PayAI, October 2026, from the FastAPI example gated at $0.01: x402 v1 (tx) and x402 v2 (tx).
Development
python -m venv .venv && .venv/bin/pip install -e ".[test]"
.venv/bin/pytest
python -m build
MIT. Made by ForgeMesh Labs (hello@forgemesh.io).
Metadata
Release files for forgemesh-pay-per-crawl 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| forgemesh_pay_per_crawl-0.1.2.tar.gz | 43.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| forgemesh_pay_per_crawl-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 77.8 kB
Release files / forgemesh_pay_per_crawl-0.1.2.tar.gz
| Download URL | forgemesh_pay_per_crawl-0.1.2.tar.gz |
|---|---|
| Size | 43.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3bfe294523b044debea16e8509a7cff0d746faba8d627a46af510344851fc739
|
|
BLAKE2b-256 checksum How to use checksums |
820b014955c5da2b4d1d41a1417b5814cb5575ab51b118d28fd9de48a6d51cee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|
Release files / forgemesh_pay_per_crawl-0.1.2-py3-none-any.whl
| Download URL | forgemesh_pay_per_crawl-0.1.2-py3-none-any.whl |
|---|---|
| Size | 34.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cf00d21c597d5f8be8c1a97c81befe5d0efacfd0ea880c0fe25501c21b26e69d
|
|
BLAKE2b-256 checksum How to use checksums |
38183c1cbea58cbd622ca75532b39e106d4829241da993db8e2777bc15dda017
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.3
|