Self-Hosting
Payment wires: every paid endpoint accepts x402 and MPP (Machine Payments Protocol) on the same 402 - see Paying with x402 and Paying with MPP. Agent402 is the applied layer of Agentic Finance: agents that pay and get paid on their own.
Run Agent402 on your own infrastructure for full control over pricing, data, rate limits, and uptime.
Why self-host
- Privacy. Inputs and outputs stay on your infrastructure, except where a tool's job is to call out: URL fetches, and the upstream APIs whose keys you configure.
- No rate limits. You control concurrency, burst policies, and who gets access.
- Custom pricing. Set your own per-call prices or run everything free.
- Reliability. No dependency on a third-party host; deploy where your agents already run.
Prerequisites
- Node.js >= 22.22.2 (the
enginesfield inpackage.json; the hosted instance runs Node 22) - git
- Optional: Redis (response caching), Postgres (the state database via
STATE_DATABASE_URL, and analytics/call tracking) - Optional: Chromium + ffmpeg if you want browser/media tools (Chromium via
npx playwright install --with-deps chromium, ffmpeg from your OS package manager; the project's ownDockerfileinstalls both) - Optional: OCR language data if you want
image-ocr:node scripts/fetch-tessdata.jsinstalls the pinned, hash-checked files intotessdata/(or pass a directory and setOCR_TESSDATA_DIR); the project's ownDockerfileruns it at build time
Quick start
Manual (recommended)
git clone https://github.com/MikeyPetrillo/Agent402.git
cd Agent402
npm install
FREE_MODE=true npm start # everything runs free, no wallet needed
The server starts on port 3000 by default (PORT env var overrides).
Docker
The repository ships its own Dockerfile (Node 22, Chromium, ffmpeg and OCR data, running as a non-root user), so the browser, media and OCR tools work in the container:
docker build -t agent402 .
docker run -p 3000:3000 -e FREE_MODE=true agent402
For persistent state (stats, memory, PoW replay protection), mount a volume at /data.
Railway
- Fork the repo on GitHub.
- Create a new Railway project from your fork.
- Add a persistent volume mounted at
/data. - Set environment variables in the Railway dashboard (see table below).
- Deploy. Railway builds the repository's
Dockerfile.
Protect in-flight calls across redeploys: set
RAILWAY_DEPLOYMENT_DRAINING_SECONDS=120on the service (the hosted deploy job sets 120). Without a grace period between SIGTERM and SIGKILL, every redeploy kills in-flight requests. With it, the server's built-in graceful drain lets ordinary active requests finish (up to 75s) before exiting; report composites still running are cut off and answer 503. Settlement runs after the handler and only on a response under 400, so a request cut off this way is not charged on x402.
Environment variables
Set these on your host. None are committed to the repo.
| Variable | Required? | What it enables |
|---|---|---|
FREE_MODE |
No | Set true to serve all tools free (no paywall, no PoW gate) |
PORT |
No | HTTP listen port (default: 3000) |
WALLET_ADDRESS |
For paid mode | Your USDC receiving address (Base) |
WALLET_ENS |
No | ENS or Basename for display (e.g. agent402.base.eth) |
NETWORK |
For paid mode | Primary chain (default: base = Base mainnet; base-sepolia for testnet) |
PAYMENT_NETWORKS |
No | Comma-separated chains to accept (default: the primary network only), e.g. base,solana,polygon,arbitrum,stellar,algorand,monad,celo,avalanche,sei,optimism,robinhood; each extra chain needs its own payTo / facilitator settings (see below) |
CDP_API_KEY_ID |
For paid mode (or FACILITATOR_URL) |
Coinbase CDP API key ID (facilitator auth) |
CDP_API_KEY_SECRET |
For paid mode (or FACILITATOR_URL) |
Coinbase CDP API secret |
FACILITATOR_URL |
No | Custom x402 facilitator URL for the primary network, used when the CDP keys are unset (the CDP keys take precedence). With neither, only NETWORK=base-sepolia boots, on the x402.org testnet facilitator |
POW_SECRET |
For PoW tier | HMAC secret for signing PoW challenges |
BRAVE_API_KEY |
No | Enables search-kit tools (Web, News, Images) |
BRAVE_ANSWERS_API_KEY |
No | Distinct Brave subscription token for the answer tool; falls back to BRAVE_API_KEY |
BRAVE_SUGGEST_API_KEY |
No | Distinct Brave subscription token for search-suggest; falls back to BRAVE_API_KEY |
NEYNAR_API_KEY |
No | Enables Farcaster tools (Neynar API); falls back to WARPCAST_API_KEY |
FRED_API_KEY |
No | Enables macro-kit v1 (FRED economic data) |
FRED_API_KEY_V2 |
No | Distinct key for macro-kit v2 bulk endpoints |
OPENROUTER_API_KEY |
No | Enables the /v1 LLM gateway tiers (chat, metered, embeddings, rerank, images, speech); the routes answer 503 without it |
OPENAI_API_KEY |
No | Enables the older /api/llm*, /api/image-gen*, /api/transcribe* and /api/embed* proxies |
E2B_API_KEY |
No | Enables the /api/code-run* sandbox tools |
REDIS_URL |
No | Enables Redis: response caching (see below), and, across replicas, the shared rate limits, breakers and budgets (SHARED_LIMITS=off turns those off), the payment replay guard and idempotency store. A plain redis:// URL to a public host is refused at boot |
STATE_DATABASE_URL |
No | Postgres connection string for the state database; unset keeps state in SQLite and files on /data |
RATE_LIMIT_REPLICAS |
With replicas | How many server replicas share the limits; set it to the replica count (default 1) |
INDEX_PULL_MS |
No | How often each replica pulls the seller index rows another wrote (default 30000) |
TEMPO_RELAY |
No | off settles Tempo payments in the server itself (verify, simulate, broadcast over TEMPO_RPC_URL) and screens every party against OFAC's SDN list; unset uses Tempo's hosted relay |
JEV_MODEL |
No | The Jev version the judgment routes pin to (default jev-1.13.0); TYPESAFE_MODEL overrides it for the internal callers |
E2B_MAX_CONCURRENT |
No | Sandboxes running at once for the whole service (default 8), split across replicas |
DATABENTO_SUMMARY |
No | off makes the stock tools read the three-venue feed instead of the end-of-day summary dataset |
MARKET_TICK |
No | on lists GET /api/market/tick, a cached per-symbol read of the figures crypto-price and stock-quote already serve (no source of its own; MARKET_TICK_CACHE_MS sets the window, default 15000). Off by default |
GATEWAY_NANOPAYMENTS |
No | on adds the Circle Gateway batched option (below). Off by default |
GATEWAY_NANOPAYMENT_NETWORKS |
No | Which networks carry the batched option: arc, base or both (default arc,base); a network must also be in the plain offer |
GATEWAY_WITHDRAW_ALERT_USD |
No | Gateway balance, in USD, at which gatewayBalance reads withdraw (default 25) |
GATEWAY_PENDING_UNRESOLVED_HOURS |
No | How long a batched payment Gateway left pending may stay unresolved before gatewayPending raises it (default 24) |
GATEWAY_SETTLE_BREAKER_MAX, GATEWAY_SETTLE_BREAKER_WINDOW_MS, GATEWAY_SETTLE_BREAKER_MIN_FAIL_SHARE, GATEWAY_SETTLE_BREAKER_HARD_MAX, GATEWAY_SETTLE_BREAKER_GLOBAL_MAX |
No | The settle-failure breaker on wallet-only routes and /v1: a wallet is refused after _MAX settle failures (default 3) inside the window (default 15 minutes) only while failures are more than _MIN_FAIL_SHARE of its outcomes there (default 0.5), and after _HARD_MAX failures (default 40) whatever its settled count; _GLOBAL_MAX distinct failing buyers (default 12) pause every /v1 tier |
X402_UPTO_NETWORKS |
No | all or a CSV of CAIP-2 ids that also offer the upto scheme; a facilitator must advertise it for that network |
BASE_DENIM_ACTIVATION_TIMESTAMP |
No | When Base's shorter block time takes effect (ISO time); payment windows are counted in seconds either side of it |
ANALYTICS_DATABASE_URL |
No | Postgres connection string for analytics; falls back to DATABASE_URL |
GLAMA_MAINTAINER_EMAIL |
No | Email returned at /.well-known/glama.json |
MPP_SECRET_KEY |
For MPP | HMAC secret binding MPP challenges; presence mounts the MPP dual-stack shim (WWW-Authenticate: Payment on every 402, evm method settled through your x402 facilitator). Unset = pure x402 |
MPP_CHALLENGE_NETWORKS |
No | all or a CSV of chain ids that get MPP evm challenges (default Base + Celo) |
MPP_EVM_DOMAIN_FALLBACK |
No | off disarms the wrong-EIP-712-domain detection. On by default: a credential signed under a different known token-domain name than the one your accepts entry advertises is refused locally with an RFC 9457 problem, and MPP challenges are withheld from that client briefly so an MPP-preferring wallet falls through to your x402 offer instead of looping. MPP_EVM_DOMAIN_FALLBACK_TTL_MS (30 min) and MPP_EVM_DOMAIN_FALLBACK_MAX_RESPONSES (5) bound the hold |
TEMPO_API_KEY |
For native Tempo (relay mode) | Tempo MPP relay key (needs the mpp:write scope; not needed with TEMPO_RELAY=off); with a recipient it offers tempo/charge challenges settled natively on Tempo |
TEMPO_RECIPIENT_ADDRESS |
No | Tempo payTo (defaults to WALLET_ADDRESS) |
TEMPO_CURRENCY |
No | CSV of TIP-20 token addresses to offer (first = preferred; default PathUSD, the hosted instance offers USDC.e then PathUSD) |
STRIPE_SECRET_KEY |
For card paths | Rollout switch for the human front door: /reports (card checkout), /monitors (subscriptions), /credits (prepaid credits) and the Authorization: Bearer a402_… credits gate. Unset = none of it is mounted; the /v1 report routes still sell over x402 / MPP |
CREDITS_SALES |
No | on opens new prepaid credit sales at /credits. Off by default: the checkout answers 503, and existing keys keep working either way |
STRIPE_WEBHOOK_SECRET |
With Stripe | Signing secret for the Stripe webhook endpoint (subscription status, invoices, credits purchases, refunds and disputes). The webhook is verified only when set |
STRIPE_PROFILE_ID |
No | With STRIPE_SECRET_KEY, mounts cards over the MPP wire (stripe/charge via Shared Payment Tokens) on routes priced $0.50 or more |
STRIPE_AUTOMATIC_TAX |
No | true adds Stripe Tax to every Checkout Session (enable Stripe Tax in the dashboard first) |
EMAIL_FROM |
For emails | Verified sender address; with a provider key below, report links, monitor reports and credits keys are emailed |
ZEPTOMAIL_TOKEN |
No | Zoho ZeptoMail send-mail token (with or without the Zoho-enczapikey prefix); ZEPTOMAIL_URL overrides the regional API base |
RESEND_API_KEY |
No | Resend API key, the alternative email provider (ZeptoMail wins when both are set) |
MONITOR_SCHEDULER |
No | off disarms the monitor fulfilment timer (manual operator runs still work) |
Free mode vs paid mode
FREE_MODE=true: every tool responds without payment. Good for development, internal deployments, or self-hosted agents that don't need metering. The PoW gate and x402 paywall are both disabled.- Without
FREE_MODE: the x402 paywall activates. Callers pay per request (USDC on Base by default; add Solana, Polygon, Arbitrum, Stellar, Algorand and the chains below withPAYMENT_NETWORKS, each with its own payTo / facilitator setting) or solve a proof-of-work challenge for pure-CPU tools. You needWALLET_ADDRESS, a facilitator (CDP_API_KEY_ID+CDP_API_KEY_SECRET, orFACILITATOR_URL), andPOW_SECRETat minimum. To also accept USDC on Monad (EVM chain 143), addmonadtoPAYMENT_NETWORKS(settles viaMONAD_FACILITATOR_URL, default a public Monad facilitator). Avalanche (43114) and Sei (1329) are likewise opt-in viaPAYMENT_NETWORKS(setPAYAI_API_KEY_ID+PAYAI_API_KEY_SECRET, the keys of the facilitator that settles them). Optimism (10) is opt-in viaPAYMENT_NETWORKSplusSOLVADOR_KEY(the key of the facilitator that settles it;NETWORK_PRICE_PREMIUMS, a CAIP-2 keyed CSV such aseip155:10=<usd>, adds a per-chain amount to that chain's quote if your facilitator charges per settlement). To also accept USDC on Celo (EVM chain 42220), addcelotoPAYMENT_NETWORKSand setCELO_FACILITATOR_KEY(a self-service key minted at x402.celo.org; the facilitator's/settlerequires it). Settles viaCELO_FACILITATOR_URL, default the Celo-operatedapi.x402.celo.org. To accept USDG on Robinhood Chain, addrobinhoodtoPAYMENT_NETWORKSand setROBINHOOD_FACILITATOR_URL(one URL, or an ordered comma-separated list whose later entries are its settle fallback underPAYMENT_SETTLE_FALLBACK=true;FACILITATOR_GAS_CHECK=offandFACILITATOR_GAS_MIN_SETTLEMENTStune the settlement-gas check;ROBINHOOD_RELAYER_KEYadds an optional settler you run yourself as the last fallback, see Robinhood-Chain). To accept USDC on Arc (EVM chain 5042), setCIRCLE_FACILITATOR_API_KEY(a Circle API key, optional): its presence is the switch, Arc then joins a multi-chainPAYMENT_NETWORKSlist on its own, and it is offered only while Circle's facilitator/supportedlists it.ARC_CONTRACT_WALLETS=on(optional, off by default) also accepts Arc payments signed by a contract wallet (ERC-1271, or ERC-6492 for one not yet deployed), verified on chain against the Arc RPC before Circle sees them; identity-bound routes (memory, attest, feedback, my-usage, receipts) still take an EOA signature only.
Circle Gateway batched option (optional): set GATEWAY_NANOPAYMENTS=on and the 402 on Arc and Base (GATEWAY_NANOPAYMENT_NETWORKS) also carries a second exact accept whose extra.name is GatewayWalletBatched, at the same price and to the same payTo. A network is offered only while it is in the plain offer and Circle Gateway's /v1/x402/supported lists its batched kind at boot (fail closed: an unreadable answer drops the option and nothing else). Gateway verifies and settles these payments off chain, keyless, and credits them to your Gateway balance, not to your wallet on chain: sales are booked with wire gateway-batched and Gateway's transfer id as their tx, and /revenue counts them from the sales ledger under that label. A payment Gateway leaves pending is answered 402 with Retry-After, kept in a pending book, and served on the buyer's resend of the same payment once Gateway confirms it; a resolver reads Gateway's record for each one every ten minutes and books one that settled and was never served as owed. Move the balance on chain with node scripts/gateway-withdraw.js (human-run, GATEWAY_SELLER_KEY in that shell only, never a server variable): it is a dry run that prints the plan unless you pass --execute, takes --from arc|base for the source domain (required with --amount), --to, --recipient (default your own address) and --max-fee, and mints on the destination chain. The withdrawal's mint is not counted as revenue again. Status words on /api/gateway-status: gatewayBalance (off, unknown, ok, or withdraw once the balance reaches GATEWAY_WITHDRAW_ALERT_USD), gatewayPending (off, ok, pending, near_capacity, full, or unresolved for a payment pending past GATEWAY_PENDING_UNRESOLVED_HOURS) and, for every rail, buyerRefusals (refusing for a while after the settle breaker refused a wallet that had settled a call inside its window, ok otherwise; BUYER_REFUSAL_WINDOW_MS). Words only; the figures are on the operator endpoints.
MPP on the same 402: set MPP_SECRET_KEY and every paid route also answers WWW-Authenticate: Payment (the evm method settles through your existing facilitator); add TEMPO_API_KEY for native Tempo settlement. Cards: STRIPE_SECRET_KEY (+ STRIPE_WEBHOOK_SECRET) mounts the report checkout, monitors and prepaid credits; STRIPE_PROFILE_ID adds cards over MPP. Email: EMAIL_FROM plus ZEPTOMAIL_TOKEN or RESEND_API_KEY; without them the card pages still work and the report link is shown on the page.
See Paying with x402, Paying with MPP, Paying with Compute and Reports and Monitors for the buyer-side flows.
Optional infrastructure
Redis (response caching)
Set REDIS_URL to enable response caching for eligible routes. The server defines a CACHEABLE_ROUTES set internally: deterministic, read-only tools whose output can be safely replayed. Cache is env-gated: no REDIS_URL, no caching, no behavior change.
Postgres (analytics)
Set ANALYTICS_DATABASE_URL (or DATABASE_URL) to enable call-level analytics tracking. This records per-tool call counts, latency, and error rates. Also env-gated: without the variable, analytics is a silent no-op.
State database (Postgres) or SQLite (built-in)
Set STATE_DATABASE_URL to a Postgres connection string to keep the server's state (documents, records, logs, memory namespaces, PoW replay, and the leases every scheduled loop runs under) in a state schema (STATE_DB_SCHEMA renames it). This is what lets two containers overlap during a deploy, and it is how the hosted instance runs. The file stores are imported once on first boot with it set.
Without STATE_DATABASE_URL, stats, memory namespaces, and PoW replay protection use SQLite (better-sqlite3, WAL mode) stored in /data, and the other stores are files there. This works out of the box with no configuration: just ensure the /data directory is writable and persistent across deploys.
Health checks
GET /health: returns200and a deliberately small public body:{ ok, meta: { toolCount, build } }. Process uptime and the operating-mode/feature flags are operator-only and appear only on the token-authenticated response. Use this path as your load-balancer or container health probe.- CI heartbeat: the repo's
heartbeat.ymlworkflow probes the hosted instance every 15 minutes and auto-opens a GitHub issue on failure. You can adapt the same workflow for your own deployment.
Verifying your deployment
# Smoke test: every tool should answer its own documented example
FREE_MODE=true PORT=3000 node src/server.js &
TARGET_URL=http://localhost:3000 node scripts/test-all.js
See also
- Getting Started: your first call in 60 seconds
- Architecture: how the server, paywall, and facilitators fit together
- Security Model: SSRF defense, PoW scoping, wallet-only tools
- Operations: CI pipeline, heartbeat watchdog, deploys