paygate-mcp
v9.2.0
Published
Pay-per-tool-call gating proxy for MCP servers. Wrap any MCP server with API key auth, per-tool pricing, rate limiting, and usage metering.
Maintainers
Readme
paygate-mcp
Monetize any MCP server with one command. Add API key auth, per-tool pricing, rate limiting, and usage metering to any Model Context Protocol server. Zero dependencies. Zero config. Zero code changes.
Table of Contents
- Quick Start
- What It Does
- Usage — Local stdio, remote HTTP, multi-server, client SDK
- API Reference — All 140+ endpoints
- CLI Options
- Deployment — Docker, docker-compose, systemd, PM2
- Load Testing — k6 benchmarking for production
- Error Codes — Complete error code reference
- Feature Reference — Detailed docs for every feature
- Programmatic API
- Security
- Tested With — Verified against popular MCP servers
- Current Limitations
- Roadmap
- Requirements
- License
Quick Start
# Wrap a local MCP server (stdio transport)
npx paygate-mcp wrap --server "npx @modelcontextprotocol/server-filesystem /tmp"
# Gate a remote MCP server (Streamable HTTP transport)
npx paygate-mcp wrap --remote-url "https://my-server.example.com/mcp" --price 5That's it. Your MCP server is now gated behind API keys with credit-based billing.
What It Does
PayGate sits between AI agents and your MCP server:
Agent → PayGate (auth + billing) → Your MCP Server (stdio or HTTP)- API Key Auth — Clients need a valid
X-API-Keyto call tools - Credit Billing — Each tool call costs credits (configurable per-tool)
- Rate Limiting — Sliding window per-key rate limits + per-tool rate limits
- Usage Metering — Track who called what, when, and how much they spent
- Multi-Server Mode — Wrap N MCP servers behind one PayGate with tool prefix routing
- Client SDK —
PayGateClientwith auto 402 retry, balance tracking, and typed errors - Two Transports — Wrap local servers via stdio or remote servers via Streamable HTTP
- Per-Tool ACL — Whitelist/blacklist tools per API key (enterprise access control)
- Per-Tool Rate Limits — Independent rate limits per tool, not just global
- Key Expiry (TTL) — Auto-expire API keys after a set time
- Spending Limits — Cap total spend per API key to prevent runaway costs
- Usage Quotas — Daily/monthly call and credit limits per key (with UTC auto-reset)
- Dynamic Pricing — Charge extra credits based on input size (
creditsPerKbInput) - OAuth 2.1 — Full authorization server with PKCE, client registration, Bearer tokens
- SSE Streaming — Full MCP Streamable HTTP transport (POST SSE, GET notifications, DELETE sessions)
- Audit Log — Structured audit trail with retention policies, query API, CSV/JSON export
- Registry/Discovery — Agent-discoverable pricing via
/.well-known/mcp-payment,/pricing, and/.well-known/mcp.jsonidentity card - OpenAPI 3.1 + Interactive Docs — Auto-generated spec at
/openapi.json, Swagger UI at/docs— all 140+ endpoints documented - Public Endpoint Rate Limiting — Configurable per-IP rate limit (default 300/min) on
/health,/info,/pricing,/docs,/openapi.json,/.well-known/*,/robots.txt,/— 429 with Retry-After header - Robots.txt + HEAD Support — Standard
/robots.txt(allow public, disallow admin/keys), HEAD method on all public endpoints for uptime monitoring - Prometheus Metrics —
/metricsendpoint with counters, gauges, and uptime in standard text format - Key Rotation — Rotate API keys without losing credits, ACLs, or quotas
- Rate Limit Headers —
X-RateLimit-*andX-Credits-Remainingon every/mcpresponse - Webhook Signatures — HMAC-SHA256 signed webhook payloads (
X-PayGate-Signature) for tamper-proof delivery - Admin Lifecycle Events — Webhook notifications for key.created, key.revoked, key.rotated, key.topup
- IP Allowlisting — Restrict API keys to specific IPs or CIDR ranges (IPv4)
- Key Tags/Metadata — Attach arbitrary key-value tags to API keys for external system integration
- Usage Analytics — Time-series analytics API with tool breakdown, top consumers, and trend comparison
- Alert Webhooks — Configurable alerts for spending thresholds, low credits, quota warnings, key expiry, rate limit spikes
- Team Management — Group API keys into teams with shared budgets, quotas, and usage tracking
- Horizontal Scaling (Redis) — Redis-backed state for multi-process deployments with atomic credit deduction, distributed rate limiting, persistent usage audit trail, real-time pub/sub notifications, and admin API sync
- Webhook Retry Queue — Exponential backoff retry (1s, 2s, 4s...) with dead letter queue for permanently failed deliveries, admin API for monitoring, clearing, and replaying
- Admin Dashboard v2 — Tabbed web dashboard at
/dashboardwith overview, keys management (create/suspend/resume/revoke/top-up), analytics (credit flow, deny reasons, top consumers, webhook health), and system status — all data via safe DOM methods, 30s auto-refresh - Self-Service Portal — API key holder portal at
/portal— check credits, usage, rate limits, available tools, and recent activity without admin access; includes Buy Credits UI, credit history with spending velocity, usage alerts, and self-service key rotation - Stripe Checkout — Self-service credit purchases via Stripe Checkout Sessions —
POST /stripe/checkoutcreates a session,GET /stripe/packageslists available packages; zero-dependency implementation using Node.jshttps, auto-tops-up credits via webhook - State Backup & Restore —
GET /admin/backupexports full server state (keys, teams, groups, webhooks) as versioned JSON with SHA-256 checksum;POST /admin/restoreimports with merge/overwrite/full modes and integrity verification - API Version Header —
X-PayGate-Versionheader on every HTTP response for client version tracking, exposed via CORS - Readiness Probe —
GET /readyreturns 200/503 based on operational state (not draining, not maintenance, backend connected) — separate from/healthliveness probe, ideal for Kubernetes - Health Check + Graceful Shutdown —
GET /healthpublic endpoint with status, uptime, version, in-flight requests, Redis & webhook stats;gracefulStop()drains in-flight requests before teardown - Config Validation + Dry Run —
paygate-mcp validate --config paygate.jsoncatches misconfigurations before starting;--dry-rundiscovers tools, prints pricing table, then exits - Batch Tool Calls —
tools/call_batchmethod for calling multiple tools in one request with all-or-nothing billing, aggregate credit checks, and parallel execution - Multi-Tenant Namespaces — Isolate API keys and usage data by tenant with namespace-filtered admin endpoints, analytics, and usage export
- Scoped Tokens — Issue short-lived
pgt_tokens scoped to specific tools with auto-expiry (max 24h), HMAC-SHA256 signed, zero server-side state - Token Revocation List — Revoke scoped tokens before expiry with O(1) lookup, auto-cleanup, Redis cross-instance sync, and admin API
- Usage-Based Auto-Topup — Automatically add credits when balance drops below a threshold with configurable daily limits, audit trail, webhook events, and Redis sync
- Admin API Key Management — Multiple admin keys with role-based permissions (super_admin, admin, viewer), file persistence, audit trail, and safety guards
- Plugin System — Extensible middleware hooks for custom billing logic, request/response transformation, custom endpoints, and lifecycle management
- Key Groups — Policy templates that apply shared ACL, rate limits, pricing overrides, IP allowlists, and quotas to groups of API keys with automatic inheritance and key-level override support
- Refund on Failure — Automatically refund credits when downstream tool calls fail
- Credit Transfers — Atomically transfer credits between API keys with validation, audit trail, and webhook events
- Bulk Key Operations — Execute multiple key operations (create, topup, revoke) in a single request with per-operation error handling and index tracking
- Key Import/Export — Export all API keys for backup/migration (JSON or CSV) and import with conflict resolution (skip, overwrite, error modes)
- Webhook Filters — Route webhook events to different destinations based on event type and API key prefix with per-filter secrets, independent retry queues, and admin CRUD API
- Key Cloning —
POST /keys/clonecreates a new API key with the same config (ACL, quotas, tags, IP, namespace, group, spending limit, expiry, auto-topup) but fresh counters — ideal for provisioning similar keys - Key Suspension — Temporarily disable API keys without revoking them — suspended keys are denied at the gate but can be resumed, and admin operations (topup, ACL, etc.) still work on suspended keys
- Per-Key Usage —
GET /keys/usage?key=...returns detailed usage breakdown for a specific key: per-tool stats, hourly time-series, deny reasons, recent events, and key metadata - Webhook Test —
POST /webhooks/testsends a test event to your configured webhook URL with synchronous response including status code, response time, and delivery success/failure — verifies webhook connectivity without generating real events - Webhook Delivery Log —
GET /webhooks/logreturns a queryable log of all webhook delivery attempts with timestamps, HTTP status codes, response times, success/failure, retry attempts, event counts, and event types — filter by success status, time range, and limit - Webhook Pause/Resume —
POST /webhooks/pauseandPOST /webhooks/resumetemporarily halt webhook delivery during maintenance — events are buffered (not lost) and flushed on resume, with pause state visible in/webhooks/stats - Key Aliases —
POST /keys/aliasassigns human-readable aliases (e.g.my-service,prod-backend) to API keys — use aliases in any admin endpoint (topup, revoke, suspend, resume, clone, transfer, usage) instead of opaque key IDs, with uniqueness enforcement, format validation, state file persistence, and audit trail - Key Expiry Scanner — Proactive background scanner that detects expiring API keys before they expire — configurable scan interval and notification thresholds (default: 7d, 24h, 1h), de-duplicated
key.expiry_warningwebhook events, audit trail,GET /keys/expiring?within=86400query endpoint, and graceful shutdown - Key Templates — Named templates for API key creation — define reusable presets (credits, ACL, quotas, IP, tags, namespace, expiry TTL, spending limit, auto-topup) and create keys with
template: "free-tier"— explicit params override template defaults, CRUD admin API, Prometheus gauge, file persistence, max 100 templates - Environment Variables Config — Configure everything via
PAYGATE_*env vars for Docker/K8s deployments — 18 env vars covering all CLI flags, with priority: CLI flags > env vars > config file > defaults,PAYGATE_CONFIGloads config file path, help text with Docker examples - Request ID Tracking — Every HTTP response includes
X-Request-Idheader (auto-generatedreq_prefix + 16 hex chars) for distributed tracing — propagates incomingX-Request-Idfrom load balancers/proxies, included in gate audit log metadata, CORS-exposed, available viagetRequestId(req)helper - Server Info Endpoint —
GET /inforeturns server capabilities, enabled features, auth methods, pricing summary, rate limits, and available endpoints — public, no admin key required, ideal for agent auto-discovery and debugging - Configurable CORS — Control which origins can access your server: single origin, multiple origins, or wildcard (
*default), with credentials support, configurable preflight max-age, andVary: Originfor proper caching — set via config filecorsobject,--cors-originCLI flag, orPAYGATE_CORS_ORIGINenv var - Custom Response Headers — Add security headers (
X-Frame-Options,X-Content-Type-Options, etc.), cache control, or any custom headers to all HTTP responses — set via config filecustomHeadersobject,--headerCLI flag, orPAYGATE_CUSTOM_HEADERSenv var - Config Export —
GET /configreturns the running server configuration with sensitive values masked (webhook secrets →***, server commands →***, webhook URLs → scheme+host only) — admin auth required, includes audit trail - Trusted Proxies — Configure trusted proxy IPs/CIDRs for accurate
X-Forwarded-Forextraction — walks the header right-to-left, skipping trusted proxies to find the real client IP, supports exact IPs and CIDR ranges (IPv4), backward compatible (first IP) when not configured - Key Listing Pagination — Enhanced
GET /keyswith cursor-based pagination (limit/offset), sorting (sortBy/order), and filtering by namespace, group, active/suspended/expired status, name prefix, and credit range — backward compatible (returns flat array when no pagination params used) - Key Statistics —
GET /keys/statsreturns aggregate statistics across all keys — total/active/suspended/expired/revoked counts, credit aggregates (allocated/spent/remaining), total calls, namespace and group breakdowns, optional?namespace=filter - Rate Limit Status —
GET /keys/rate-limit-status?key=...returns the current rate limit window state for any key — global calls used/remaining/reset time, per-tool rate limits with individual usage, read-only (doesn't consume a call) - Quota Status —
GET /keys/quota-status?key=...returns daily/monthly quota usage for any key — calls and credits used/remaining/limits, reset periods, quota source (per-key vs global vs none) - Credit History —
GET /keys/credit-history?key=...returns per-key credit mutation log — tracks initial allocation, topups, transfers (in/out), auto-topups, with type/limit/since filters, balance-before/after on every entry, newest-first ordering, capped at 100 entries per key - Spending Velocity —
GET /keys/spending-velocity?key=...returns credit burn rate and depletion forecast — credits/calls per hour/day, estimated depletion date, top tools by spend, configurable analysis window (1h–30d) - Key Comparison —
GET /keys/compare?keys=pg_a,pg_breturns side-by-side comparison of 2–10 keys — credits, usage, velocity, rate limits, status, metadata (namespace/group/tags) — with not-found key reporting - Key Health Score —
GET /keys/health?key=...returns composite health score (0–100) with weighted component breakdown: balance health (30%), quota utilization (25%), rate limit pressure (20%), error rate (25%) — status levels (healthy/good/caution/warning/critical), key issue detection (revoked/suspended/expired/expiring/zero credits), alias support - Maintenance Mode —
POST /maintenanceenables/disables maintenance mode with custom message —/mcpreturns 503 to clients while admin endpoints stay operational,GET /maintenancechecks status,GET /healthreflects maintenance state, full audit trail - Admin Event Stream —
GET /admin/eventsSSE endpoint streams real-time audit events to admin clients — tool calls, denials, key operations, maintenance changes, all with optional?types=filter for event type filtering, keepalive pings, multi-client support - Key Notes —
POST /keys/notesadds timestamped notes to API keys,GET /keys/notes?key=...lists notes,DELETE /keys/notes?key=...&index=Nremoves notes — max 50 per key, 1000 char limit, works on suspended/revoked keys, alias support, audit trail - Scheduled Actions —
POST /keys/schedulecreates future-dated actions (revoke/suspend/topup) on API keys,GET /keys/schedulelists pending schedules with optional?key=filter,DELETE /keys/schedule?id=...cancels a schedule — max 20 per key, alias support, background execution timer, audit trail - Key Activity Timeline —
GET /keys/activity?key=...returns a unified chronological feed of audit events and usage events for a specific key — newest first, optional?since=and?limit=filters, alias support - Credit Reservations —
POST /keys/reserveholds credits,POST /keys/reserve/commitdeducts held credits,POST /keys/reserve/releasefrees the hold,GET /keys/reservelists active reservations — prevents overcommit, configurable TTL (10s–1h), max 50 per key, auto-expiry, audit trail - Request Log —
GET /requestsqueryable log of every tool call with timing, credits charged, status (allowed/denied), deny reason, key, and request ID — filter by key/tool/status/since, pagination, summary statistics (totals + avg duration), 5000-entry ring buffer - Tool Stats —
GET /tools/statsper-tool analytics: call counts, success rate, avg/p95 latency, credits consumed, deny reason breakdown, top 10 consumers — optional?tool=for detailed single-tool view,?since=filter - Request Log Export —
GET /requests/exportexports the full request log as JSON or CSV with Content-Disposition headers — filter by key/tool/status/since/until, combined time-window queries, no pagination limit - Tool Call Dry Run —
POST /requests/dry-runsimulates a tool call without executing — checks key validity, ACL, rate limits, credits, and spending limits, returns predicted outcome with credits-after calculation and rate limit status - Batch Dry Run —
POST /requests/dry-run/batchsimulates multiple tool calls at once — aggregate credit check, per-tool ACL validation, spending limit, returns per-tool results with total credits required and credits-after - Tool Availability —
GET /tools/available?key=...returns per-key tool availability with pricing, affordability (canAfford), ACL enforcement (accessible/denyReason), and per-tool + global rate limit status - Key Dashboard —
GET /keys/dashboard?key=...consolidated single-endpoint view with metadata, balance, health score, spending velocity, rate limits, quotas, usage summary, and recent activity timeline - Admin Notifications —
GET /admin/notificationsscans all keys for actionable issues: expired/expiring keys, zero credits, credit depletion velocity, suspended keys, high error rates, and rate limit pressure — with severity filtering and priority sorting - System Dashboard —
GET /admin/dashboardsystem-wide overview with key counts (active/suspended/revoked/expired), credit summary (allocated/spent/remaining), usage breakdown with deny reasons, top consumers, top tools, notification counts, and uptime - Key Lifecycle Report —
GET /admin/lifecycleaggregated lifecycle trends with daily creation/revocation/suspension buckets, average key lifetime, and at-risk keys (expiring, expired, zero credits) - Cost Analysis —
GET /admin/costscost-centric view with per-tool and per-namespace cost breakdowns, hourly spending trends, top spenders, average cost per call, and namespace filtering - Rate Limit Analysis —
GET /admin/rate-limitsrate limit utilization analysis with per-key and per-tool breakdown, denial trends, most throttled keys, and current window utilization - Quota Analysis —
GET /admin/quotasquota utilization analysis with per-key daily/monthly usage vs limits, per-tool denial breakdown, most constrained keys, and global/per-key quota source tracking - Denial Analysis —
GET /admin/denialscomprehensive denial breakdown by reason type (insufficient_credits, rate_limited, quota_exceeded, key_suspended, etc.) with per-key and per-tool stats, hourly trends, and most denied keys - Traffic Analysis —
GET /admin/trafficrequest volume analysis with tool popularity, hourly volume, top consumers by call count, namespace breakdown, peak hour identification, and success rates - Response Caching — SHA-256 keyed response cache for identical tool calls — skips backend invocation and credit deduction on cache hit, LRU eviction, per-tool or global TTL,
X-Cache: HIT/MISSheader, admin management (GET/DELETE /admin/cache), Prometheus gauge - Circuit Breaker — Three-state circuit breaker (closed → open → half_open) for backend failure detection — opens after N consecutive failures, auto-recovers after cooldown, error code
-32003, admin management (GET/POST /admin/circuit) - Configurable Timeouts — Per-tool and global timeout for tool calls — returns error code
-32004on timeout, per-tool override viatoolPricing[tool].timeoutMs, triggers circuit breaker failure recording - Security Audit —
GET /admin/securitysecurity posture analysis identifying keys without IP allowlists, quotas, ACL restrictions, spending limits, or expiry dates, flagging high-credit keys, and computing a composite security score - Revenue Analysis —
GET /admin/revenuerevenue metrics with per-tool revenue breakdown, per-key spending, hourly revenue trends, credit flow summary (allocated/spent/remaining), and average revenue per call - Key Portfolio Health —
GET /admin/key-portfolioportfolio-wide key health with active/inactive/suspended counts, stale keys, expiring-soon keys, age distribution, credit utilization, and namespace breakdown - Anomaly Detection —
GET /admin/anomaliesidentifies unusual patterns: keys with high denial rates, rapid credit depletion, low remaining credits, with severity ratings and detailed descriptions - Usage Forecasting —
GET /admin/forecastpredicts future credit consumption with per-key depletion estimates, calls remaining, at-risk key identification, system-wide consumption aggregates, and per-tool cost breakdown - Compliance Report —
GET /admin/compliancegenerates compliance-ready report with key governance (expiry coverage), access control (ACL/IP/spending limit coverage), audit trail completeness, weighted overall score, and actionable recommendations - SLA Monitoring —
GET /admin/slatracks service level metrics: success rates, denial breakdowns by reason, per-tool availability and error rates, uptime tracking, sorted by call volume - Capacity Planning —
GET /admin/capacitysystem capacity analysis with credit burn rates, utilization percentages, top consumers, per-namespace breakdown, and scaling recommendations - Key Dependency Map —
GET /admin/dependenciestool-to-key relationship map with tool usage popularity, unique key counts per tool, per-key tool lists, and used/unused tool identification - Tool Latency Analysis —
GET /admin/latencyper-tool response time metrics with avg/p95/min/max durations, slowest tools ranking, and per-key latency breakdown - Error Rate Trends —
GET /admin/error-trendsdenial rate trends with per-tool error rates, denial reason breakdown, worst-performing tools, and trend direction - Credit Flow Analysis —
GET /admin/credit-flowcredit inflow/outflow analysis with utilization percentage, top spenders, and per-tool spend breakdown - Key Age Analysis —
GET /admin/key-agekey age distribution with oldest/newest keys, age buckets (24h/7d/30d/older), and recently created list - Namespace Usage Summary —
GET /admin/namespace-usageper-namespace usage metrics with credit allocation, spending, call counts, and cross-namespace comparison - Audit Summary —
GET /admin/audit-summaryaudit event analytics with type breakdown, top actors, recent events, and activity summary - Group Performance —
GET /admin/group-performanceper-group analytics with key counts, credit allocation/spending, call volume, utilization, and policy summary - Request Volume Trends —
GET /admin/request-trendshourly time-series of request volume, success/failure counts, credit spend, avg duration, and peak hour identification - Key Status Overview —
GET /admin/key-statuskey status dashboard with active/suspended/revoked/expired counts and keys needing attention (low credits, near expiry) - Webhook Health —
GET /admin/webhook-healthwebhook delivery health overview with success rate, pending retries, dead letter count, pause status, and buffered events - Consumer Insights —
GET /admin/consumer-insightsper-key behavioral analytics with top spenders, most active callers, tool diversity, and spending patterns - System Health Score —
GET /admin/system-healthcomposite 0-100 health score with weighted component breakdowns for key health, error rates, and credit utilization - Tool Adoption —
GET /admin/tool-adoptionper-tool adoption metrics with unique consumers, adoption rate, first/last seen timestamps, and usage ranking - Credit Efficiency —
GET /admin/credit-efficiencycredit allocation efficiency with burn efficiency, waste ratio, over-provisioned and under-provisioned key detection - Access Heatmap —
GET /admin/access-heatmaphourly access patterns with tool breakdown, unique consumers, and peak hour identification - Key Churn Analysis —
GET /admin/key-churnkey churn metrics with creation/revocation rates, churn and retention percentages, and never-used key detection - Tool Correlation —
GET /admin/tool-correlationtool co-occurrence analysis showing which tools are commonly used together by the same consumers - Consumer Segmentation —
GET /admin/consumer-segmentationclassifies API key consumers into power/regular/casual/dormant segments with per-segment metrics - Credit Distribution —
GET /admin/credit-distributionhistogram of credit balances across active keys with bucket ranges and median calculation - Response Time Distribution —
GET /admin/response-time-distributionhistogram of response times with latency buckets and p50/p95/p99 percentiles - Consumer Lifetime Value —
GET /admin/consumer-lifetime-valueper-consumer spend analysis with value tiers, tool diversity, and top spender rankings - Tool Revenue Ranking —
GET /admin/tool-revenueranks tools by total credits consumed with call counts, unique consumers, and percentage breakdown - Consumer Retention Cohorts —
GET /admin/consumer-retentiongroups consumers by creation date with retention rates and avg spend per cohort - Error Breakdown —
GET /admin/error-breakdowncategorizes denied requests by reason with counts, percentages, affected consumers, and error rate - Credit Utilization Rate —
GET /admin/credit-utilizationshows utilization percentage across active keys with utilization bands and over-provisioning detection - Namespace Revenue —
GET /admin/namespace-revenuerevenue breakdown by namespace with spend, call counts, key counts, and percentage breakdown - Group Revenue —
GET /admin/group-revenuerevenue breakdown by key group with spend, call counts, key counts, and percentage breakdown - Peak Usage Times —
GET /admin/peak-usagetraffic patterns by hour-of-day with request counts, credits, unique consumers, and peak hour identification - Consumer Activity —
GET /admin/consumer-activityper-consumer activity metrics with calls, spend, credits remaining, last active time, and active/inactive status - Tool Popularity —
GET /admin/tool-popularitytool usage popularity with call counts, credits, unique consumers, percentage, and most popular tool identification - Credit Allocation Summary —
GET /admin/credit-allocationcredit allocation across active keys with tier breakdown (1-100, 101-500, 501+), totals, and average allocation - Daily Summary —
GET /admin/daily-summarydaily rollup of requests, credits spent, new keys, errors, unique consumers and tools for trend analysis - Key Ranking —
GET /admin/key-rankingleaderboard of active keys ranked by spend, calls, or credits remaining with configurable sorting - Hourly Traffic —
GET /admin/hourly-trafficgranular per-hour request counts with allowed/denied breakdown, credits, consumers, tools, and busiest hour - Tool Error Rate —
GET /admin/tool-error-rateper-tool error rates with denied/allowed counts, error percentage, and overall reliability metrics - Consumer Spend Velocity —
GET /admin/consumer-spend-velocityper-consumer spend rate with credits/hour, depletion forecast, and velocity ranking - Namespace Activity —
GET /admin/namespace-activityper-namespace activity metrics with key counts, spend, calls, credits remaining for multi-tenant visibility - Credit Burn Rate —
GET /admin/credit-burn-ratesystem-wide credit burn rate with credits/hour, utilization percentage, depletion forecast - Consumer Risk Score —
GET /admin/consumer-risk-scoreper-consumer risk scoring based on utilization with risk levels (low/medium/high/critical) - Revenue Forecast —
GET /admin/revenue-forecastprojected revenue with hourly/daily/weekly/monthly forecasts capped by remaining credits - System Overview —
GET /admin/system-overviewexecutive summary with key counts, credit totals, utilization, activity metrics - Key Health Overview —
GET /admin/key-health-overviewholistic per-key health check with utilization, status levels, health distribution - Namespace Comparison —
GET /admin/namespace-comparisonside-by-side namespace comparison with allocation, spend, utilization, leader - Consumer Growth —
GET /admin/consumer-growthconsumer growth metrics with age, spend rate, credits allocated, new consumer count - Tool Profitability —
GET /admin/tool-profitabilityper-tool profitability analysis with revenue, calls, avg revenue per call, unique callers - Credit Waste Analysis —
GET /admin/credit-wasteper-key credit waste analysis with utilization metrics and waste percentage - Group Activity —
GET /admin/group-activityper-group activity metrics with key counts, spend, calls, credits remaining for policy-template analytics - Config Hot Reload —
POST /config/reloadreloads pricing, rate limits, webhooks, quotas, and behavior flags from config file without server restart - Webhook Events — POST batched usage events to any URL for external billing/alerting
- Config File Mode — Load all settings from a JSON file (
--config) - Shadow Mode — Log everything without enforcing payment (for testing)
- Persistent Storage — Keys, credits, admin keys, and groups survive restarts with
--state-file - Zero Dependencies — No external npm packages. Uses only Node.js built-ins.
Usage
Wrap a Local MCP Server (stdio)
# Default: 1 credit per call, 60 calls/min, port 3402
npx paygate-mcp wrap --server "npx @modelcontextprotocol/server-filesystem /tmp"
# Custom pricing and limits
npx paygate-mcp wrap \
--server "python my-server.py" \
--price 2 \
--rate-limit 30 \
--port 8080
# Per-tool pricing
npx paygate-mcp wrap \
--server "node server.js" \
--tool-price "search:1,generate:5,premium_analyze:20"
# Shadow mode (observe without enforcing)
npx paygate-mcp wrap --server "node server.js" --shadowGate a Remote MCP Server (Streamable HTTP)
Gate any remote MCP server that supports the Streamable HTTP transport (MCP spec 2025-03-26):
npx paygate-mcp wrap --remote-url "https://my-mcp-server.example.com/mcp"
# With custom pricing
npx paygate-mcp wrap \
--remote-url "https://api.example.com/mcp" \
--price 5 \
--tool-price "gpt4:20,search:2"The proxy handles:
- JSON-RPC forwarding via HTTP POST
- SSE (text/event-stream) response parsing
Mcp-Session-Idsession management- Graceful session cleanup (HTTP DELETE on shutdown)
When started, you'll see your admin key in the console. Save it.
Multi-Server Mode
Wrap multiple MCP servers behind a single PayGate instance. Tools are prefixed with the server name:
npx paygate-mcp wrap --config multi-server.jsonExample multi-server.json:
{
"port": 3402,
"defaultCreditsPerCall": 1,
"servers": [
{
"prefix": "fs",
"serverCommand": "npx",
"serverArgs": ["@modelcontextprotocol/server-filesystem", "/tmp"]
},
{
"prefix": "github",
"remoteUrl": "https://github-mcp.example.com/mcp"
}
]
}Tools are exposed with prefixes: fs:read_file, fs:write_file, github:search_repos, etc. Pricing and ACLs work on the prefixed names:
{
"toolPricing": {
"github:search_repos": { "creditsPerCall": 5 },
"fs:read_file": { "creditsPerCall": 1 }
}
}Credits are shared across all backends — one API key works for all servers.
Client SDK
Use PayGateClient to call tools from TypeScript/Node.js with auto 402 retry:
import { PayGateClient, PayGateError } from 'paygate-mcp/client';
const client = new PayGateClient({
url: 'http://localhost:3402',
apiKey: 'pg_abc123...',
autoRetry: true,
onCreditsNeeded: async (info) => {
// Called when credits run out — add credits and return true to retry
await topUpCredits(info.creditsRequired);
return true;
},
});
const tools = await client.listTools();
const result = await client.callTool('search', { query: 'hello' });
const balance = await client.getBalance();Features:
- Auto 402 retry: When a tool call returns payment-required, calls
onCreditsNeededand retries - Balance tracking:
client.lastKnownBalancetracks credits fromgetBalance()calls - Typed errors:
PayGateErrorwith.isPaymentRequired,.isRateLimited,.isExpiredhelpers - Zero dependencies: Uses Node.js built-in
http/https
Create API Keys
curl -X POST http://localhost:3402/keys \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"name": "my-client", "credits": 100}'Call Tools
curl -X POST http://localhost:3402/mcp \
-H "Content-Type: application/json" \
-H "X-API-Key: CLIENT_API_KEY" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": {"path": "/tmp/test.txt"}
}
}'Top Up Credits
curl -X POST http://localhost:3402/topup \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"key": "CLIENT_API_KEY", "credits": 500}'Check Balance (Client Self-Service)
curl http://localhost:3402/balance \
-H "X-API-Key: CLIENT_API_KEY"Returns credits, total spent, call count, and last used timestamp. Clients can check their own balance without needing admin access.
Export Usage Data (Admin)
# JSON export
curl http://localhost:3402/usage \
-H "X-Admin-Key: YOUR_ADMIN_KEY"
# CSV export (for spreadsheet/billing import)
curl "http://localhost:3402/usage?format=csv" \
-H "X-Admin-Key: YOUR_ADMIN_KEY"
# Filter by date
curl "http://localhost:3402/usage?since=2025-01-01T00:00:00Z" \
-H "X-Admin-Key: YOUR_ADMIN_KEY"Returns per-call usage events with tool name, credits charged, and timestamps. API keys are masked in output.
Check Status
curl http://localhost:3402/status \
-H "X-Admin-Key: YOUR_ADMIN_KEY"Returns active keys, usage stats, per-tool breakdown, and deny reasons.
Admin Dashboard
Open the web dashboard in your browser:
http://localhost:3402/dashboardA real-time admin UI for managing keys, viewing usage, and monitoring tool calls. Enter your admin key to authenticate. Features auto-refresh every 30s, top tools chart, activity feed, and key creation/management.
API Reference
| Endpoint | Method | Auth | Description |
|----------|--------|------|-------------|
| /mcp | POST | X-API-Key or Bearer | JSON-RPC 2.0 proxy (returns JSON or SSE) |
| /mcp | GET | X-API-Key or Bearer | SSE notification stream (Streamable HTTP) |
| /mcp | DELETE | Mcp-Session-Id | Terminate an MCP session |
| /balance | GET | X-API-Key | Client self-service — check credits, quota, ACL, expiry |
| /keys | POST | X-Admin-Key | Create API key (with ACL, expiry, quota, credits) |
| /keys | GET | X-Admin-Key | List all keys (masked, with expiry status) |
| /topup | POST | X-Admin-Key | Add credits to an existing key |
| /keys/transfer | POST | X-Admin-Key | Transfer credits between API keys |
| /keys/bulk | POST | X-Admin-Key | Execute multiple key operations (create, topup, revoke) in one request |
| /keys/export | GET | X-Admin-Key | Export all API keys for backup/migration (JSON or CSV) |
| /keys/import | POST | X-Admin-Key | Import API keys from backup with conflict resolution |
| /keys/revoke | POST | X-Admin-Key | Permanently revoke an API key |
| /keys/suspend | POST | X-Admin-Key | Temporarily suspend a key (reversible) |
| /keys/resume | POST | X-Admin-Key | Resume a suspended key |
| /keys/clone | POST | X-Admin-Key | Clone a key (new key, same config, fresh counters) |
| /keys/usage | GET | X-Admin-Key | Per-key usage breakdown (per-tool, time-series, deny reasons) |
| /keys/rotate | POST | X-Admin-Key | Rotate key (new key, same credits/ACL/quotas) |
| /keys/acl | POST | X-Admin-Key | Set tool ACL (whitelist/blacklist) on a key |
| /keys/expiry | POST | X-Admin-Key | Set or remove key expiry (TTL) |
| /keys/quota | POST | X-Admin-Key | Set usage quota (daily/monthly limits) |
| /keys/tags | POST | X-Admin-Key | Set key tags/metadata (merge semantics) |
| /keys/ip | POST | X-Admin-Key | Set IP allowlist (CIDR + exact match) |
| /keys/search | POST | X-Admin-Key | Search keys by tag values |
| /keys/auto-topup | POST | X-Admin-Key | Configure or disable auto-topup for a key |
| /admin/keys | GET | X-Admin-Key (super_admin) | List all admin keys (masked) |
| /admin/keys | POST | X-Admin-Key (super_admin) | Create a new admin key with role |
| /admin/keys/revoke | POST | X-Admin-Key (super_admin) | Revoke an admin key |
| /limits | POST | X-Admin-Key | Set spending limit on a key |
| /usage | GET | X-Admin-Key | Export usage data (JSON or CSV) |
| /status | GET | X-Admin-Key | Full dashboard with usage stats |
| /dashboard | GET | None (admin key in-browser) | Real-time admin web dashboard |
| /stripe/checkout | POST | X-API-Key | Create Stripe Checkout Session for credit purchase |
| /stripe/packages | GET | None | List available credit packages (public, rate-limited) |
| /stripe/webhook | POST | Stripe Signature | Auto-top-up credits on payment |
| /admin/backup | GET | X-Admin-Key | Export full server state as versioned JSON snapshot |
| /admin/restore | POST | X-Admin-Key | Import state from backup (merge/overwrite/full modes) |
| /admin/cache | GET | X-Admin-Key | Response cache stats (entries, hits, misses, hit rate) |
| /admin/cache | DELETE | X-Admin-Key | Clear cache (all or ?tool= filter) |
| /admin/circuit | GET | X-Admin-Key | Circuit breaker status (state, failures, rejections) |
| /admin/circuit | POST | X-Admin-Key | Reset circuit breaker to closed state |
| /.well-known/oauth-authorization-server | GET | None | OAuth 2.1 server metadata |
| /oauth/register | POST | None | Dynamic Client Registration (RFC 7591) |
| /oauth/authorize | GET | None | Authorization endpoint (PKCE required) |
| /oauth/token | POST | None | Token endpoint (code exchange + refresh) |
| /oauth/revoke | POST | None | Token revocation (RFC 7009) |
| /oauth/clients | GET | X-Admin-Key | List registered OAuth clients |
| /.well-known/mcp-payment | GET | None | Server payment metadata (SEP-2007) |
| /.well-known/mcp.json | GET | None | MCP Server Identity card (discovery) |
| /pricing | GET | None | Full per-tool pricing breakdown |
| /openapi.json | GET | None | OpenAPI 3.1 spec (all 140+ endpoints) |
| /docs | GET | None | Interactive API docs (Swagger UI) |
| /robots.txt | GET | None | Crawler directives (allow public, disallow admin/keys) |
| /portal | GET | None | Self-service API key portal (browser UI, auth via X-API-Key prompt) |
| /ready | GET | None | Readiness probe (200 when ready, 503 when draining/maintenance) |
| /metrics | GET | None | Prometheus metrics (counters, gauges, uptime) |
| /analytics | GET | X-Admin-Key | Usage analytics (time-series, tool breakdown, trends) |
| /alerts | GET | X-Admin-Key | Consume pending alerts |
| /alerts | POST | X-Admin-Key | Configure alert rules |
| /teams | GET | X-Admin-Key | List all teams |
| /teams | POST | X-Admin-Key | Create a team (name, budget, quota, tags) |
| /teams/update | POST | X-Admin-Key | Update team settings |
| /teams/delete | POST | X-Admin-Key | Delete (deactivate) a team |
| /teams/assign | POST | X-Admin-Key | Assign an API key to a team |
| /teams/remove | POST | X-Admin-Key | Remove an API key from a team |
| /teams/usage | GET | X-Admin-Key | Team usage summary with member breakdown |
| /tokens | POST | X-Admin-Key | Create a scoped token (short-lived, tool-restricted) |
| /tokens/revoke | POST | X-Admin-Key | Revoke a scoped token (by full token string) |
| /tokens/revoked | GET | X-Admin-Key | List all revoked token entries |
| /namespaces | GET | X-Admin-Key | List all namespaces with key/credit/spending stats |
| /audit | GET | X-Admin-Key | Query audit log (filter by type, actor, time) |
| /audit/export | GET | X-Admin-Key | Export full audit log (JSON or CSV) |
| /audit/stats | GET | X-Admin-Key | Audit log statistics |
| /plugins | GET | X-Admin-Key | List registered plugins with hook info |
| /groups | GET | X-Admin-Key | List all key groups (policy templates) |
| /groups | POST | X-Admin-Key | Create a key group with shared policies |
| /groups/update | POST | X-Admin-Key | Update group policies |
| /groups/delete | POST | X-Admin-Key | Delete (deactivate) a group |
| /groups/assign | POST | X-Admin-Key | Assign an API key to a group |
| /groups/remove | POST | X-Admin-Key | Remove an API key from a group |
| /webhooks/filters | GET | X-Admin-Key | List all webhook filter rules |
| /webhooks/filters | POST | X-Admin-Key | Create a webhook filter rule |
| /webhooks/filters/update | POST | X-Admin-Key | Update a webhook filter rule |
| /webhooks/filters/delete | POST | X-Admin-Key | Delete a webhook filter rule |
| /webhooks/replay | POST | X-Admin-Key | Replay dead letter webhook events (all or by index) |
| /webhooks/test | POST | X-Admin-Key | Send test event to configured webhook URL (synchronous) |
| /webhooks/log | GET | X-Admin-Key | Webhook delivery log with status, timing, and filters |
| /webhooks/pause | POST | X-Admin-Key | Pause webhook delivery (events buffered until resumed) |
| /webhooks/resume | POST | X-Admin-Key | Resume webhook delivery and flush buffered events |
| /keys/alias | POST | X-Admin-Key | Set or clear a human-readable alias for an API key |
| /keys/expiring | GET | X-Admin-Key | List keys expiring within a time window (?within=86400 seconds) |
| /keys/templates | GET | X-Admin-Key | List all key templates |
| /keys/templates | POST | X-Admin-Key | Create or update a key template |
| /keys/templates/delete | POST | X-Admin-Key | Delete a key template |
| /config/reload | POST | X-Admin-Key | Hot-reload config file (pricing, rate limits, webhooks, quotas) |
| /health | GET | None | Health check (status, uptime, version, in-flight, Redis/webhook status) |
| / | GET | None | Root endpoint (endpoint list) |
Free Methods
These MCP methods pass through without auth or billing:
initialize, initialized, ping, tools/list, resources/list, prompts/list
Gated methods: tools/call (single), tools/call_batch (batch — all-or-nothing billing, parallel execution). See Batch Tool Calls.
CLI Options
--server <cmd> MCP server command to wrap via stdio
--remote-url <url> Remote MCP server URL (Streamable HTTP transport)
--port <n> HTTP port (default: 3402)
--price <n> Default credits per tool call (default: 1)
--rate-limit <n> Max calls/min per key (default: 60, 0=unlimited)
--name <s> Server display name
--shadow Shadow mode — log without enforcing payment
--admin-key <s> Set admin key (default: auto-generated)
--tool-price <t:n> Per-tool price (e.g. "search:5,generate:10")
--import-key <k:c> Import existing key with credits (e.g. "pg_abc:100")
--state-file <path> Persist keys/credits to a JSON file (survives restarts)
--stripe-secret <s> Stripe webhook signing secret (enables /stripe/webhook)
--webhook-url <url> POST batched usage events to this URL
--webhook-secret <s> HMAC-SHA256 secret for signing webhook payloads
--refund-on-failure Refund credits when downstream tool call fails
--redis-url <url> Redis URL for distributed state (e.g. "redis://localhost:6379")
--config <path> Load settings from a JSON config fileNote: Use
--serverOR--remote-urlfor single-server mode. Useserversin a config file for multi-server mode.
Persistent Storage
Add --state-file to save API keys and credits to disk. Data survives server restarts.
npx paygate-mcp wrap --server "your-mcp-server" --state-file ~/.paygate/state.jsonStripe Integration
Connect Stripe to automatically top up credits when customers pay:
npx paygate-mcp wrap \
--server "your-mcp-server" \
--state-file ~/.paygate/state.json \
--stripe-secret "whsec_your_stripe_webhook_secret"Setup:
- Create a Stripe Checkout Session with metadata:
paygate_api_key— the customer's API key (e.g.pg_abc123...)paygate_credits— credits to add on payment (e.g.500)
- Point your Stripe webhook to
https://your-server/stripe/webhook - Subscribe to
checkout.session.completedandinvoice.payment_succeededevents
When a customer completes payment, credits are automatically added to their API key. Subscriptions auto-renew credits on each billing cycle.
Security:
- HMAC-SHA256 signature verification (Stripe's v1 scheme)
- Timing-safe comparison to prevent timing attacks
- 5-minute timestamp tolerance to prevent replay attacks
- Payment status verification (only
paidtriggers credits) - Zero dependencies — uses Node.js built-in
crypto
Per-Tool ACL (Access Control)
Control which tools each API key can access:
# Create a key that can only access search and read tools
curl -X POST http://localhost:3402/keys \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"name": "limited-client", "credits": 100, "allowedTools": ["search", "read_file"]}'
# Create a key with specific tools blocked
curl -X POST http://localhost:3402/keys \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"name": "safe-client", "credits": 100, "deniedTools": ["delete_file", "admin_reset"]}'
# Update ACL on an existing key
curl -X POST http://localhost:3402/keys/acl \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"key": "CLIENT_API_KEY", "allowedTools": ["search"], "deniedTools": ["admin"]}'- allowedTools (whitelist): Only these tools are accessible. Empty = all tools.
- deniedTools (blacklist): These tools are always denied. Applied after allowedTools.
- ACL also filters
tools/list— clients only see their permitted tools.
Per-Tool Rate Limits
Set independent rate limits per tool (on top of the global limit):
{
"toolPricing": {
"expensive_analyze": { "creditsPerCall": 10, "rateLimitPerMin": 5 },
"search": { "creditsPerCall": 1, "rateLimitPerMin": 30 },
"cheap_read": { "creditsPerCall": 1 }
}
}Per-tool limits are enforced independently per API key. A key can be rate-limited on one tool while still accessing others. The global --rate-limit applies across all tools.
Key Expiry (TTL)
Create API keys that auto-expire:
# Create a key that expires in 1 hour (3600 seconds)
curl -X POST http://localhost:3402/keys \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"name": "trial-user", "credits": 50, "expiresIn": 3600}'
# Create a key with a specific expiry date
curl -X POST http://localhost:3402/keys \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"name": "quarterly", "credits": 1000, "expiresAt": "2026-06-01T00:00:00Z"}'
# Set or extend expiry on an existing key
curl -X POST http://localhost:3402/keys/expiry \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"key": "CLIENT_API_KEY", "expiresIn": 86400}'
# Remove expiry (key never expires)
curl -X POST http://localhost:3402/keys/expiry \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"key": "CLIENT_API_KEY", "expiresAt": null}'Expired keys return a clear api_key_expired error. Admins can extend or remove expiry at any time.
Credit Transfers
Atomically transfer credits between API keys:
curl -X POST http://localhost:3402/keys/transfer \
-H "X-Admin-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{ "from": "pg_source_key", "to": "pg_dest_key", "credits": 500, "memo": "Monthly allocation" }'Response:
{
"transferred": 500,
"from": { "keyMasked": "pg_sour...key1", "balance": 500 },
"to": { "keyMasked": "pg_dest...key2", "balance": 700 },
"memo": "Monthly allocation",
"message": "Transferred 500 credits"
}Validation: Both keys must exist, be active (not revoked/expired), and the source must have sufficient credits. Fractional credits are floored to integers. Self-transfers are rejected.
Audit trail: Every transfer logs a key.credits_transferred audit event with masked keys, amount, balances, and memo.
Bulk Key Operations
Execute multiple key operations (create, topup, revoke) in a single request. Failed operations don't stop subsequent ones — each result includes success status and index for easy correlation.
curl -X POST http://localhost:3402/keys/bulk \
-H "X-Admin-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"operations": [
{ "action": "create", "name": "api-key-1", "credits": 500, "tags": { "env": "prod" } },
{ "action": "create", "name": "api-key-2", "credits": 200 },
{ "action": "topup", "key": "pg_existing_key", "credits": 1000 },
{ "action": "revoke", "key": "pg_old_key" }
]
}'Response:
{
"total": 4,
"succeeded": 4,
"failed": 0,
"results": [
{ "index": 0, "action": "create", "success": true, "result": { "key": "pg_abc...", "name": "api-key-1", "credits": 500 } },
{ "index": 1, "action": "create", "success": true, "result": { "key": "pg_def...", "name": "api-key-2", "credits": 200 } },
{ "index": 2, "action": "topup", "success": true, "result": { "creditsAdded": 1000, "newBalance": 1500 } },
{ "index": 3, "action": "revoke", "success": true, "result": { "message": "Key revoked" } }
]
}Actions: create (with optional name, credits, tags, namespace, allowedTools, deniedTools), topup (key + credits), revoke (key). Unknown actions return an error result without stopping the batch.
Limits: Maximum 100 operations per request. Empty operations array returns 400.
Audit trail: Each successful operation logs an individual audit event with "(bulk)" suffix.
Key Import/Export
Export all API keys for backup or migration between PayGate instances:
# Export as JSON (includes full key secrets)
curl http://localhost:3402/keys/export \
-H "X-Admin-Key: $ADMIN_KEY" \
-o paygate-keys-backup.json
# Export as CSV
curl "http://localhost:3402/keys/export?format=csv" \
-H "X-Admin-Key: $ADMIN_KEY" \
-o paygate-keys-backup.csv
# Export only active keys in a specific namespace
curl "http://localhost:3402/keys/export?activeOnly=true&namespace=production" \
-H "X-Admin-Key: $ADMIN_KEY"Import keys into a PayGate instance:
curl -X POST http://localhost:3402/keys/import \
-H "X-Admin-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"keys": [{ "key": "pg_abc123...", "name": "my-key", "credits": 500, "active": true, "tags": {} }],
"mode": "skip"
}'Response:
{
"total": 1,
"imported": 1,
"overwritten": 0,
"skipped": 0,
"errors": 0,
"mode": "skip",
"results": [{ "key": "pg_abc123...", "name": "my-key", "status": "imported" }]
}Conflict modes: skip (default) — skip keys that already exist, overwrite — replace existing keys, error — fail on duplicate keys.
Limits: Maximum 1000 keys per import request. Keys must start with pg_ prefix.
Export formats: JSON (full records with all fields) or CSV (key subset for spreadsheet use).
Spending Limits
Cap the total credits any API key can spend:
# Set a spending limit on a key (admin only)
curl -X POST http://localhost:3402/limits \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"key": "CLIENT_API_KEY", "spendingLimit": 500}'
# Check remaining budget
curl http://localhost:3402/balance -H "X-API-Key: CLIENT_API_KEY"
# → { "spendingLimit": 500, "remainingBudget": 350, ... }Set spendingLimit to 0 for unlimited. When a key hits its limit, tool calls are denied with a clear error.
Refund on Failure
Automatically return credits when a downstream tool call fails:
npx paygate-mcp wrap --server "node server.js" --refund-on-failureCredits are deducted before the tool call. If the wrapped server returns an error, credits are refunded and totalSpent / totalCalls are rolled back. Prevents charging users for failed operations.
Webhook Events
POST usage events to any external URL for billing, alerting, or analytics:
npx paygate-mcp wrap --server "node server.js" --webhook-url "https://billing.example.com/events"Events are batched (up to 10 per POST) and flushed every 5 seconds. Each event includes tool name, credits charged, API key, and timestamp.
Retry Queue & Dead Letters
Failed webhook deliveries are retried with exponential backoff (1s, 2s, 4s, 8s, 16s — configurable up to --webhook-retries attempts). After all retries are exhausted, events move to a dead letter queue for admin inspection.
# Custom max retries (default: 5)
npx paygate-mcp wrap --server "node server.js" \
--webhook-url "https://billing.example.com/events" \
--webhook-retries 10Admin endpoints:
| Endpoint | Method | Description |
|----------|--------|-------------|
| /webhooks/stats | GET | Delivery statistics (delivered, failed, pending retries, dead letters) |
| /webhooks/dead-letter | GET | List permanently failed deliveries with error details |
| /webhooks/dead-letter | DELETE | Clear dead letter queue |
| /webhooks/replay | POST | Replay dead letter events (all or by index) |
Retry attempts include an X-PayGate-Retry header with the attempt number for observability.
Webhook Event Replay
Replay permanently failed webhook events from the dead letter queue:
# Replay all dead letter entries
curl -X POST http://localhost:3402/webhooks/replay \
-H "X-Admin-Key: $ADMIN_KEY"
# Replay specific entries by index
curl -X POST http://localhost:3402/webhooks/replay \
-H "X-Admin-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{ "indices": [0, 2, 5] }'Replayed entries are removed from the dead letter queue and re-queued for fresh delivery (attempt counter resets to 0). If delivery fails again, they follow the normal retry/dead-letter flow.
Webhook Signatures (HMAC-SHA256)
Sign webhook payloads for tamper-proof delivery:
npx paygate-mcp wrap --server "node server.js" \
--webhook-url "https://billing.example.com/events" \
--webhook-secret "whsec_your_secret_here"When --webhook-secret is set, every webhook POST includes an X-PayGate-Signature header:
X-PayGate-Signature: t=1709123456,v1=a1b2c3d4...Verifying signatures (Node.js example):
import { WebhookEmitter } from 'paygate-mcp';
const signature = req.headers['x-paygate-signature'];
const [tPart, v1Part] = signature.split(',');
const timestamp = tPart.split('=')[1];
const sig = v1Part.split('=')[1];
// Reconstruct signed payload: timestamp.body
const signedPayload = `${timestamp}.${rawBody}`;
const isValid = WebhookEmitter.verify(signedPayload, sig, 'whsec_your_secret_here');The signature covers timestamp.body to prevent replay attacks. Use timing-safe comparison (built into WebhookEmitter.verify).
Admin Lifecycle Events
When webhooks are enabled, admin operations also fire webhook events:
| Event Type | Trigger | Metadata |
|------------|---------|----------|
| key.created | POST /keys | keyMasked, name, credits |
| key.topup | POST /topup | keyMasked, creditsAdded, newBalance |
| key.revoked | POST /keys/revoke | keyMasked |
| key.rotated | POST /keys/rotate | oldKeyMasked, newKeyMasked |
| key.expired | Gate evaluation | keyMasked |
| alert.fired | Gate evaluation | alertType, keyPrefix, message, value, threshold |
| team.created | POST /teams | teamId, name, budget |
| team.updated | POST /teams/update | teamId, changes |
| team.deleted | POST /teams/delete | teamId |
| team.key_assigned | POST /teams/assign | teamId, keyMasked |
| team.key_removed | POST /teams/remove | teamId, keyMasked |
Admin events appear in the adminEvents array of the webhook payload (separate from usage events). Both arrays can be present in the same batch.
Webhook Filters (Event Routing)
Route webhook events to different destinations based on event type and API key prefix. Each filter rule routes matching events to its own URL with independent retry queues, dead letter queues, and optional signing secrets.
Create a filter rule:
curl -X POST http://localhost:3402/webhooks/filters \
-H "X-Admin-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "production-alerts",
"events": ["key.created", "key.revoked", "alert.fired"],
"url": "https://alerts.example.com/webhook",
"secret": "whsec_alerts_secret",
"keyPrefixes": ["pk_prod_"],
"active": true
}'List filters:
curl http://localhost:3402/webhooks/filters -H "X-Admin-Key: $ADMIN_KEY"Update a filter:
curl -X POST http://localhost:3402/webhooks/filters/update \
-H "X-Admin-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{ "id": "wf_abc123", "active": false }'Delete a filter:
curl -X POST http://localhost:3402/webhooks/filters/delete \
-H "X-Admin-Key: $ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{ "id": "wf_abc123" }'Filter rules:
events— Array of event types to match (exact match or"*"wildcard for all events)keyPrefixes— Optional array of API key prefixes (e.g.,["pk_prod_"]). Events only match if the associated key starts with one of these prefixes. Omit for all keys.url— Destination URL for matched events (each unique URL gets its own retry queue)secret— Optional HMAC-SHA256 signing secret for this destinationactive— Enable/disable the filter without deleting it
Routing behavior:
- Events matching filter rules are sent to the filter's destination URL
- The default webhook URL (if configured) always receives all events (backward compatible)
- Multiple filters can match the same event — it's sent to all matching destinations
- Inactive filters are skipped during routing
Config file:
{
"webhookUrl": "https://billing.example.com/events",
"webhookFilters": [
{
"name": "production-alerts",
"events": ["key.created", "key.revoked", "alert.fired"],
"url": "https://alerts.example.com/webhook",
"keyPrefixes": ["pk_prod_"]
}
]
}Stats: GET /webhooks/stats includes per-URL delivery statistics for all filter destinations plus the default endpoint.
Usage Quotas
Set daily or monthly usage limits per API key:
# Create a key with 10 calls/day, 200 calls/month
curl -X POST http://localhost:3402/keys \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"name": "metered-user", "credits": 1000, "quota": {"dailyCallLimit": 10, "monthlyCallLimit": 200}}'
# Set credit-based quotas (max 50 credits/day)
curl -X POST http://localhost:3402/keys/quota \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"key": "CLIENT_API_KEY", "dailyCreditLimit": 50}'
# Remove per-key quota (fall back to global defaults)
curl -X POST http://localhost:3402/keys/quota \
-H "Content-Type: application/json" \
-H "X-Admin-Key: YOUR_ADMIN_KEY" \
-d '{"key": "CLIENT_API_KEY", "remove": true}'Quota types: dailyCallLimit, monthlyCallLimit, dailyCreditLimit, monthlyCreditLimit. Set to 0 for unlimited. Counters reset at UTC midnight (daily) and UTC month boundary (monthly). Set global defaults in the config file with globalQuota.
Dynamic Pricing
Charge extra credits based on input argument size:
{
"toolPricing": {
"analyze_text": { "creditsPerCall": 2, "creditsPerKbInput": 5 },
"search": { "creditsPerCall": 1 }
}
}For analyze_text, a 3 KB input would cost 2 + ceil(3 × 5) = 17 credits. Small inputs round up to at least 1 KB. Tools without creditsPerKbInput use the flat base price.
OAuth 2.1
Full OAuth 2.1 authorization server for MCP clients. Implements PKCE, dynamic client registration, token refresh, and revocation.
Enable OAuth in config:
{
"oauth": {
"accessTokenTtl": 3600,
"refreshTokenTtl": 2592000,
"scopes": ["tools:*", "tools:read", "tools:write"]
}
}Full flow:
# 1. Register an OAuth client
curl -X POST http://localhost:3402/oauth/register \
-H "Content-Type: application/json" \
-d '{"client_name": "My Agent", "redirect_uris": ["http://localhost:8080/callback"], "api_key": "pg_..."}'
# 2. Generate PKCE challenge (code_verifier → SHA256 → base64url)
# 3. Authorize: GET /oauth/authorize?response_type=code&client_id=...&redirect_uri=...&code_challenge=...&code_challenge_method=S256
# 4. Exchange code for tokens
curl -X POST http://localhost:3402/oauth/token \
-H "Content-Type: application/json" \
-d '{"grant_type": "authorization_code", "code": "...", "client_id": "...", "redirect_uri": "...", "code_verifier": "..."}'
# 5. Use Bearer token on /mcp
curl -X POST http://localhost:3402/mcp \
-H "Authorization: Bearer pg_at_..." \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "search", "arguments": {"query": "hello"}}}'
# 6. Refresh token
curl -X POST http://localhost:3402/oauth/token \
-d '{"grant_type": "refresh_token", "refresh_token": "pg_rt_...", "client_id": "..."}'OAuth tokens are backed by API keys — each token maps to an API key for billing. The /mcp endpoint accepts both X-API-Key and Authorization: Bearer headers.
SSE Streaming (MCP Streamable HTTP)
PayGate implements the full MCP Streamable HTTP transport with SSE support:
# POST /mcp with SSE response (add Accept header)
curl -N -X POST http://localhost:3402/mcp \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-H "X-API-Key: YOUR_KEY" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"analyze","arguments":{}}}'
# Response: SSE stream with event: message + data: {jsonrpc response}
# GET /mcp — Open SSE notification stream
curl -N http://localhost:3402/mcp \
-H "Accept: text/event-stream" \
-H "Mcp-Session-Id: mcp_sess_..."
# Receives server-initiated notifications as SSE events
# DELETE /mcp — Terminate session
curl -X DELETE http://localhost:3402/mcp \
-H "Mcp-Session-Id: mcp_sess_..."Session Management:
- Every POST
/mcpresponse includes anMcp-Session-Idheader - Clients reuse sessions by sending
Mcp-Session-Idon subsequent requests - GET
/mcpopens a long-lived SSE connection for server-to-client notifications - DELETE
/mcpterminates a session and closes all SSE connections - Sessions auto-expire after 30 minutes of inactivity
Transport modes:
POST /mcpwithoutAccept: text/event-stream→ standard JSON response (backward compatible)POST /mcpwith `A
