Observed since 20 Oct, 16:41 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
a402
ASGI middleware that gates FastAPI/Starlette endpoints behind a402 HTTP payments
ASGI middleware that gates FastAPI/Starlette endpoints behind a402 HTTP payments. The seller never handles keys or crypto — the facilitator does.
pip install a402
# With FastAPI extras:
pip install "a402[fastapi]"
import os
from fastapi import FastAPI
from a402 import A402Middleware, A402Config
app = FastAPI()
app.add_middleware(
A402Middleware,
# facilitator_url is optional — defaults to a402's hosted facilitator.
# Set it only to self-host or point at a staging facilitator.
config=A402Config(api_key=os.environ["A402_KEY"]),
routes=[
{"method": "GET", "path": "/v1/financials", "price": "$0.05"},
{"path": "/v1/quote", "price": "$0.01"}, # method defaults to GET
],
)
@app.get("/v1/financials")
async def get_financials():
return {"ticker": "NVDA", "revenue": "$60.9B"}
@pay decoratorAttach pricing metadata to a route function. Must still be listed in the routes dict.
from a402 import pay
@app.get("/v1/report")
@pay("$0.10")
async def get_report():
return {"report": "..."}
A request arrives with no X-PAYMENT header. The middleware calls POST /facilitator/challenge with the seller's API key and route price. The 402 response contains an accepts array describing the EIP-3009 USDC authorization the client must sign.
The client signs the authorization off-chain and retries with an X-PAYMENT header (base64-encoded JSON).
The middleware decodes the header and calls POST /facilitator/settle. On success (HTTP 200) the real handler runs and the response carries X-PAYMENT-RESPONSE (base64 settlement receipt). On 402/403 the status and body are forwarded to the client.
The settlement uses EIP-3009 (transferWithAuthorization) — the on-chain transfer is confirmed before the 200 is returned, so a 200 from the middleware means funds are settled on the source chain. Cross-chain consolidation via CCTP may still be in progress (check mintTxHash).
| Key | Type | Default |
|---|---|---|
price | "$0.05" or 0.05 | required |
description | str | "Access to METHOD /path" |
resource | str | "METHOD /path" |
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
httpxEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | httpx>=0.27 | runtime |
starletteEnvironment{
"extras": "",
"marker": null,
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | starlette>=0.27 | runtime |
fastapiEnvironment{
"extras": "",
"marker": "extra == \"fastapi\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | fastapi>=0.100; extra == "fastapi" | runtime |
pytestEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pytest>=8; extra == "dev" | runtime |
anyioEnvironment{
"extras": "trio",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | anyio[trio]; extra == "dev" | runtime |
httpxEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | httpx; extra == "dev" | runtime |
pytest-anyioEnvironment{
"extras": "",
"marker": "extra == \"dev\"",
"requires_python": ">=3.10",
"evaluation": "NOT_EVALUATED"
} | pytest-anyio; extra == "dev" | runtime |
bdist_wheel · 6.21 KB · Python py3
sdist · 9.34 KB · Python source
Observed since 20 Oct, 16:41 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:0aaf2cc01e4ffa44d55c1604772b9aed8607e06ad463fae32d62e2b83337e906