# Projects > Source: https://docs.erpc.cloud/config/projects > A project bundles a set of networks, upstreams, providers, auth, and rate-limit budgets — one eRPC instance can serve many projects (e.g. backend, indexer, frontend) with different cost/reliability profiles. > Format: machine-readable markdown export of the docs page above. > All collapsible AI sections are inlined and fully expanded. # Projects A 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: `///` or `//` (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](/config/projects/providers.llms.txt)) - **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 ```yaml projects: - id: main upstreams: - endpoint: alchemy://YOUR_API_KEY ``` That'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. **Config path:** `projects[]` **YAML — `erpc.yaml`:** ```yaml 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} ``` **TypeScript — `erpc.ts`:** ```typescript import { createConfig } from "@erpc-cloud/config"; export default createConfig({ projects: [ { id: "frontend", auth: { strategies: [{ type: "jwt", jwt: { verificationKeys: { "kid-1": process.env.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://\${process.env.ALCHEMY_KEY}\` }], }, { id: "indexer", rateLimitBudget: "indexer-tier", forwardHeaders: ["X-Indexer-Job", "X-Request-Trace"], networks: [{ architecture: "evm", evm: { chainId: 1 }, directiveDefaults: { retryEmpty: true } }], upstreams: [{ endpoint: process.env.INDEXER_RPC_URL }], }, { id: "backend", auth: { strategies: [{ type: "secret", secret: { value: process.env.BACKEND_SECRET } }] }, allowMethods: ["*", "debug_*", "trace_*"], rateLimitBudget: "backend-tier", upstreams: [{ endpoint: process.env.ARCHIVE_NODE_URL }], }, ], }); ``` ## Sub-pages - [Networks](/config/projects/networks.llms.txt) — per-chain failsafe, selection, integrity, static responses - [Upstreams](/config/projects/upstreams.llms.txt) — RPC endpoints, vendor shorthands, block-availability, scoring - [Providers](/config/projects/providers.llms.txt) — one-line vendor onboarding - [Selection policies](/config/projects/selection-policies.llms.txt) — JS DSL for choosing which upstreams handle a request - [CORS](/config/projects/cors.llms.txt) — origin / method / header rules for browser callers --- ### Copy for your AI assistant — full ProjectConfig reference ### `ProjectConfig` — every field | Field | Type | Notes | |---|---|---| | `id` | string | **Required.** Unique within the eRPC instance. Used in URL routing (`//...`), 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](/config/auth.llms.txt). | | `cors` | `CORSConfig` | Per-project CORS policy. See [CORS](/config/projects/cors.llms.txt). | | `providers` | `ProviderConfig[]` | Vendor providers (auto-fan-out across all chains a vendor supports). See [Providers](/config/projects/providers.llms.txt). | | `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](/config/projects/upstreams.llms.txt). | | `networkDefaults` | `NetworkDefaults` | Default settings deep-merged into every entry in `networks[]` (including lazy-loaded ones). | | `networks` | `NetworkConfig[]` | Per-network overrides. See [Networks](/config/projects/networks.llms.txt). | | `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](/config/projects/selection-policies.llms.txt#health-tracker-rolling-window) and [Advanced tuning](/config/projects/selection-policies.llms.txt#advanced-tuning-coupling-between-evalinterval-scoremetricswindowsize--probe-window) 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`](/config/projects/selection-policies.llms.txt)**. 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. ```yaml 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-sharedstate ``` If `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.failsafe` is set and `networks[i].failsafe` is also set, the entry's array completely replaces the defaults. Same for `selectionPolicy`. ### `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: ```yaml projects: - id: indexer forwardHeaders: - X-Request-ID - traceparent # W3C trace context - X-Indexer-Job ``` Hop-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: 1. **Project level** — blocks across the whole project. 2. **Upstream level** — blocks per upstream. The interaction: - A request is rejected by project-level `ignoreMethods` BEFORE any upstream is considered. - A request is rejected by upstream-level `ignoreMethods` only when that specific upstream would otherwise serve it. - `allowMethods` precedence is the same — project allow first, then upstream allow. - When `allowMethods` is set at either level and `ignoreMethods` is NOT, an implicit `ignoreMethods: ["*"]` 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): ```yaml routingStrategy: score-based scoreGranularity: upstream scoreRefreshInterval: 30s scorePenaltyDecayRate: 0.95 scoreSwitchHysteresis: 0.10 scoreMinSwitchInterval: 2m ``` **Fast failover** — when reliability matters more than upstream cost stability: ```yaml scoreSwitchHysteresis: -1 # always pick the highest-scoring scoreMinSwitchInterval: -1 # no cooldown ``` **Reactive scoring** — for upstreams that degrade quickly: ```yaml scorePenaltyDecayRate: 0.80 # recent metrics dominate scoreRefreshInterval: 10s # faster ticks ``` **Per-method scoring** — when method profiles differ a lot: ```yaml scoreGranularity: method ``` This 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. - **`forwardHeaders` includes `Authorization`** — 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. - **`allowMethods` set at project level but you also want admin/debug methods** — project-level `allowMethods` doesn't combine with upstream-level. Use one or the other, not both. - **`scoreGranularity: method` × hundreds of methods × many upstreams** — massive metric cardinality. Stick to `upstream` granularity unless you have a specific reason. - **`scoreSwitchHysteresis: 0`** — primary switches on every refresh tick. Use `0.10` (default) or higher for stability. - **`userAgentMode: raw` in production with a Grafana Cloud / hosted Prometheus** — can blow past cardinality limits in days. Stick to `simplified`. --- > **TIP** > 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.