# Directives > Source: https://docs.erpc.cloud/operation/directives > Send an HTTP header or query param and change routing, caching, validation, or consensus for exactly that one request — no restarts, no config changes. > Format: machine-readable markdown export of the docs page above. > All collapsible AI sections are inlined and fully expanded. # Directives Every request can carry its own instructions. Add an `X-ERPC-*` header or a `?param=` query string and eRPC instantly shifts routing, caching, integrity checks, or consensus behavior for that call only. No server restart. No config change. The same 23 directives can also be pinned as `directiveDefaults` on any network so every call starts with a sensible baseline. After each response, eRPC echoes back what actually happened — which upstream won, cache hit or miss, retry and hedge counts — so you always know exactly what the proxy did. ## Quick taste Per request, the way directives are actually used — pin one call to specific upstreams via header or query string: **File:** `terminal` ```bash # via header: only Alchemy upstreams may serve this request curl https://rpc.example.com/main/evm/1 \\ -H 'X-ERPC-Use-Upstream: alchemy-*' \\ -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' # same directive as a query parameter curl 'https://rpc.example.com/main/evm/1?use-upstream=alchemy-*' \\ -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' ``` Directives that make sense as a permanent baseline can be pinned per network with `directiveDefaults` — headers and query params still override them per request: **Config path:** `projects[].networks[].directiveDefaults` **YAML — `erpc.yaml`:** ```yaml projects: - id: main networks: - architecture: evm evm: { chainId: 1 } directiveDefaults: # retry when upstream returns null/empty (e.g. not-yet-indexed block) retryEmpty: true # reject responses whose block number is behind the known tip enforceHighestBlock: true ``` **TypeScript — `erpc.ts`:** ```typescript projects: [{ id: "main", networks: [{ architecture: "evm", evm: { chainId: 1 }, directiveDefaults: { // retry when upstream returns null/empty (e.g. not-yet-indexed block) retryEmpty: true, // reject responses whose block number is behind the known tip enforceHighestBlock: true, }, }], }] ``` ## Agent reference Copy one of these prompts into your AI agent session (Claude Code, Cursor, …) — each one points the agent at this page's machine-readable reference so it can do the work correctly: **Prompt Example #1: set sane directive defaults for a new network** ```text I'm adding a new EVM network to my eRPC config and want to pin sensible directiveDefaults — retryEmpty, enforceHighestBlock, validateTransactionsRoot, and useUpstream routing for archival calls. Read the full reference first: https://docs.erpc.cloud/operation/directives.llms.txt ``` **Prompt Example #2: audit and tighten my existing directive defaults** ```text Review the directiveDefaults blocks in my eRPC config: check which validation directives are on by default but may be incompatible with non-standard chains (zkSync, Filecoin, Kaia style), flag any retryPending: true set globally (dangerous), and suggest per-network overrides. Read the reference: https://docs.erpc.cloud/operation/directives.llms.txt ``` **Prompt Example #3: pin an archival upstream for indexer traffic** ```text My indexer sends eth_getLogs and eth_getBlockReceipts requests that should always hit an archival upstream tagged family:archival in my eRPC config. Configure directiveDefaults.useUpstream so those calls are pinned to that group and add the right receipt-validation directives for indexing correctness. Reference: https://docs.erpc.cloud/operation/directives.llms.txt ``` **Prompt Example #4: debug why X-ERPC-Retry-Empty is not retrying** ```text My requests have X-ERPC-Retry-Empty: true in the header but eRPC is not retrying empty results. Walk me through why this might happen (boolean parse rules, EmptyResultMaxAttempts cap, gRPC path limitation) and how to verify via the X-ERPC-Attempts response header that retries are actually firing. Work with my existing eRPC config. Reference: https://docs.erpc.cloud/operation/directives.llms.txt ``` **Prompt Example #5: reduce diagnostic header noise in production logs** ```text Our load balancer logs every X-ERPC-Upstreams response header and it's adding megabytes to our access logs. Configure executionHeaders on the server to suppress the per-attempt trace while keeping the counter headers for dashboards. Work with my existing eRPC config. Reference: https://docs.erpc.cloud/operation/directives.llms.txt ``` --- ### Directives — full agent reference ### How it works **Parsing pipeline.** For every HTTP request eRPC runs two passes. First, `ApplyDirectiveDefaults` copies any `directiveDefaults` config block into the request struct (lowest priority). Second, `EnrichFromHttp` scans all 23 registered header and query names. If none are present it returns immediately after extracting User-Agent — zero allocations, zero locks. When directive inputs are present the struct is cloned before mutation so batch sub-requests that share the same pointer do not race. Precedence from lowest to highest: `directiveDefaults` config → HTTP header → URL query parameter. A query-param value always wins over the same header, which always wins over config. `ApplyDirectiveDefaults` is idempotent — once `r.directives` is non-nil every subsequent call is a no-op, so per-request overrides can never be clobbered by a second config pass. Source: [`common/request.go:563-676`](https://github.com/erpc/erpc/blob/main/common/request.go#L563-L676). **Boolean parsing rule.** For all 23 directive headers the only truthy value is the exact string `"true"` (case-insensitive, whitespace stripped). `"1"` and `"yes"` are not truthy. `X-ERPC-Force-Trace` is handled by the tracing subsystem separately and accepts all three. Always use `"true"` to avoid this asymmetry. Confirmed: [`common/request_test.go:406-427`](https://github.com/erpc/erpc/blob/main/common/request_test.go#L406-L427). **Exception for headers #20–23.** `X-ERPC-Validate-Header-Field-Lengths`, `X-ERPC-Validate-Transaction-Fields`, `X-ERPC-Validate-Transaction-Block-Info`, and `X-ERPC-Validate-Log-Fields` parse with `strings.ToLower` only (no `TrimSpace`), so `" true "` evaluates to `false` for these four. Query-param parsers always apply `TrimSpace`. Source: [`common/request.go:798-807`](https://github.com/erpc/erpc/blob/main/common/request.go#L798-L807). **`directiveDefaults` placement.** Settable at `projects[].networks[].directiveDefaults` (per network) or `projects[].networks[].failsafe.directiveDefaults` (failsafe scope). Every `*bool` field has three states: nil (omitted — request field stays at Go zero `false`), pointer to `false` (explicitly written), pointer to `true`. The nil check in `ApplyDirectiveDefaults` is a "was this field set?" guard, not a multi-level inheritance chain. **gRPC path.** `EnrichFromHttp` is never called on the gRPC path. gRPC callers receive only `directiveDefaults`; per-request header overrides are not available. Source: [`erpc/request_processor.go:35-67`](https://github.com/erpc/erpc/blob/main/erpc/request_processor.go#L35-L67). ### Config schema Config struct: [`common/config.go:2105-2152`](https://github.com/erpc/erpc/blob/main/common/config.go#L2105-L2152). Applied by `ApplyDirectiveDefaults` at [`common/request.go:563-676`](https://github.com/erpc/erpc/blob/main/common/request.go#L563-L676). #### Complete directive registry (all 23) | # | HTTP header | Query param | Type | Config field | Default | Effect | Consumed at | |---|---|---|---|---|---|---|---| | 1 | `X-ERPC-Retry-Empty` | `retry-empty` | bool | `retryEmpty` | `false` | Retry on null/empty upstream response. Subject to `EmptyResultMaxAttempts` cap. | [`erpc/network_executor.go:427-442`](https://github.com/erpc/erpc/blob/main/erpc/network_executor.go#L427-L442) | | 2 | `X-ERPC-Retry-Pending` | `retry-pending` | bool | `retryPending` | `false` (struct comment "true by default" is **stale**) | Retry `eth_getTransactionReceipt` / `eth_getTransactionByHash` / `eth_getTransactionByBlockHashAndIndex` / `eth_getTransactionByBlockNumberAndIndex` while tx is pending. `EmptyResultMaxAttempts` cap applies. | [`erpc/network_executor.go:445-459`](https://github.com/erpc/erpc/blob/main/erpc/network_executor.go#L445-L459) | | 3 | `X-ERPC-Skip-Cache-Read` | `skip-cache-read` | string (bool in config YAML normalized to string) | `skipCacheRead` | `""` (no skip) | `"true"` = skip all; `"false"`/`""` = use cache; other string = connector-ID WildcardMatch pattern. YAML `true`/`false` normalized to strings via `fmt.Sprintf("%v", v)`. Header NOT trimmed; query IS trimmed. | [`common/request.go:898-914`](https://github.com/erpc/erpc/blob/main/common/request.go#L898-L914) | | 4 | `X-ERPC-Use-Upstream` | `use-upstream` | string | `useUpstream` | `""` (no filter) | Selector matched against upstream `id`; for purely-positive patterns also against upstream `Tags` (any tag match wins). Negated patterns (`!`) match ID only. Header NOT trimmed; query IS trimmed. No match → `ErrUpstreamsExhausted`. Activates selector-scoped served-tip partitioning. Stateful methods with multiple matching upstreams fail with `ErrNotImplemented`. | [`upstream/upstream.go:1526-1534`](https://github.com/erpc/erpc/blob/main/upstream/upstream.go#L1526-L1534) | | 5 | `X-ERPC-Skip-Interpolation` | `skip-interpolation` | bool | `skipInterpolation` | `false` | Suppresses block-tag → hex substitution in forwarded params. Internal block refs still computed/cached. | [`architecture/evm/json_rpc.go:132`](https://github.com/erpc/erpc/blob/main/architecture/evm/json_rpc.go#L132) | | 6 | `X-ERPC-Skip-Consensus` | `skip-consensus` | bool | `skipConsensus` | `false` | Bypasses consensus branch; uses standard `retry(hedge(upstreamSweep))`. Retry/hedge/breaker/timeout still apply. | [`erpc/network_executor.go:179-188`](https://github.com/erpc/erpc/blob/main/erpc/network_executor.go#L179-L188) | | 7 | `X-ERPC-Enforce-Highest-Block` | `enforce-highest-block` | bool | `enforceHighestBlock` | **`true`** (SetDefaults) | `eth_getBlockByNumber` with `"latest"`/`"finalized"`: re-routes if response is behind known highest block. Skipped for cached responses. | [`architecture/evm/eth_getBlockByNumber.go:111-170`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockByNumber.go#L111-L170) | | 8 | `X-ERPC-Enforce-GetLogs-Range` | `enforce-getlogs-range` | bool | `enforceGetLogsBlockRange` | **`true`** (SetDefaults) | Pre-forward: rejects `eth_getLogs`/`trace_filter` if block range exceeds `getLogsMaxAllowedRange`. Consumed via legacy `EvmIntegrityConfig` (not a true per-request override — see Edge cases). | [`architecture/evm/eth_getLogs.go:280-288`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getLogs.go#L280-L288) | | 9 | `X-ERPC-Enforce-Non-Null-Tagged-Blocks` | `enforce-non-null-tagged-blocks` | bool | `enforceNonNullTaggedBlocks` | **`true`** (SetDefaults) | Treats null block for tag-based `eth_getBlockByNumber` as an error. Disable for chains that legitimately return null for certain tags (e.g., zkSync). | [`architecture/evm/eth_getBlockByNumber.go:304`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockByNumber.go#L304) | | 10 | `X-ERPC-Enforce-Log-Index-Strict-Increments` | `enforce-log-index-strict-increments` | bool | `enforceLogIndexStrictIncrements` | `false` | `eth_getBlockReceipts`: log indices must increment by exactly 1 **globally** across all receipts (counter does not reset per receipt). | [`architecture/evm/eth_getBlockReceipts.go:399-416`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L399-L416) | | 11 | `X-ERPC-Validate-Logs-Bloom-Emptiness` | `validate-logs-bloom-emptiness` | bool | `validateLogsBloomEmptiness` | `false` | Logs exist → bloom non-zero; no logs → bloom zero. | [`architecture/evm/eth_getBlockReceipts.go:307-321`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L307-L321) | | 12 | `X-ERPC-Validate-Logs-Bloom-Match` | `validate-logs-bloom-match` | bool | `validateLogsBloomMatch` | `false` | Recomputes 2048-bit Bloom filter from receipt logs (keccak256) and verifies against `logsBloom`. CPU-intensive. For `eth_getBlockReceipts` only (no ground truth needed — logs are embedded in each receipt). **Bloom algorithm:** for each log, feeds the 20-byte address and each 32-byte topic into `bloomAdd`; computes three bit positions using keccak256 — for each k = 0..2, pos equals bytes 2k and 2k+1 of keccak256(value) joined as a big-endian 16-bit value masked to 11 bits (`& 0x7FF`); each position sets one bit in the big-endian 256-byte array. Receipts with empty logs are skipped. Source: [`architecture/evm/eth_getBlockReceipts.go:419-480`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L419-L480) | | 13 | `X-ERPC-Validate-Tx-Hash-Uniqueness` | `validate-tx-hash-uniqueness` | bool | `validateTxHashUniqueness` | `false` | Rejects empty `transactionHash` AND duplicates — stricter than always-on dedup (which only catches duplicates among non-empty hashes). | [`architecture/evm/eth_getBlockReceipts.go:202-213`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L202-L213) | | 14 | `X-ERPC-Validate-Transaction-Index` | `validate-transaction-index` | bool | `validateTransactionIndex` | `false` | Each receipt's `transactionIndex` must equal its array position `i`. Absent fields skipped. | [`architecture/evm/eth_getBlockReceipts.go:217-233`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L217-L233) | | 15 | `X-ERPC-Receipts-Count-Exact` | `receipts-count-exact` | int64 | `receiptsCountExact` | nil | `eth_getBlockReceipts` must return exactly N receipts. Parse failure silently skips. | [`architecture/evm/eth_getBlockReceipts.go:149-155`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L149-L155) | | 16 | `X-ERPC-Receipts-Count-At-Least` | `receipts-count-at-least` | int64 | `receiptsCountAtLeast` | nil | `eth_getBlockReceipts` must return at least N receipts. Parse failure silently skips. | [`architecture/evm/eth_getBlockReceipts.go:157-163`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L157-L163) | | 17 | `X-ERPC-Validation-Expected-Block-Hash` | `validation-expected-block-hash` | string | `validationExpectedBlockHash` | `""` | All receipts must carry this block hash. Case-insensitive, `0x` prefix stripped at comparison. | [`architecture/evm/eth_getBlockReceipts.go:168-170`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L168-L170) | | 18 | `X-ERPC-Validation-Expected-Block-Number` | `validation-expected-block-number` | int64 | `validationExpectedBlockNumber` | nil | All receipts must carry this block number. | [`architecture/evm/eth_getBlockReceipts.go:182-184`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L182-L184) | | 19 | `X-ERPC-Validate-Transactions-Root` | `validate-transactions-root` | bool | `validateTransactionsRoot` | **`true`** (SetDefaults) | Checks `transactionsRoot` against transaction list. Disable for non-standard chains. | [`architecture/evm/eth_getBlockByNumber.go:515`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockByNumber.go#L515) | | 20 | `X-ERPC-Validate-Header-Field-Lengths` | `validate-header-field-lengths` | bool | `validateHeaderFieldLengths` | `false` | Block header hash fields must be 32 bytes; `logsBloom` 256 bytes. **Parses with `ToLower` only (no `TrimSpace`)** — `" true "` → `false`. | [`architecture/evm/eth_getBlockByNumber.go:615-683`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockByNumber.go#L615-L683) | | 21 | `X-ERPC-Validate-Transaction-Fields` | `validate-transaction-fields` | bool | `validateTransactionFields` | `false` | Each tx `hash` must be 32 bytes; no duplicates. Hash-only blocks bypass entirely. **Parses without `TrimSpace`**. | [`architecture/evm/eth_getBlockByNumber.go:687-708`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockByNumber.go#L687-L708) | | 22 | `X-ERPC-Validate-Transaction-Block-Info` | `validate-transaction-block-info` | bool | `validateTransactionBlockInfo` | `false` | Per-tx: `blockHash` matches block hash; `blockNumber` matches; `transactionIndex` matches array position. Full-object txs only. **Parses without `TrimSpace`**. | [`architecture/evm/eth_getBlockByNumber.go:711-753`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockByNumber.go#L711-L753) | | 23 | `X-ERPC-Validate-Log-Fields` | `validate-log-fields` | bool | `validateLogFields` | `false` | Per log: address 20 bytes, each topic 32 bytes, topic count ≤ `MaxTopics`, context fields match enclosing receipt. Absent fields skipped. **Parses without `TrimSpace`**. | [`architecture/evm/eth_getBlockReceipts.go:324-397`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L324-L397) | #### Config-only directives (no HTTP header or query param) | Field | Config path | Default | Notes | |---|---|---|---| | `validateReceiptTransactionMatch` | `directiveDefaults.validateReceiptTransactionMatch` | `false` | Requires `GroundTruthTransactions` (library mode). Silently no-op over HTTP. **Algorithm:** asserts `len(receipts) == len(GroundTruthTransactions)` (count mismatch → `ErrEndpointContentValidation`), then for each index `i`: `receipt[i].transactionHash` decoded from hex must equal `GroundTruthTransactions[i].Hash` (raw bytes). Source: [`architecture/evm/eth_getBlockReceipts.go:235-300`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L235-L300) | | `validateContractCreation` | `directiveDefaults.validateContractCreation` | `false` | Only active inside the `ValidateReceiptTransactionMatch` loop; requires `GroundTruthTransactions`. **Algorithm:** for each transaction `gtTx`: if `gtTx.To` is nil/empty (contract-creation tx), receipt MUST have a non-empty `contractAddress` decoding to exactly 20 bytes; if `gtTx.To` is non-empty (regular tx), receipt MUST NOT have `contractAddress`. Any violation → `ErrEndpointContentValidation`. Source: [`architecture/evm/eth_getBlockReceipts.go:264-299`](https://github.com/erpc/erpc/blob/main/architecture/evm/eth_getBlockReceipts.go#L264-L299) | | `GroundTruthTransactions` | Go library mode only (`json:"-"`) | n/a (inject at call site) | Slice of `*GroundTruthTransaction` (fields: `Hash []byte`, `To []byte`, `TransactionIndex *uint32`). Required to activate `validateReceiptTransactionMatch` and `validateContractCreation`. Cannot be supplied over HTTP. Source: [`common/request.go:206-226`](https://github.com/erpc/erpc/blob/main/common/request.go#L206-L226) | | `GroundTruthLogs` | Go library mode only (`json:"-"`) | n/a (inject at call site) | Slice of `*GroundTruthLog` (fields: `Address []byte`, `Topics [][]byte`). Enables bloom validation (`validateLogsBloomMatch`) for methods where logs are not embedded in the response (e.g., `eth_getBlockByNumber`). For `eth_getBlockReceipts`, logs are embedded in each receipt so no ground truth is needed. Source: [`common/request.go:212-214`](https://github.com/erpc/erpc/blob/main/common/request.go#L212-L214) | #### `server.executionHeaders` — response diagnostic header mode | Value | Effect | Source | |---|---|---| | `"all"` (default) | Full counter headers + `X-ERPC-Upstreams` per-attempt trace | [`erpc/http_server.go:1081-1086`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L1081-L1086) | | `"summary"` | All counter headers; `X-ERPC-Upstreams` trace suppressed only | [`erpc/http_server.go:1105-1127`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L1105-L1127) | | `"off"` | No `X-ERPC-*` diagnostics (version/commit still emitted) | [`erpc/http_server.go:1105-1127`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L1105-L1127) | ### Worked examples All patterns below are distilled from real production fleets; comments explain the non-obvious choices. **1. Global network defaults: retryEmpty on, retryPending explicitly off.** Production fleets set `retryEmpty: true` at `networkDefaults` so every network retries null/empty upstream responses (e.g., a not-yet-indexed block) without per-network config. `retryPending: false` is explicit because the struct's stale "true by default" comment causes confusion — production configs state it clearly: **Config path:** `projects[].networkDefaults.directiveDefaults` **YAML — `erpc.yaml`:** ```yaml networkDefaults: directiveDefaults: # Retry null/empty results globally — covers block-availability races # on eth_getLogs, eth_getBlockByNumber, eth_getTransactionReceipt. retryEmpty: true # retryPending default is false despite stale struct comment; # pin it explicitly so intent is clear to future maintainers. retryPending: false ``` **TypeScript — `erpc.ts`:** ```typescript networkDefaults: { directiveDefaults: { // Retry null/empty results globally — covers block-availability races // on eth_getLogs, eth_getBlockByNumber, eth_getTransactionReceipt. retryEmpty: true, // retryPending default is false despite stale struct comment; // pin it explicitly so intent is clear to future maintainers. retryPending: false, }, } ``` **2. Non-standard chain: disable validateTransactionsRoot.** zkSync Era, Kaia, Filecoin, HyperEVM, Sophon, Abstract, and Etherlink all use non-standard transaction-trie roots that fail eRPC's `validateTransactionsRoot` check. Production configs disable it per-network while inheriting all other defaults. zkSync and similar ZK-rollups also disable `enforceNonNullTaggedBlocks` because their chains can legitimately return null for certain block tags: **Config path:** `projects[].networks[].directiveDefaults` **YAML — `erpc.yaml`:** ```yaml networks: - evm: chainId: 324 # zkSync Era mainnet integrity: # zkSync can return null for certain block tags — don't treat as error enforceNonNullTaggedBlocks: false directiveDefaults: # zkSync uses a custom transaction trie; root will never match validateTransactionsRoot: false ``` **TypeScript — `erpc.ts`:** ```typescript networks: [{ evm: { chainId: 324, // zkSync Era mainnet integrity: { // zkSync can return null for certain block tags — don't treat as error enforceNonNullTaggedBlocks: false, }, }, directiveDefaults: { // zkSync uses a custom transaction trie; root will never match validateTransactionsRoot: false, }, }] ``` **3. Indexer workload — full receipt integrity.** When backfilling a chain for an indexer you want to catch any upstream that sends partial or misordered receipts. Enable the receipt cluster at `directiveDefaults` so every call on that network is validated automatically: **Config path:** `projects[].networks[].directiveDefaults` **YAML — `erpc.yaml`:** ```yaml directiveDefaults: retryEmpty: true # tx-receipt polling — only enable per-indexer-network, not globally retryPending: true enforceHighestBlock: true validateTransactionsRoot: true # reject receipts with empty or duplicate tx hashes validateTxHashUniqueness: true # each receipt must appear at its array position validateTransactionIndex: true # log indices must increment globally across all receipts in the block enforceLogIndexStrictIncrements: true ``` **TypeScript — `erpc.ts`:** ```typescript directiveDefaults: { retryEmpty: true, // tx-receipt polling — only enable per-indexer-network, not globally retryPending: true, enforceHighestBlock: true, validateTransactionsRoot: true, // reject receipts with empty or duplicate tx hashes validateTxHashUniqueness: true, // each receipt must appear at its array position validateTransactionIndex: true, // log indices must increment globally across all receipts in the block enforceLogIndexStrictIncrements: true, } ``` **4. Per-request upstream pinning.** A dApp wants to read its own just-submitted transaction from the same upstream it used to broadcast, bypassing the normal load-balanced pool. Send the header at call time — no config change needed: ```http POST /1/main HTTP/1.1 X-ERPC-Use-Upstream: alchemy-mainnet X-ERPC-Retry-Pending: true ``` The upstream selector supports wildcards and tags: `"alchemy-*"`, `"alchemy-mainnet|quicknode-*"`, `"family:archival"` (tag match), `"!drpc"` (exclude one by ID). **5. Per-call cache bypass for fresh data.** A price-feed service needs uncached `eth_call` results on every tick. Send `X-ERPC-Skip-Cache-Read: true` per request; write-back still fires so the next caller gets the fresh value from cache: ```http POST /1/main HTTP/1.1 X-ERPC-Skip-Cache-Read: true ``` To bypass only the in-memory tier and keep Redis warm reads: `X-ERPC-Skip-Cache-Read: memory*`. **6. Skipping consensus for internal tooling.** A monitoring script wants low-latency reads and trusts a single upstream. Bypass the consensus quorum for that call: ```http POST /1/main HTTP/1.1 X-ERPC-Skip-Consensus: true ``` Retry, hedge, circuit-breaker, and timeout still apply — only the dispute/agreement step is skipped. ### Request/response behavior **Request headers — canonical truthy value.** All 23 directive headers require `"true"` (any case, optional surrounding whitespace). `"1"` and `"yes"` evaluate to `false` and silently have no effect. Exception: `X-ERPC-Force-Trace` (tracing subsystem, not a directive) accepts `"true"`, `"1"`, or `"yes"`. Always use `"true"`. **Directive-adjacent request inputs** (not in the 23-directive registry): | Input | Kind | Values / behavior | Source | |---|---|---|---| | `X-ERPC-Force-Trace` | header | `"true"`, `"1"`, or `"yes"` → bypass OTel trace sampling for that request (attribute `erpc.force_trace = true`) | [`common/tracing_util.go:107-115`](https://github.com/erpc/erpc/blob/main/common/tracing_util.go#L107-L115) | | `force-trace` | query param | same three truthy values | [`common/tracing_core.go:31`](https://github.com/erpc/erpc/blob/main/common/tracing_core.go#L31) | | `user-agent` | query param | overrides `User-Agent` header for agent-name metrics tracking | [`common/request.go:1299-1314`](https://github.com/erpc/erpc/blob/main/common/request.go#L1299-L1314) | | `User-Agent` | header | stored raw or simplified per `project.userAgentMode`; drives agent-name metric labels | [`common/request.go:1300-1311`](https://github.com/erpc/erpc/blob/main/common/request.go#L1300-L1311) | | `networkId` | JSON body field | `"evm:42161"`-style network selection when architecture/chain are absent from the URL path | [`erpc/http_server.go:613-634`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L613-L634) | **Response headers always emitted (every HTTP response, not controlled by `executionHeaders`):** | Header | Value | Source | |---|---|---| | `Content-Type` | `application/json` | [`erpc/http_server.go:259`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L259) | | `X-ERPC-Version` | eRPC version string | [`erpc/http_server.go:260`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L260) | | `X-ERPC-Commit` | git commit SHA | [`erpc/http_server.go:261`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L261) | | custom `server.responseHeaders` | static values, env-expanded at startup | [`erpc/http_server.go:263-266`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L263-L266) | **Execution diagnostic headers** (single responses only; controlled by `server.executionHeaders`; never emitted for batch): | Header | Value | When | |---|---|---| | `X-ERPC-Attempts` | total physical ops (upstream + cache) | always | | `X-ERPC-Upstream-Attempts` / `X-ERPC-Upstream-Retries` / `X-ERPC-Upstream-Hedges` | counters | always | | `X-ERPC-Network-Attempts` / `X-ERPC-Network-Retries` / `X-ERPC-Network-Hedges` | counters | always | | `X-ERPC-Cache-Attempts` / `X-ERPC-Cache-Retries` / `X-ERPC-Cache-Hedges` | counters | only when > 0 | | `X-ERPC-Consensus-Slots` / `X-ERPC-Consensus-Disputes` / `X-ERPC-Consensus-Low-Participants` | counters | only when > 0 | | `X-ERPC-Cache` | `HIT` or `MISS` | when response metadata present | | `X-ERPC-Upstream` | winning upstream id | when known | | `X-ERPC-Duration` | milliseconds | NormalizedResponse only | | `X-ERPC-Upstreams` | per-attempt trace: `upstreamId=reason:outcome:durationMs[:won]` segments joined by `;`. Known `reason` values: `primary`, `hedge`, `retry`, `consensus_slot`. Known `outcome` values: `success`, `timeout`, `exec_revert`, `rate_limited`. `:won` suffix appended only when the attempt's response contributed to the final answer. | `all` mode only; when `len(attempts) > 0` | Example `X-ERPC-Upstreams` value: `alchemy=primary:success:50ms:won;quicknode=hedge:timeout:5000ms;drpc=consensus_slot:exec_revert:20ms`. **Error shapes produced by directive checks.** Failed validation directives return `ErrEndpointContentValidation`. A `UseUpstream` selector that matches no upstream returns `ErrUpstreamsExhausted` (wrapping `ErrUpstreamNotAllowed` per candidate) — there is no distinct "selector matched nothing" error code. ### Best practices - **Start with `enforceHighestBlock: true` and `validateTransactionsRoot: true`** — both are on by default and catch the most common upstream data-quality issues with negligible overhead. - **Turn on the receipt-validation cluster only for indexing workloads** — `validateLogsBloomMatch`, `enforceLogIndexStrictIncrements`, `validateLogFields` are CPU-intensive; they penalize latency for general-purpose proxy traffic. - **Use `retryEmpty: true` for block-polling calls** but verify `EmptyResultMaxAttempts` is set appropriately; unbounded retries on a degraded upstream can exhaust the timeout budget. - **Never set `retryPending: true` globally** — it converts every pending-tx lookup into a polling loop. Pin it per request (`X-ERPC-Retry-Pending: true`) or to a dedicated network for transaction-tracking flows. - **Pin `useUpstream` at config via `directiveDefaults` for known-good archival nodes** rather than relying on callers to send the header — this prevents a misconfigured client from silently routing archival calls to full nodes. - **Always send `"true"`, never `"1"` or `"yes"`**, for all `X-ERPC-*` directive headers. Four of the 23 headers (#20–23) lack `TrimSpace` — a value like `" true "` (with spaces) evaluates to `false` on those. - **On gRPC, per-request overrides are not available.** Wire all desired defaults into `directiveDefaults` in config; `EnrichFromHttp` is never called on the gRPC path. ### Edge cases & gotchas 1. **`RetryPending` default is `false`, not `true`.** The struct comment at `common/request.go:125` ("true by default") is stale. No `SetDefaults` entry exists. Must be explicitly enabled via `directiveDefaults.retryPending: true` or `X-ERPC-Retry-Pending: true`. 2. **`X-ERPC-Use-Upstream` header is NOT trimmed; query IS.** A header with leading/trailing spaces will not match any upstream. Source: [`common/request.go:741`](https://github.com/erpc/erpc/blob/main/common/request.go#L741) vs [`:812`](https://github.com/erpc/erpc/blob/main/common/request.go#L812). 3. **Headers #20–23 lack `TrimSpace`.** `" true "` → `false` for `X-ERPC-Validate-Header-Field-Lengths`, `X-ERPC-Validate-Transaction-Fields`, `X-ERPC-Validate-Transaction-Block-Info`, `X-ERPC-Validate-Log-Fields`. All other boolean directive headers do trim. Source: [`common/request.go:798,801,804,807`](https://github.com/erpc/erpc/blob/main/common/request.go#L798-L807). 4. **`"1"` and `"yes"` are NOT truthy for directive headers.** Only `"true"` (any case). This differs from `X-ERPC-Force-Trace`. Source: [`common/request_test.go:406-427`](https://github.com/erpc/erpc/blob/main/common/request_test.go#L406-L427). 5. **`UseUpstream` failure produces `ErrUpstreamsExhausted`, not a selector-specific error.** Operators must parse the error message text to diagnose selector mismatches. Source: [`common/errors.go:1432-1440`](https://github.com/erpc/erpc/blob/main/common/errors.go#L1432-L1440). 6. **`X-ERPC-Skip-Consensus: false` actively disables consensus bypass.** Sending the header with the value `"false"` is NOT the same as omitting the header. A header value `"false"` parses to `false` and overrides a `directiveDefaults.skipConsensus: true` config entry — the consensus branch then runs normally. Omitting the header leaves `SkipConsensus` at its config-default value. Tested: [`common/request_test.go:409`](https://github.com/erpc/erpc/blob/main/common/request_test.go#L409), [`erpc/skip_consensus_directive_test.go:125-153`](https://github.com/erpc/erpc/blob/main/erpc/skip_consensus_directive_test.go#L125-L153). 7. **`EnforceGetLogsBlockRange` cannot be overridden per-request via HTTP.** `SetDefaults` copies `DirectiveDefaults` values into `Evm.Integrity`; the architecture layer reads from the network config struct, not the per-request directive. Source: [`common/defaults.go:1957-1964`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1957-L1964). 8. **`EnforceLogIndexStrictIncrements` counts globally, not per-receipt.** Indices must be contiguous across all receipts in the block response. Upstreams that reset `logIndex` per-receipt fail this check. 9. **`ValidateTxHashUniqueness` rejects empty hashes; always-on dedup does not.** Enabling this on a chain that legitimately omits transaction hashes causes all responses to fail. 10. **`ReceiptsCountExact` / `ReceiptsCountAtLeast` parse failures are silent.** A non-integer value leaves the pointer nil; no check fires and no warning is logged. 11. **`ValidateReceiptTransactionMatch` and `ValidateContractCreation` are silently no-ops over HTTP.** `GroundTruthTransactions` cannot be supplied via headers; both directives only activate in Go library mode. 12. **`ValidateTransactionFields` and `ValidateTransactionBlockInfo` skip hash-only blocks.** When `eth_getBlockByNumber` returns hash-only transactions, the validation loop is entirely bypassed. 13. **Batch responses never emit `X-ERPC-*` diagnostic headers.** Use per-response `id` fields for batch diagnostics. 14. **`executionHeaders: summary` removes only `X-ERPC-Upstreams`.** All counter and metadata headers still emit. Use `"off"` to suppress everything. 15. **Selector-scoped served-tip partitions are capped.** Beyond `maxServedTipPartitions` per network, no partition is created and the stateless fallback is used silently — no error. Source: [`erpc/networks.go:429`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L429). 16. **`ValidateLogFields` cross-checks log fields against the enclosing receipt, not the block.** Absent log context fields (e.g., `blockHash` missing from a log entry) pass silently even when the receipt's field is set. 17. **`nil *bool` in `DirectiveDefaultsConfig` ≠ `false *bool`.** Nil means "not set — skip"; a `*false` pointer means "explicitly disable". Only non-nil pointers are applied in `ApplyDirectiveDefaults`. Source: [`common/request.go:570-580`](https://github.com/erpc/erpc/blob/main/common/request.go#L570-L580). 18. **`ValidateHeaderFieldLengths` struct comment is stale.** `common/request.go:172` says "only via config/library, not HTTP headers" — incorrect. The directive is fully HTTP-settable. Source: [`common/request.go:797-798`](https://github.com/erpc/erpc/blob/main/common/request.go#L797-L798). ### Observability Directives have no dedicated Prometheus metrics; their effects appear in existing counters. | Metric | Type | When it fires | |---|---|---| | `erpc_upstream_request_retries_total{reason="empty_result"}` | counter | Each retry triggered by `RetryEmpty` | | `erpc_upstream_request_retries_total{reason="pending_tx"}` | counter | Each retry triggered by `RetryPending` | | `erpc_upstream_request_retries_total{reason="integrity_validation"}` | counter | Each retry due to a failed validation directive | | `erpc_network_consensus_rounds_total` | counter | Consensus rounds; `SkipConsensus=true` prevents increment | **Trace / log.** `X-ERPC-Force-Trace: true` (or `force-trace` query param) forces OTel sampler to record the span regardless of sampling rate (attribute `erpc.force_trace = true`). Source: [`common/tracing_util.go:89-100`](https://github.com/erpc/erpc/blob/main/common/tracing_util.go#L89-L100). `Request.Lock` / `Request.RLock` detail spans are emitted inside directive mutations ([`common/request.go:924-934`](https://github.com/erpc/erpc/blob/main/common/request.go#L924-L934)). At `trace` level, `applied request directives` is logged with `Interface("directives", ...)` after every `EnrichFromHttp` call. Source: [`erpc/http_server.go:659`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L659). ### Source code entry points - [`common/request.go:116-215`](https://github.com/erpc/erpc/blob/main/common/request.go#L116-L215) — `RequestDirectives` struct: all 23 directive fields + library-only fields - [`common/request.go:563-676`](https://github.com/erpc/erpc/blob/main/common/request.go#L563-L676) — `ApplyDirectiveDefaults`: idempotency guard, nil-check per `*bool` field, copy-from-config - [`common/request.go:702-893`](https://github.com/erpc/erpc/blob/main/common/request.go#L702-L893) — `EnrichFromHttp`: fast-path scan, clone-on-write, all 23 header + query parsers - [`common/config.go:2105-2175`](https://github.com/erpc/erpc/blob/main/common/config.go#L2105-L2175) — `DirectiveDefaultsConfig` struct; `skipCacheRead` custom YAML/JSON unmarshal - [`common/defaults.go:1454-1471`](https://github.com/erpc/erpc/blob/main/common/defaults.go#L1454-L1471) — `DirectiveDefaultsConfig.SetDefaults`: four `true` defaults (`enforceHighestBlock`, `enforceGetLogsBlockRange`, `enforceNonNullTaggedBlocks`, `validateTransactionsRoot`) - [`erpc/http_server.go:1081-1272`](https://github.com/erpc/erpc/blob/main/erpc/http_server.go#L1081-L1272) — `executionHeadersMode`, `setResponseHeaders`, `writeCounterHeaders`, `writeResponseMetadataHeaders`, `writeUpstreamTraceHeaders` - [`erpc/network_executor.go:179-188`](https://github.com/erpc/erpc/blob/main/erpc/network_executor.go#L179-L188) — `SkipConsensus` branch - [`erpc/network_executor.go:374-462`](https://github.com/erpc/erpc/blob/main/erpc/network_executor.go#L374-L462) — `shouldRetryWithReason`: `RetryEmpty` / `RetryPending` checks and `EmptyResultMaxAttempts` cap - [`erpc/networks.go:374-441`](https://github.com/erpc/erpc/blob/main/erpc/networks.go#L374-L441) — selector-scoped served-tip: `requestSelector`, `servedTipPartitionFor`, partition cap - [`upstream/upstream.go:1505-1548`](https://github.com/erpc/erpc/blob/main/upstream/upstream.go#L1505-L1548) — `shouldSkip`: `UseUpstream` selector check via `UpstreamMatchesSelector` - [`common/matcher.go:34-112`](https://github.com/erpc/erpc/blob/main/common/matcher.go#L34-L112) — `WildcardMatch`, `MatchesSelector`, `UpstreamMatchesSelector` - [`erpc/skip_consensus_directive_test.go`](https://github.com/erpc/erpc/blob/main/erpc/skip_consensus_directive_test.go) — end-to-end `SkipConsensus` tests - [`common/request_test.go:391-541`](https://github.com/erpc/erpc/blob/main/common/request_test.go#L391-L541) — unit tests for boolean parse rules, header/query precedence, `ValidateTransactionsRoot` override ### Related pages - [Auth](/config/auth.llms.txt) — credential headers (`X-ERPC-Secret-Token`, `Authorization`, `X-Siwe-*`) parsed separately before directives. - [Rate limiters](/config/rate-limiters.llms.txt) — caps total request volume; important when `retryEmpty`/`retryPending` loops could multiply upstream calls. - [Consensus](/config/projects/consensus.llms.txt) — the branch that `skipConsensus` bypasses. - [Selection policies](/config/projects/selection-policies.llms.txt) — controls which upstreams are eligible before `useUpstream` further restricts the set. - [Matcher syntax](/config/matcher.llms.txt) — the WildcardMatch grammar used by `useUpstream` and `skipCacheRead` patterns. - [Survive provider outages](/use-cases/survive-provider-outages.llms.txt) — a use case that combines `retryEmpty`, `enforceHighestBlock`, and `useUpstream`. --- ## Navigation (machine-readable surface) - Up: [All pages index](https://docs.erpc.cloud/llms.txt) - Root index of every page: [llms.txt](https://docs.erpc.cloud/llms.txt) · everything in one file: [llms-full.txt](https://docs.erpc.cloud/llms-full.txt) ### Sibling pages - [Admin API](https://docs.erpc.cloud/operation/admin.llms.txt) — A built-in operator control plane — inspect topology, cordon sick upstreams without restarts, and manage API keys, all over a secure JSON-RPC 2.0 endpoint. - [Batching & multiplexing](https://docs.erpc.cloud/operation/batch.llms.txt) — Send one request, get back a merged response — eRPC parallelises inbound batch arrays, re-batches calls to supporting upstreams, and collapses identical in-flight requests so each unique call hits the network exactly once. - [CLI & env vars](https://docs.erpc.cloud/operation/cli.llms.txt) — Start, validate, or inspect your eRPC config from the command line — then deploy with confidence knowing exactly what the engine will run. - [Cordoning](https://docs.erpc.cloud/operation/cordoning.llms.txt) — Pull any upstream out of routing instantly with one admin call — no metric window to wait for, no config redeploy required. - [Healthcheck](https://docs.erpc.cloud/operation/healthcheck.llms.txt) — One endpoint that tells Kubernetes exactly when your pod is ready, draining, or broken — with eight probe strategies from "any upstream alive" to live chain-ID verification. - [Monitoring & metrics](https://docs.erpc.cloud/operation/monitoring.llms.txt) — Every subsystem in eRPC — upstreams, cache, rate limits, consensus, hedging — emits Prometheus metrics. One scrape target, full visibility, zero instrumentation work. - [Production checklist](https://docs.erpc.cloud/operation/production.llms.txt) — Go live confidently — a short list of settings that separate a hardened eRPC deployment from a dev-mode one. - [Tracing & logging](https://docs.erpc.cloud/operation/tracing.llms.txt) — Every request, cache lookup, and upstream call becomes a searchable span — shipped to any OTel backend. Secrets never leave the process. - [URL structure](https://docs.erpc.cloud/operation/url.llms.txt) — One URL pattern routes every chain — domain and network aliases let you publish clean, memorable endpoints without touching your app code.