Projects
AIOpen as plain markdown for AIA single eRPC instance can host many projects side-by-side. Each project bundles its own networks, upstreams, providers, auth strategies, rate-limit budgets, CORS, header-forwarding rules, and method allow/deny lists. Typical split: separate backend, indexer, and frontend projects with very different cost/reliability profiles.
A request's URL picks the project: /<projectId>/<architecture>/<chainId> or /<projectId>/<alias> (when a network alias is defined).
You can configure (per project):
- Identity —
id(required; appears in logs, metrics, URLs) - Networks & upstreams —
networks[],upstreams[], plus defaults (networkDefaults,upstreamDefaults) - Vendor providers —
providers[](lazy-load chains by API key — see Providers) - Auth & CORS —
auth.strategies[],cors - Rate limits —
rateLimitBudget(project-wide budget) - Method allow/deny — project-level
allowMethods/ignoreMethods - Header policy —
forwardHeaders(whitelist client headers to pass to upstreams) - User-agent tracking —
userAgentMode: simplified | raw - Upstream scoring & routing —
routingStrategy,scoreGranularity,scoreRefreshInterval,scoreMetricsWindowSize,scorePenaltyDecayRate,scoreSwitchHysteresis,scoreMinSwitchInterval,scoreMetricsMode
Minimum useful config
projects:
- id: main
upstreams:
- endpoint: alchemy://YOUR_API_KEYThat's it — chains are auto-discovered from the upstream, defaults apply for everything else.
Three projects with different profiles
A typical setup: a public-facing frontend, an internal indexer, and a backend service. Each gets its own rate limits, auth, and method allowlist.
projects: # Public frontend — JWT auth, read-only methods, tight rate limit - id: frontend auth: strategies: - type: jwt jwt: verificationKeys: { "kid-1": "${JWT_PUB_KEY}" } allowedIssuers: ["https://my-app.example"] rateLimitBudget: frontend-tier allowMethods: ["eth_call", "eth_blockNumber", "eth_chainId", "eth_getLogs", "eth_getBalance"] upstreams: - endpoint: alchemy://${ALCHEMY_KEY}
# Internal indexer — no auth, full method surface, large budget - id: indexer rateLimitBudget: indexer-tier forwardHeaders: ["X-Indexer-Job", "X-Request-Trace"] networks: - architecture: evm evm: { chainId: 1 } directiveDefaults: retryEmpty: true upstreams: - endpoint: ${INDEXER_RPC_URL}
# Backend services — secret auth, debug methods allowed - id: backend auth: strategies: - type: secret secret: { value: ${BACKEND_SECRET} } allowMethods: ["*", "debug_*", "trace_*"] rateLimitBudget: backend-tier upstreams: - endpoint: ${ARCHIVE_NODE_URL}Sub-pages
- Networks — per-chain failsafe, selection, integrity, static responses
- Upstreams — RPC endpoints, vendor shorthands, block-availability, scoring
- Providers — one-line vendor onboarding
- Selection policies — JS DSL for choosing which upstreams handle a request
- CORS — origin / method / header rules for browser callers
Copy for your AI assistant — full ProjectConfig referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.
ProjectConfig — every field
| Field | Type | Notes |
|---|---|---|
id | string | Required. Unique within the eRPC instance. Used in URL routing (/<id>/...), logs, metrics labels, and admin API. Stable IDs are important — they're embedded in dashboards and alerts. |
auth | AuthConfig | Per-project auth strategies. See Authentication. |
cors | CORSConfig | Per-project CORS policy. See CORS. |
providers | ProviderConfig[] | Vendor providers (auto-fan-out across all chains a vendor supports). See Providers. |
upstreamDefaults | UpstreamConfig | Default settings deep-merged into every entry in upstreams[]. Useful for shared jsonRpc, failsafe, or proxyPool defaults. |
upstreams | UpstreamConfig[] | RPC endpoints. See Upstreams. |
networkDefaults | NetworkDefaults | Default settings deep-merged into every entry in networks[] (including lazy-loaded ones). |
networks | NetworkConfig[] | Per-network overrides. See Networks. |
rateLimitBudget | string | ID of a budget under top-level rateLimiters.budgets[]. Project-wide limit, applied before any per-network or per-upstream limits. |
userAgentMode | "simplified"|"raw" | How the client's User-Agent header is bucketed for metric labels. simplified (default) groups by family (Chrome, Firefox, Go-http-client, etc.) — keeps cardinality low. raw uses the unmodified string — high cardinality, useful for debugging. |
forwardHeaders | string[] | List of HTTP header names to forward from the client request to the outbound upstream call. The standard hop-by-hop headers (Host, Connection, etc.) are stripped regardless. Use for tracing headers (X-Request-ID, traceparent) or app-defined context. |
ignoreMethods | string[] | Project-level method denylist (matcher syntax). Blocks methods across every upstream in this project. Combine with allowMethods for fine-grained control. |
allowMethods | string[] | Project-level method allowlist (matcher syntax). When set, blocks every method NOT in the list. Implicitly sets ignoreMethods: ["*"] when ignoreMethods is not set. |
scoreMetricsWindowSize | duration | Rolling window the health tracker uses for its per-upstream counters (errorRate, p50/p70/p95 latency, throttledRate, misbehaviorRate). 10 sliding buckets spanning this duration. Default 1m. Paired with the default evalInterval: 15s (4 metric samples per window) and probeExcluded.minSamplesWindow: 60s for symmetric ranking + re-admission behaviour. See Health-tracker window and Advanced tuning for the coupling between these three knobs. |
Routing behaviour (which upstream is primary, when to flip, when to exclude, hysteresis, cooldowns, …) lives in networks[].selectionPolicy. The default policy gives you production-grade routing out of the box.
clusterKey — top-level (shares behavior across the project tree)
The top-level clusterKey is not under projects[]; it sits at the root of the config. It identifies a logical group of eRPC replicas — useful when multiple instances coordinate via shared state.
clusterKey: erpc-prod-eu # all replicas in EU prod share this key
server: # ...
projects: # ...
database:
sharedState:
connector:
driver: redis
redis: { uri: redis://... }
# When set, this overrides the top-level clusterKey for shared-state specifically.
# clusterKey: erpc-prod-eu-sharedstateIf database.sharedState.clusterKey is set, it overrides the top-level value for shared-state operations only. For consistent behavior, leave only the top-level one set.
Defaults & merge semantics
networkDefaults and upstreamDefaults are deep-merged into each entry:
- Scalar fields (
rateLimitBudget,userAgentMode, etc.) — the entry-level value wins if set. - Object fields (
evm,directiveDefaults,jsonRpc) — deep merge per sub-field. - Array fields are NOT merged — if
networkDefaults.failsafeis set andnetworks[i].failsafeis also set, the entry's array completely replaces the defaults. Same forselectionPolicy.
userAgentMode — concrete behavior
The User-Agent header has very high natural cardinality (thousands of distinct strings even on a small site). The two modes:
| Mode | Example header → metric label |
|---|---|
simplified (default) | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36... → Chrome |
raw | Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36... → Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36... |
simplified buckets known agents — Chrome, Firefox, Safari, Edge, Brave, Curl, Go-http-client, Python-requests, Node-fetch, etc. Unknown agents fall through as unknown.
Use raw only when actively debugging a particular client's behavior.
forwardHeaders — what passes through
By default, eRPC does not forward client headers to upstreams (other than the body and content-type machinery it creates itself). forwardHeaders is an explicit allowlist:
projects:
- id: indexer
forwardHeaders:
- X-Request-ID
- traceparent # W3C trace context
- X-Indexer-JobHop-by-hop headers (Host, Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer, Transfer-Encoding, Upgrade) are stripped regardless of this list — that's a fundamental HTTP/1.1 invariant.
Use for distributed tracing (forward traceparent/tracestate) and customer-supplied request context.
ignoreMethods / allowMethods — project-level vs upstream-level
Both fields exist at:
- Project level — blocks across the whole project.
- Upstream level — blocks per upstream.
The interaction:
- A request is rejected by project-level
ignoreMethodsBEFORE any upstream is considered. - A request is rejected by upstream-level
ignoreMethodsonly when that specific upstream would otherwise serve it. allowMethodsprecedence is the same — project allow first, then upstream allow.- When
allowMethodsis set at either level andignoreMethodsis NOT, an implicitignoreMethods: ["*"]applies.
Use project-level for product-shape decisions ("this project never serves debug methods"). Use upstream-level for capability differences ("this archive node serves trace methods; this RPC vendor does not").
Routing & scoring tuning recipes
Stable production setup (default — change nothing):
routingStrategy: score-based
scoreGranularity: upstream
scoreRefreshInterval: 30s
scorePenaltyDecayRate: 0.95
scoreSwitchHysteresis: 0.10
scoreMinSwitchInterval: 2mFast failover — when reliability matters more than upstream cost stability:
scoreSwitchHysteresis: -1 # always pick the highest-scoring
scoreMinSwitchInterval: -1 # no cooldownReactive scoring — for upstreams that degrade quickly:
scorePenaltyDecayRate: 0.80 # recent metrics dominate
scoreRefreshInterval: 10s # faster ticksPer-method scoring — when method profiles differ a lot:
scoreGranularity: methodThis triples the metric series count (one score per (network, method, upstream)) — pair with scoreMetricsMode: compact to manage cardinality.
Common pitfalls
- Two projects with the same
id— eRPC fails to start. IDs are checked at config load. forwardHeadersincludesAuthorization— your upstream gets the client's auth header. Usually fine for vendor URLs (they ignore unknown auth schemes) but can confuse private upstreams. Be explicit about what's in the list.allowMethodsset at project level but you also want admin/debug methods — project-levelallowMethodsdoesn't combine with upstream-level. Use one or the other, not both.scoreGranularity: method× hundreds of methods × many upstreams — massive metric cardinality. Stick toupstreamgranularity unless you have a specific reason.scoreSwitchHysteresis: 0— primary switches on every refresh tick. Use0.10(default) or higher for stability.userAgentMode: rawin production with a Grafana Cloud / hosted Prometheus — can blow past cardinality limits in days. Stick tosimplified.
Append .llms.txt to this URL (or use the AI link above) to fetch the entire expanded reference as plain markdown for an AI assistant.