Operation
URL
AI agents: fetch https://docs.erpc.cloud/operation/url.llms.txt for the complete machine-readable version of this page (full configuration schema, defaults, worked examples, and source links). Append `.llms.txt` to any docs URL for the same treatment.AIFor agents: /operation/url.llms.txt

URL structure

Every request reaches eRPC at /<project>/<architecture>/<chainId>. Two aliasing layers let you shorten or domain-bind that path without touching your app: domain aliasing maps a wildcard Host header to any combination of project, network, and chain; network aliasing lets a name like "arbitrum" stand in for evm/42161 anywhere in the path. Unknown projects or networks return clean JSON-RPC error envelopes, never silent failures.

Quick taste

Illustrative, not a tuned production config — map a dedicated domain to a single chain:

server.aliasing.rules
erpc.yaml
server:  aliasing:    rules:        # map a dedicated domain → single chain, no path segments needed      - matchDomain: "eth.myservice.com"        serveProject: "main"        serveArchitecture: "evm"        serveChain: "1"

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 up clean per-chain domains
I want each of my EVM chains to have its own dedicated subdomain (e.g. eth.myservice.com,
arb.myservice.com) so clients never need to include chain IDs in the URL path. Configure
eRPC domain aliasing rules in my eRPC config. Read the full reference first:
https://docs.erpc.cloud/operation/url.llms.txt
Prompt Example #2: add human-readable network aliases
Replace the raw evm/42161 and evm/8453 path segments in my eRPC setup with friendly
aliases like "arbitrum" and "base" so my app can POST to /main/arbitrum instead of
/main/evm/42161. Update my eRPC config and tell me how alias collisions are handled.
Reference: https://docs.erpc.cloud/operation/url.llms.txt
Prompt Example #3: debug a 400 on my healthcheck probe
My load-balancer health probe is hitting GET /main/evm/1/healthcheck and getting HTTP 400
instead of 200. Explain why eRPC returns 400 for this URL shape and show me the correct
probe path given my config in my eRPC config. Reference:
https://docs.erpc.cloud/operation/url.llms.txt
Prompt Example #4: route multi-chain SDK calls without URL changes
My SDK sends all requests to the same endpoint but includes the target chain in the
JSON-RPC body as "networkId":"evm:42161". Confirm my eRPC config is set up to accept
project-level POST /main requests where the network comes from the body, and flag any
footguns in the current alias config. Reference:
https://docs.erpc.cloud/operation/url.llms.txt
URL structure — full agent referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.

How it works

Routing happens in two phases. First, the server evaluates server.aliasing.rules against the stripped Host header (port removed). The first matching rule wins and pre-populates up to all three of (projectId, architecture, chainId). Then parseUrlPath reads the remaining URL path segments and fills in any fields not yet set by domain aliasing.

After path parsing, common.IsValidArchitecture validates the architecture string. Currently only "evm" is valid; any other value — including an empty string when chainId is present — returns HTTP 400. If projectId is still empty after all resolution, the request is also rejected with 400.

For POST requests where architecture and chainId are still unknown after the path, the server inspects the JSON-RPC body for a top-level "networkId" field of the form "evm:42161". This allows project-level requests (POST /main) to self-describe their network inline without a URL change.

Any request whose HTTP method is neither POST nor OPTIONS has isHealthCheck forced to true after path parsing — meaning GET /main/evm/1 silently becomes a network-scoped healthcheck rather than an error. Architecture validation still runs first; a GET to an invalid architecture path returns 400, not 200.

Domain alias wildcard matching. matchDomain is evaluated with common.WildcardMatch, which uses Go filepath.Match semantics supporting * and ?. The Host header has its port stripped before matching. Only the first matching rule applies; remaining rules are ignored. [Source: erpc/http_server.go:L234-256]

Network alias resolution. Inside path parsing, whenever a single unresolved segment is being interpreted as architecture or chainId, the parser calls project.networksRegistry.ResolveAlias(segment). If it matches, the stored (architecture, chainId) override the raw segment — enabling POST /main/arbitrum instead of POST /main/evm/42161. Aliases are registered eagerly at startup from all statically configured networks, and lazily when a network is first bootstrapped. [Source: erpc/http_server.go:L860-866]

Global NetworkAlias resolver. A process-global atomic function pointer (common.networkAliasResolver) is installed at startup via common.SetNetworkAliasResolver(f). common.NetworkAlias(networkId) calls the installed resolver and returns the alias if found, or the raw networkId unchanged. Components without project context (e.g., the gRPC cache connector) use this for consistent metric label emission. This is distinct from the per-project URL routing alias maps. [Source: common/network_alias.go:L10-34]

Full set of accepted proxy URL shapes after alias resolution:

URL shapePre-populated by aliasSegments needed
POST /<project>/<arch>/<chain>none3 — canonical form
POST /<arch>/<chain>project2
POST /<chain>project + arch1
POST /project + arch + chain0 — single-chain dedicated domain
POST /<project>/<network-alias>none2 — alias resolved to arch+chain
POST /<network-alias>project1 — alias resolved
POST /<project>none1 — arch+chain from body networkId
POST /<project>/<arch>none2 — chainId from body networkId

Path parsing state machine. parseUrlPath has six distinct cases keyed by which fields were pre-populated by domain aliasing. Before the case switch, the path is cleaned with path.Clean (collapsing double slashes, resolving .. segments) and split on /. A trailing segment "healthcheck" or an empty root GET is always a healthcheck regardless of case.

CasePre-selectedSegment handling
1none0→healthcheck; 1→projectId; 2→project+(alias or arch); 3→project+arch+chain; 4+→400
2project0→healthcheck; 1→alias or arch; 2→arch+chain; 3→full override; 4+→400
3project + arch0→healthcheck; 1→alias override or chainId; 2→400; 3→full override; 4+→400
4all three0 or 1 (healthcheck suffix)→pass; anything else→400
5arch + chain0→healthcheck; 1→projectId; 2→400; 3→full override; 4+→400
6arch only0→healthcheck; 1→projectId; 2→project+chainId; 3→full override; 4+→400
defaultproject + chain, no archalways 400 — impossible combination

Source: [erpc/http_server.go:L852-991]

Invalid and impossible path combinations (after the case switch, post-switch checks at L993-L999 apply):

ConditionHTTP statusError message
projectId still empty (not healthcheck)400"project is required either in path or via domain aliasing"
Architecture fails IsValidArchitecture400"architecture is not valid (must be 'evm')"
projectId + chainId pre-selected but no architecture (default case)400"it is not possible to alias for project and chain WITHOUT architecture"
4+ segments with no pre-selection400"must only provide /<project>/<architecture>/<chainId>"

Healthcheck URL shapes. The following GET (or any non-POST/non-OPTIONS) URL patterns reach healthcheck handlers — alias resolution and architecture validation still apply to the path segments:

URL shapeScope
GET /Global (all projects)
GET /healthcheckGlobal (all projects)
GET /<project>/healthcheckPer-project
GET /<project>/<arch>/<chain>/healthcheckPer-network (explicit)
GET /<project>/<network-alias>/healthcheckPer-network (alias resolved)
Aliased domain GET /healthcheckScope matches what alias pre-selects
Aliased domain GET /Same as above

Note: GET /main/foo/healthcheck where foo is neither a valid alias nor "evm" returns HTTP 400, not 200, because architecture validation runs before healthcheck dispatch.

Config schema

Domain aliasingserver.aliasing.rules[] (struct: common/config.go:L253-262):

FieldTypeDefaultBehavior / footguns
matchDomainstringrequiredWildcard pattern matched against Host header after port strip. Supports * and ? (Go filepath.Match). First matching rule wins; no merging. Footgun: a "*" wildcard before specific rules captures everything. Source: common/config.go:L258
serveProjectstring""Project ID pre-populated when rule matches. If empty, projectId must appear in path. Source: common/config.go:L259
serveArchitecturestring""Architecture pre-populated (e.g. "evm"). If empty, must appear in path or be resolved from a network alias. Source: common/config.go:L260
serveChainstring""Chain ID string pre-populated (e.g. "1", "42161"). If empty, must appear in path. Footgun: setting serveChain without serveArchitecture is an impossible combination and always returns 400. Source: common/config.go:L261

Network (path) aliasingprojects[].networks[].alias (struct: common/config.go:L1995-2006):

FieldTypeDefaultBehavior / footguns
projects[].networks[].aliasstring"" (no alias)Human-readable path alias for this network (e.g. "arbitrum"). Used in URL instead of raw <arch>/<chainId>. Per-project; two projects can have the same alias string without conflict. Footgun: alias collision within a project drops the second registration silently with a WARN log — first registration wins. Also propagated to the process-global NetworkAlias resolver for metric-label consistency. Source: common/config.go:L2002, erpc/networks_registry.go:L67-79

Total config fields in scope: 5

Worked examples

1. Single-chain dedicated domain. A product endpoint where every call is Ethereum mainnet — clients POST to https://eth.myservice.com/ with no path beyond the root:

server.aliasing.rules
erpc.yaml
server:  aliasing:    rules:      - matchDomain: "eth.myservice.com"        serveProject: "main"        serveArchitecture: "evm"        serveChain: "1"

2. Multi-chain domain with architecture pre-selected. Pre-select only serveArchitecture: "evm" so clients can hit POST /main/42161 or POST /main/1 without the evm segment. Useful when all traffic is EVM but chains vary per request:

server.aliasing.rules
erpc.yaml
server:  aliasing:    rules:      - matchDomain: "rpc.myservice.com"        serveProject: "main"        serveArchitecture: "evm"

3. Human-readable network alias in path. Configure alias: "arbitrum" on the Arbitrum network so clients can POST to /main/arbitrum instead of /main/evm/42161. When two projects both define alias: "mainnet" they resolve independently — no cross-project conflict:

projects[].networks[].alias
erpc.yaml
projects:  - id: main    networks:      - architecture: evm        evm:          chainId: 42161        alias: arbitrum

4. Body-level network routing (no network in URL). For services that dynamically pick chains per call, send POST /main with "networkId": "evm:42161" in the JSON-RPC body. No URL change needed across chains — useful for SDK integrations where the network travels in the request payload:

POST /main
Content-Type: application/json
 
{"jsonrpc":"2.0","method":"eth_blockNumber","networkId":"evm:42161","id":1}

Request/response behavior

  • All proxy responses use a JSON-RPC envelope regardless of HTTP status code; 404s are never plain-text.
  • HTTP status codes are derived by httpStatusCode(err):
HTTP statusError codesCondition
400ErrCodeInvalidUrlPath, ErrCodeJsonRpcRequestUnmarshal, ErrCodeInvalidRequestInvalid URL path, bad JSON, invalid architecture, impossible alias combination, corrupt gzip body
401ErrCodeAuthUnauthorized, ErrCodeEndpointUnauthorizedAuth failure at project or upstream level
404ErrCodeProjectNotFound, ErrCodeNetworkNotFound, ErrCodeNetworkNotSupportedUnknown projectId, unknown networkId, or unsupported network
429ErrCodeAuthRateLimitRuleExceeded, ErrCodeProjectRateLimitRuleExceeded, ErrCodeNetworkRateLimitRuleExceeded, ErrCodeEndpointCapacityExceededRate limiter exceeded at any level
200(all other)All other outcomes — including upstream errors, which return HTTP 200 with a JSON-RPC error body

[Source: erpc/http_server.go:L1285-1317]

  • Non-POST/non-OPTIONS methods: isHealthCheck is forced true after path parsing; the client receives a healthcheck response with no indication the substitution occurred. [erpc/http_server.go:L1001-1003]
  • Unknown project (ErrProjectNotFound): s.erpc.GetProject(projectId) returns HTTP 404 with a JSON-RPC envelope and message "project <id> is not configured". [Source: erpc/projects_registry.go:L118]
  • Networks are lazily initialized: ErrNetworkNotFound (HTTP 404) is returned only when the networkId format is valid but resolution definitively fails. A network that has never been requested may succeed on first call for provider-based projects. [Source: erpc/networks_registry.go:L221-232]
  • Batch detection fires on body[0] == '[' — no Content-Type gate, no pre-parse. A body like [invalid json is detected as batch then fails unmarshalling with a 400. [erpc/http_server.go:L405-409]
  • Request gzip decompression is unconditional: Content-Encoding: gzip always triggers decompression regardless of the server.enableGzip setting (which controls response compression only). Corrupt gzip → 400 ErrInvalidRequest immediately. [erpc/http_server.go:L349-370]
  • MaxHeaderBytes is 1 MiB on both IPv4 and IPv6 servers; requests with oversized headers are rejected at the Go net/http layer with HTTP 431 before any eRPC handler runs. Body size has no configured cap. [erpc/http_server.go:L186]
  • gRPC dispatch (Content-Type: application/grpc + HTTP/2) happens before URL parsing — only when grpcSharesHttpV4 is enabled; IPv6 never gets gRPC sharing. [erpc/http_server.go:L161-177]
  • The admin endpoint is exactly POST /admin or OPTIONS /admin; GET /admin is a healthcheck; /admin/foo fails with a URL parse error. [erpc/http_server.go:L836-838]

Best practices

  • Use serveArchitecture: "evm" without serveChain on a project-scoped domain to drop evm from every URL (POST /main/1 instead of POST /main/evm/1), saving clients a segment while preserving multi-chain flexibility.
  • Order domain alias rules most-specific first. The first matching rule wins and remaining rules are not evaluated. A catch-all matchDomain: "*" placed before specific rules silently captures every request — include it last only if intentional.
  • Never set serveChain without serveArchitecture. This combination is explicitly detected as impossible and returns 400. If you want to pre-select a chain, always pair it with serveArchitecture: "evm".
  • Assign unique aliases within a project. Alias collision silently drops the second registration (only a WARN log is emitted). Verify at startup that each project's alias values are distinct.
  • Apply body-size limits at the reverse proxy. The 1 MiB limit is headers only; bodies are read into memory without a cap. Nginx/Envoy client_max_body_size prevents memory exhaustion from oversized requests.
  • Test healthcheck paths with your alias setup. parseUrlPath runs alias and architecture validation even on healthcheck requests — a GET /main/foo/healthcheck where foo is neither a valid alias nor "evm" returns 400, not 200, which can break load-balancer probes.

Edge cases & gotchas

  1. Domain aliasing matches the Host header after port stripping. If an ingress forwards Host: api.example.com:443, the colon and port are stripped before matching. A rule matchDomain: "api.example.com" works regardless of the port in the Host header. Source: erpc/http_server.go:L234-237
  2. GET /main/evm/1 is a healthcheck, not a proxy request. Any non-POST/non-OPTIONS method forces isHealthCheck=true after path parsing. The response gives no indication this substitution occurred. This is intentional for load-balancer probes. Source: erpc/http_server.go:L1001-1003
  3. serveProject + serveChain without serveArchitecture always errors. The combination is detected as an impossible alias and returns 400 immediately. Source: erpc/http_server.go:L985-988
  4. First domain alias rule wins; no merging. Order rules from most-specific to least-specific. A matchDomain: "*" before specific rules captures everything. Source: erpc/http_server.go:L244-256
  5. Network aliases are per-project, not global. The alias map is stored per NetworksRegistry per PreparedProject. Source: erpc/networks_registry.go:L31
  6. Alias collision: first-registration wins, silently for callers. If a provider dynamically creates a network whose alias matches a statically configured one, the static alias wins. The dynamic registration is dropped with a WARN log but no error is returned. Source: erpc/networks_registry.go:L340-344
  7. Alias resolution only in specific path positions. In a fully-explicit 3-segment path (/project/evm/42161), no alias resolution is attempted — an alias named "evm" would NOT intercept the architecture position. Source: erpc/http_server.go:L860-866
  8. Body networkId requires valid JSON. If the body is not valid JSON when architecture is still empty, a JSON-RPC parse error is returned. This only applies to proxy requests; healthcheck does not read the body. Source: erpc/http_server.go:L617-622
  9. Healthcheck paths pass through architecture validation. GET /main/foo/healthcheck where foo is not a known alias and not "evm" returns HTTP 400, not 200. Source: erpc/http_server.go:L997-999
  10. path.Clean normalizes unusual paths. /main//evm/1 becomes /main/evm/1; /main/../other/evm/1 resolves to /other/evm/1. Source: erpc/http_server.go:L821
  11. Batch detection has no Content-Type gate. A body starting with [ is treated as batch even if Content-Type: text/plain or omitted entirely. A malformed batch like [invalid json is detected as batch and then fails unmarshal with a 400. Source: erpc/http_server.go:L405-414
  12. server.enableGzip controls response compression only. Request decompression is unconditional. Operators cannot disable inbound gzip decompression via config. Source: erpc/http_server.go:L349-370
  13. Body size has no configured cap. The 1 MiB limit is headers only. Arbitrarily large bodies are read into memory. Apply body-size limits at the reverse-proxy layer. Source: erpc/http_server.go:L186, erpc/http_server.go:L374
  14. Unknown network may succeed on first call for provider-based projects. Networks are lazily initialized. ErrNetworkNotFound (HTTP 404) is returned only when resolution definitively fails. Source: erpc/networks_registry.go:L221-232
  15. serveArchitecture: "evm" with no serveChain lets callers omit evm from the path. With a domain alias pre-selecting architecture, the URL becomes POST /<project>/<chainId> — no need to include evm in every call. Source: erpc/http_server.go:L963-983
  16. gRPC sharing is IPv4-only. The Content-Type: application/grpc + HTTP/2 dispatch happens before URL parsing only on IPv4. IPv6 never gets a shared gRPC handler. Source: erpc/http_server.go:L161-177

Observability

MetricTypeLabelsWhen it fires
erpc_cors_requests_totalcounterproject, originEvery request carrying an Origin header
erpc_cors_preflight_requests_totalcounterproject, originOPTIONS preflight from an allowed origin
erpc_cors_disallowed_origin_totalcounterproject, originRequest from an origin not in the allowlist

Tracing. All requests share an Http.ReceivedRequest OTel span with http.method, http.url, http.scheme, http.user_agent attributes. After routing, common.SetForceTraceNetwork(ctx, architecture+":"+chainId) injects the network into the span context. [Source: erpc/http_server.go:L285-288]

Log messages.

  • "received http request" — INFO with body JSON field for non-empty bodies
  • "received http request with empty body" — INFO for empty-body requests
  • "failed to match aliasing rule" — ERROR if WildcardMatch returns an error for a domain alias rule
  • "registered network alias" — DEBUG on alias registration
  • "skipping duplicate alias registration with different target" — WARN on alias collision

Source code entry points

Related pages

  • Auth — auth failure produces the 401 this page's error table maps.
  • Rate limiters — rate limit exceeded produces the 429 this page's error table maps.
  • Projects — where networks[].alias is configured.
  • Deployment — reverse-proxy body-size limits recommended in best practices.