Meteria402 is a Cloudflare Workers gateway that turns AI API usage into x402 payments. It exposes OpenAI-compatible and provider-native routes through Cloudflare AI Gateway, tracks usage in D1, gates requests with deposit-backed accounts and invoices, and supports wallet-based login plus scoped autopay settlement.
The intended flow is:
Client / provider SDK
-> /v1/*, /compat/*, or provider-native routes
-> Meteria402 account and invoice checks
-> Cloudflare AI Gateway provider endpoint
-> usage-based invoice creation
-> x402 invoice payment before the next request
This implementation includes:
- Anonymous deposit quote and settlement endpoints.
- API key generation after a settled deposit.
- D1-backed accounts, API keys, requests, invoices, payments, and append-only ledger entries.
- Provider SDK endpoint proxying through Cloudflare AI Gateway.
- Usage-based invoice creation after successful requests.
- Blocking of new model requests when an unpaid invoice exists.
- Optional autopay-worker integration for deposit and invoice settlement.
- A minimal same-origin
/consolepage for deposit setup and account inspection.
D1 is the billing source of truth. Durable Objects are used only for short-lived login coordination and account request gates.
/— Home page with gateway overview./console— Account dashboard for deposits, API keys, usage, invoices, and autopay./login— Owner-wallet login on the main site, with injected-wallet signing or scan login./pay-deposit— Standalone deposit payment page for wallets that do not exposewindow.ethereum(mobile / non-ETH browsers). Deep-link or QR-code based.
When unset, the Worker defaults to Coinbase CDP's hosted x402 facilitator and Base mainnet:
X402_FACILITATOR_URL = "https://api.cdp.coinbase.com/platform/v2/x402"
X402_NETWORK = "eip155:8453"
X402_ASSET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"That network is Base mainnet, and the asset is native USDC on Base.
Set your recipient wallet before using real payments:
wrangler secret put X402_RECIPIENT_ADDRESSIf your facilitator requires authentication, set one of:
# For CDP JWT authentication (preferred)
wrangler secret put CDP_API_KEY_ID
wrangler secret put CDP_API_KEY_SECRET
# Or for legacy token auth
wrangler secret put X402_FACILITATOR_AUTH_TOKENInstall dependencies:
npm installCreate a D1 database:
wrangler d1 create meteria402Copy the returned database ID into wrangler.toml, then apply migrations:
npm run db:migrate:localFor a deployed D1 database, apply the same migrations remotely:
npm run db:migrate:remoteSet required secrets for deployed usage:
wrangler secret put CLOUDFLARE_ACCOUNT_ID
wrangler secret put CLOUDFLARE_API_TOKEN
wrangler secret put AI_GATEWAY_API_KEY
wrangler secret put X402_RECIPIENT_ADDRESS
wrangler secret put X402_RECIPIENT_PRIVATE_KEYCLOUDFLARE_API_TOKEN is used only for AI Gateway log reconciliation and needs
Cloudflare AI Gateway read access for the configured account/gateway.
Billing costs are multiplied by BILLING_COST_MULTIPLIER before invoices are
created. This is intended to cover card processing fees and similar transaction
costs. If unset, the Worker defaults to 1.055.
For a gateway that requires Cloudflare AI Gateway authentication, also set:
wrangler secret put AI_GATEWAY_AUTH_TOKENTo prefill /console with a default payment worker URL at frontend build time, set:
export VITE_DEFAULT_AUTOPAY_URL="https://autopay.example.com"For autopay pre-authorizations, the main Worker signs each /api/pay request
with an EIP-712 requester wallet proof. X402_RECIPIENT_PRIVATE_KEY must match
X402_RECIPIENT_ADDRESS; it identifies the main site's recipient/refund wallet
and is not the user's owner wallet or the autopay payer hot wallet. Set
AUTOPAY_REQUESTER_ORIGIN when background jobs need to use stored autopay
capabilities outside an incoming request, and optionally set
AUTOPAY_REQUESTER_NAME for the label shown on the autopay authorization page.
The same recipient private key is also used to derive the HMAC key for login,
session, quote, and autopay state tokens.
Run locally:
npm run devnpm run dev and npm run deploy build the React console into dist/client before starting or deploying the Worker. API routes still run through the Worker, while /console is served as a single-page app.
Open:
http://localhost:8787/console
Local development can bypass facilitator settlement by setting:
ALLOW_DEV_PAYMENTS = "true"
DEV_PAYMENT_PROOF = "<local random proof>"Then /console can settle a quote using:
{ "dev_proof": "<local random proof>" }Do not enable development payments in production. The proof must be a local secret; there is no built-in fixed proof string.
This is the main route surface exposed by the Worker.
GET /api/session— Returns the current session identity (owner address and expiry) ornull.POST /api/login/challenge— Creates a short-lived main-site wallet login challenge.POST /api/login/complete— Verifies the wallet signature and sets the session cookie.POST /api/login/scan/start— Creates a QR/deeplink login request backed by a Durable Object.GET /api/login/scan/:id/details— Get public scan-login request details.GET /api/login/scan/:id/events— Hibernatable WebSocket stream for scan-login status.POST /api/login/scan/:id/challenge— Creates the wallet-page challenge for a scan login.POST /api/login/scan/:id/approve— Verifies the wallet-page signature and approves the scan login.POST /api/login/scan/:id/deny— Rejects a pending scan login request.POST /api/login/scan/:id/complete— Completes the desktop browser login after approval.POST /api/logout— Clears the session cookie.POST /api/session/autopay— Updates the account autopay endpoint stored in D1.
POST /api/deposits/quote— Create a refundable deposit quote.GET /api/deposits/intent— Load a signed deposit intent for standalone payment pages.POST /api/deposits/settle— Settle a deposit via x402 (browser wallet, mobile deep-link, or dev bypass).POST /api/deposits/:id/autopay/start— Start autopay settlement for a deposit quote.POST /api/deposits/:id/autopay/complete— Complete autopay settlement for a deposit quote.GET /api/deposits— List your deposit history.
GET /api/account— Get deposit balance, unpaid invoice total, and status.PATCH /api/account— Update account settings, including minimum autopay recharge amount.POST /api/account/owner-rebind/challenge— Create an owner-wallet rebind challenge.POST /api/account/owner-rebind/complete— Complete an owner-wallet rebind.GET /api/api-keys— List API keys.POST /api/api-keys— Create a new API key.POST /api/api-keys/:id/disable— Disable an API key.POST /api/api-keys/:id/enable— Re-enable a disabled API key.DELETE /api/api-keys/:id— Soft-delete an API key.GET /api/invoices— List invoices.GET /api/requests— List model calls / usage records.POST /api/reconcile— Manually trigger pending AI Gateway usage reconciliation.POST /api/refund— Request a refund settlement.
POST /api/invoices/:id/pay/quote— Create a payment quote for an unpaid invoice.POST /api/invoices/:id/pay/settle— Settle an invoice payment.POST /api/invoices/:id/pay/autopay/start— Start autopay invoice settlement.POST /api/invoices/:id/pay/autopay/complete— Complete autopay invoice settlement.
GET /api/autopay/capabilities— List scoped autopay authorizations (limits, remaining budget, expiry).POST /api/autopay/capabilities— Create a new scoped autopay limit.DELETE /api/autopay/capabilities/:id— Revoke an autopay authorization.POST /api/autopay/capabilities/:id/complete— Complete the approval after wallet signature.GET /api/autopay-wallet/balance— Query the autopay wallet address and USDC balance.
POST /v1/*— OpenAI native endpoints. Proxied to Cloudflare AI Gateway/openai/*.POST /compat/*— Cloudflare unified OpenAI-compatible endpoints. Proxied to AI Gateway/compat/*.POST /anthropic/*,/google-ai-studio/*,/openrouter/*,/mistral/*,/groq/*,/deepseek/*,/perplexity/*,/grok/*,/workers-ai/*,/azure-openai/*,/cohere/*,/replicate/*, and/huggingface/*— provider-native Gateway endpoints.GETrequests under these provider paths are proxied without creating usage invoices.GET /health— Service health check.GET /api/config— Public frontend configuration (min deposit, asset decimals, etc.).
Use the generated API key with any OpenAI-compatible client:
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "mia2_xxx",
baseURL: "https://your-worker.example.com/v1",
});
const response = await client.chat.completions.create({
model: "gpt-5-mini",
messages: [{ role: "user", content: "Hello" }],
});For the Cloudflare unified OpenAI-compatible endpoint, use:
const client = new OpenAI({
apiKey: "mia2_xxx",
baseURL: "https://your-worker.example.com/compat",
});
await client.chat.completions.create({
model: "openai/gpt-5-mini",
messages: [{ role: "user", content: "Hello" }],
});If the previous request created an unpaid invoice, the next model request returns:
{
"error": {
"type": "payment_required",
"code": "unpaid_invoice",
"message": "An unpaid invoice must be paid before making another request."
}
}- API keys are shown only once.
- Account ownership is controlled by the owner wallet stored as
owner_address, not by API keys. If an API key is lost, sign in with the owner wallet and create a new key from/console. - Owner-wallet loss recovery is not implemented. Rebinding currently requires the existing owner wallet to sign the change.
- Streaming responses are proxied through without buffering; billing relies on Cloudflare AI Gateway log reconciliation.
- Successful metered requests are marked
pending_reconcilefirst. Billing is delayed until the Worker can read the Cloudflare AI Gateway log cost, then the request is settled and an invoice is created. - AI Gateway log reconciliation runs shortly after the response with
waitUntilretries and is swept again by the scheduled Worker trigger.