Skip to main content

aifinpay-gate (Python)

Accept payments from AI agents in a Python API. When an agent calls a paid route without paying, the gate answers HTTP 402 with everything it needs to buy a batch of calls; the agent settles on-chain from its own wallet (you receive 99%, AiFinPay 1%, non-custodial) and retries with an AIFP-Receipt. The gate verifies that receipt locally — an Ed25519 signature against the AiFinPay JWKS, no call to AiFinPay per request — and meters the batch.

A port of @aifinpay/gate: the same 402 body, the same checks and the same refusal sentences, held to the Node package by recorded fixtures (tests/test_parity.py).

pip install aifinpay-gate            # PyNaCl is the only dependency
pip install "aifinpay-gate[redis]"   # shared counters across processes

Register

curl -s -X POST https://api.aifinpay.io/v1/merchants -H 'content-type: application/json' \
  -d '{"name":"My API","pay_to":{"evm":"0xYourPayoutWallet"},"settlement_version":"1.4"}'

Keep merchant_id and the one-time merchant_secret, then claim the merchant at https://dash.aifinpay.io with the secret to see payments. One pay_to.evm receives on every EVM network.

FastAPI / Starlette (ASGI)

from fastapi import FastAPI, Request
from aifinpay_gate import AifpGateMiddleware, Gate, Route

gate = Gate("mrch_…", routes=[
    Route("/create", "premium", methods={"POST"}),   # $0.005 per call
    Route("/api/*"),                                  # the section and everything under it
])
app = FastAPI()
app.add_middleware(AifpGateMiddleware, gate=gate)

@app.post("/create")
async def create(request: Request):
    paid = request.state.aifp      # {"agent", "receipt_id", "used", "remaining", ...}
    return {"ok": True, "remaining": paid["remaining"]}

Flask / Django (WSGI)

from aifinpay_gate import AifpGateWSGI, Gate, Route

app.wsgi_app = AifpGateWSGI(app.wsgi_app, Gate("mrch_…", routes=[Route("/api/*")]))
# in a view: request.environ["aifp"]

Both adapters also serve /.well-known/x402.json so agents discover the paid routes before they hit one (serve_discovery=False to turn it off).

Any other framework

from aifinpay_gate import Gate, SimpleRequest

result = gate.decide(SimpleRequest(path, headers, method))
if result is None:        # not a paid route
    ...
elif not result.ok:       # 402 / 403 / 503 — send result.status, result.headers, result.body as JSON
    ...
else:                     # paid: add result.headers to your response; result.aifp is the metering context
    ...

Routes

Route(pattern, tier="standard", weight=None, methods=None, paywall=True)

  • pattern is exact, or ends in /* — /api/* covers /api and everything under /api/. The longest matching pattern wins. This is the hosted gateway's matcher; wildcards in the middle (/backtest/*/bid) are refused.
  • tier: standard $0.0005, complex $0.002, premium $0.005 per call (weights 1, 4, 10 billing units). weight overrides the units per call.
  • paywall=False serves the route free and meters nothing.
  • Paths no route matches pass through untouched.

Gate(merchant_id, resource="/api/search", tier="complex") is the single-mount form: every request handed to it is charged against that one resource.

Production: share the counters

The default MemoryStore meters per process. Under gunicorn/uvicorn with several workers, or several pods, each gets a full copy of every batch — a 200-unit batch serves up to 200 × workers calls. Use Redis:

import redis
from aifinpay_gate import Gate, RedisStore

gate = Gate("mrch_…", routes=[...], store=RedisStore(redis.Redis.from_url(REDIS_URL)))

The store increments atomically and compares after the increment, so concurrent requests can never overspend a batch; the counter's TTL is set once and expires with the receipt.

Options

Option Default
should_charge everyone pays known_ai_agent for content sites: human browsers read free and never see a 402; self-identifying AI crawlers and anything speaking AIFP pay. A predicate that raises charges.
allow(ctx) — Your veto after verification, before metering (a refused call costs nothing). Return False → 403.
on_store_error "closed" "open" serves un-metered calls when the store is down (AIFP-Meter: degraded).
require_agent_match False Refuse when AIFP-Agent-Id differs from the receipt subject (anti-accident, not anti-theft).
replay "auto" Single-use receipts get a one-shot nonce check.
jwks fetched Pin the key set and skip network I/O (redeploy on key rotation).
on_event(e) — 402 / serve / 403 / meter_error events for your metrics.
refund_on_error (adapters) False Give the units back when your handler answers 5xx.

What it guarantees

  • Fails closed: if the JWKS cannot be reached, paid routes answer 503, never free.
  • Only EdDSA receipts from https://api.aifinpay.io with aud = your merchant id.
  • Only quota receipts are spendable; per-call billing receipts are refused.
  • An expired receipt gets a 402 (buy again), a wrong one a 403 with a reason.
  • Stricter than the Node gate in two places: a receipt without a numeric exp, or without a usable unit_quota, is refused.

Not included, on purpose: free allowances, daily caps and per-agent blocks live in the AiFinPay dashboard, so there is one source of truth for each rule.

Opt-in dashboard request reporting (0.1.2)

Use one GateReporter per merchant per worker, created after worker fork. It uses only Python's standard library. Add its callback to your existing gate; keep the gate's shared Redis quota store and verification settings.

import os
from aifinpay_gate import GateReporter, Gate, Route, RedisStore

merchant_id = os.environ["AIFP_MERCHANT_ID"]
reporter = GateReporter(
    merchant_id=merchant_id,
    merchant_secret=os.environ["AIFP_MERCHANT_SECRET"],
)
gate = Gate(
    merchant_id=merchant_id,
    routes=[Route(pattern="/api/agent/genres/list", tier="standard")],
    store=RedisStore(redis_client),
    on_event=reporter.on_event,
)
# In your existing worker shutdown hook, after stopping new requests:
reporter.close(timeout=10.0)

Register the identical static or prefix route pattern in the dashboard first. Routes mode emits the matched Route.pattern; a single-resource gate must set resource explicitly. Full URLs, queries, fragments, encoded or invalid paths are rejected instead of rewritten. The API rejects unregistered patterns. Keep the merchant secret server-side. Share the reporter across mounts; do not report the same event through multiple reporters or through both hosted and self-hosted analytics. If you already have a callback, compose the two hooks.

Only 402 and gate-admitted serve events are reported; exempt humans and other event kinds are excluded. serve is admission before your handler, not proof of successful HTTP response or delivered content. The payload has only UUID, kind, registered path and UTC timestamp; no headers, receipts, payer IDs, IPs or customer content. This populates request counters, not geographic/user-agent charts or general website visits. Settlements still supply payments/revenue; purchased quota is never backfilled as actual usage.

api_base accepts an HTTPS origin only (default https://api.aifinpay.io); the reporting API must be deployed before enabling this hook. Redirects are blocked before a second request, including302, so secrets stay at that origin. on_event is synchronous enqueue only. The background daemon thread sends batches of at most 50 with a 3 second socket I/O timeout; it never performs HTTP on the gate thread. The memory queue includes in-flight events, holds at most 1000, and drops new events when full. UUIDs and timestamps stay identical on transient retries (at most 5 attempts/15 minutes). Permanent 4xx except 408/429, or redirects, stop reporting and discard the queue. Fix configuration or credentials before creating a replacement reporter.

reporter.stats returns queued, delivered (API acknowledgements including retry duplicates), dropped, retries, last_error (sanitized code), stopped and closed. Monitor these in your own service. An abrupt process stop loses unsent telemetry; reporting failures never alter verification or quotas. reporter.flush(timeout=10.0) requests one attempt per queued event and waits at most the deadline; transient failures remain for background retries. reporter.close(timeout=10.0) stops acceptance, flushes within that deadline, drops unsent events and tells the worker to stop. An already in-flight socket operation may finish later, but it cannot start another request. Neither method promises delivery. A reporter inherited across fork refuses to report and exposes wrong_process; construct a fresh reporter in each worker.

Metadata

Release files for aifinpay-gate 0.1.3

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

Source distribution (sdist)

Source distribution for aifinpay-gate 0.1.3
File Size Uploaded
aifinpay_gate-0.1.3.tar.gz 61.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aifinpay-gate 0.1.3
File Interpreter ABI Platform
aifinpay_gate-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 97.8 kB

Release files / aifinpay_gate-0.1.3.tar.gz

Download URL aifinpay_gate-0.1.3.tar.gz
Size 61.4 kB
Tags Source
SHA-256 checksum
How to use checksums
be38cabcf2f6a49bf221ebe84ead982760a76d2c5b0ece8da8c53e56d017a975
BLAKE2b-256 checksum
How to use checksums
6d06a2049a485c778a62ca8e5f5d0f51e1e5f1fd10ab75aa645b0076944916c4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.7

Release files / aifinpay_gate-0.1.3-py3-none-any.whl

Download URL aifinpay_gate-0.1.3-py3-none-any.whl
Size 36.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1dd19eb0af84153fc708aa6d1b16318c9de66c70642d5d085202aaaa1dd0bd4d
BLAKE2b-256 checksum
How to use checksums
7bf23ad445acd413673ccee1408b7149fa375c40471f87682f0813792d8858d9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.7

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

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