Docs › Self Hosting

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 engines field in package.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 own Dockerfile installs both)
  • Optional: OCR language data if you want image-ocr: node scripts/fetch-tessdata.js installs the pinned, hash-checked files into tessdata/ (or pass a directory and set OCR_TESSDATA_DIR); the project's own Dockerfile runs it at build time

Quick start

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

  1. Fork the repo on GitHub.
  2. Create a new Railway project from your fork.
  3. Add a persistent volume mounted at /data.
  4. Set environment variables in the Railway dashboard (see table below).
  5. Deploy. Railway builds the repository's Dockerfile.

Protect in-flight calls across redeploys: set RAILWAY_DEPLOYMENT_DRAINING_SECONDS=120 on 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 with PAYMENT_NETWORKS, each with its own payTo / facilitator setting) or solve a proof-of-work challenge for pure-CPU tools. You need WALLET_ADDRESS, a facilitator (CDP_API_KEY_ID + CDP_API_KEY_SECRET, or FACILITATOR_URL), and POW_SECRET at minimum. To also accept USDC on Monad (EVM chain 143), add monad to PAYMENT_NETWORKS (settles via MONAD_FACILITATOR_URL, default a public Monad facilitator). Avalanche (43114) and Sei (1329) are likewise opt-in via PAYMENT_NETWORKS (set PAYAI_API_KEY_ID + PAYAI_API_KEY_SECRET, the keys of the facilitator that settles them). Optimism (10) is opt-in via PAYMENT_NETWORKS plus SOLVADOR_KEY (the key of the facilitator that settles it; NETWORK_PRICE_PREMIUMS, a CAIP-2 keyed CSV such as eip155: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), add celo to PAYMENT_NETWORKS and set CELO_FACILITATOR_KEY (a self-service key minted at x402.celo.org; the facilitator's /settle requires it). Settles via CELO_FACILITATOR_URL, default the Celo-operated api.x402.celo.org. To accept USDG on Robinhood Chain, add robinhood to PAYMENT_NETWORKS and set ROBINHOOD_FACILITATOR_URL (one URL, or an ordered comma-separated list whose later entries are its settle fallback under PAYMENT_SETTLE_FALLBACK=true; FACILITATOR_GAS_CHECK=off and FACILITATOR_GAS_MIN_SETTLEMENTS tune the settlement-gas check; ROBINHOOD_RELAYER_KEY adds an optional settler you run yourself as the last fallback, see Robinhood-Chain). To accept USDC on Arc (EVM chain 5042), set CIRCLE_FACILITATOR_API_KEY (a Circle API key, optional): its presence is the switch, Arc then joins a multi-chain PAYMENT_NETWORKS list on its own, and it is offered only while Circle's facilitator /supported lists 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: returns 200 and 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.yml workflow 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