{"components":{"responses":{"NotFound":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"No such resource"},"RateLimited":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Too many requests. The public API allows 120 requests per minute per IP."}},"schemas":{"BSCStaking":{"description":"Per-validator staking returns measured from each StakeCredit contract's own daily ledger, with the share price behind them.","type":"object"},"Chain":{"description":"One chain.","type":"object"},"ChainList":{"description":"Chains being measured.","type":"object"},"ChainStatus":{"description":"One chain's current state, its providers as components with a daily uptime history each, attainment against a published objective and its error budget, and the recent incidents. The objective is ours, not any provider's.","type":"object"},"Decentralisation":{"description":"Stake concentration per chain, each naming the unit it counts.","type":"object"},"DepthLadder":{"description":"Per-endpoint depth reach, with every rung and the provider's own words where it declined.","type":"object"},"Error":{"properties":{"error":{"properties":{"code":{"type":"string"},"message":{"type":"string"},"request_id":{"type":"string"}},"type":"object"}},"type":"object"},"EthereumStaking":{"description":"Consensus-layer issuance, the active set behind it, and the entry and exit queues. No per-validator rate: the chain reports none.","type":"object"},"EventList":{"description":"Classified validator events, newest first, each carrying the raw transition it was derived from.","type":"object"},"Freshness":{"description":"Head lag in the chain's own unit. Carries `window`, `generated_at` and a sample count. An aggregate without them cannot be judged.","type":"object"},"GeoJSON":{"description":"A FeatureCollection, one feature per located validator.","type":"object"},"IncidentList":{"description":"Provider outages, degradations and throttling, with a per-provider summary and the thresholds that decided what counted.","type":"object"},"MethodCatalog":{"description":"Reference entries for every documented method.","type":"object"},"MethodDetail":{"description":"One method: its reference entry, per-provider history, and degradation timeline. Carries `window`, `generated_at` and a sample count. An aggregate without them cannot be judged.","type":"object"},"MethodList":{"description":"Methods with a benchmark scenario.","type":"object"},"MethodMatrix":{"description":"Which providers answer which methods. Carries `window`, `generated_at` and a sample count. An aggregate without them cannot be judged.","type":"object"},"Methodology":{"description":"Scoring weights and thresholds currently in force.","type":"object"},"Propagation":{"description":"First-seen cohorts. Cohorts are not comparable with each other.","type":"object"},"ProviderDetail":{"description":"One provider, its endpoints and its scores. Carries `window`, `generated_at` and a sample count. An aggregate without them cannot be judged.","type":"object"},"ProviderList":{"description":"Providers measured on at least one chain, with the chains and transports each is measured on.","type":"object"},"Ranking":{"description":"Provider ranking. Carries `window`, `generated_at` and a sample count. An aggregate without them cannot be judged.","type":"object"},"RegionBreakdown":{"description":"The same measurements grouped by the region they were taken from.","type":"object"},"RegionList":{"description":"Regions measurements are taken from, with what each has collected.","type":"object"},"RegressionList":{"description":"Per-method latency changes against each provider's own baseline, largest movement first, improvements included.","type":"object"},"SoftwareCensus":{"description":"What each measured endpoint reports itself to be running, the raw banner beside every field parsed from it, and how many endpoints name a client at all. Self-reported: the claim is the endpoint's, not ours.","type":"object"},"SolanaLeaders":{"description":"The observed chain position, the projected one, and the leaders scheduled around it.","type":"object"},"SolanaStaking":{"description":"Network staking economics and a per-validator leaderboard. Every rate carries whether it was measured from a payment or derived from the cohort's own measured gross rate.","type":"object"},"SolanaValidatorSchedule":{"description":"One identity's next slot assignments this epoch.","type":"object"},"SolanaValidatorStaking":{"description":"One validator's completed epochs: what it was paid, at what commission, against how much stake.","type":"object"},"Suggestion":{"description":"Where to measure from for a given location: the nearest vantage point, how far it is, and how much that distance is worth. Pass its `measured_from` to /rankings as `region` for the figures themselves.","type":"object"},"Summary":{"description":"Platform-wide totals.","type":"object"},"Timeseries":{"description":"Bucketed latency percentiles. Carries `window`, `generated_at` and a sample count. An aggregate without them cannot be judged.","type":"object"},"Validator":{"description":"One validator with chain-specific detail.","type":"object"},"ValidatorHistory":{"description":"Recorded changes, written change-only.","type":"object"},"ValidatorList":{"description":"A page of validators, filtered and ordered by the request, with the facet counts a filter menu needs.","type":"object"},"ValidatorSummary":{"description":"Validator counts per chain.","type":"object"},"WithdrawalAddress":{"description":"Every validator withdrawing to one execution address, with the group's totals and its share of the validator set.","type":"object"}}},"info":{"description":"Every figure here comes from a measurement taken by a probe agent against a real endpoint, or from a record read from a public chain. Nothing is estimated, and an unknown is returned as null rather than as a plausible value.\n\nTwo rules apply throughout:\n\n1. **Every aggregate carries its provenance** — the window it covers, how many samples back it, and when it was generated. A number without those cannot be judged.\n2. **Nothing that is not comparable is combined.** Head lag is in each chain's own blocks or slots, so it is never averaged across chains. Propagation deltas are grouped by chain, transport and subscription, because two subscriptions on one chain can fire on different events.\n\n**Live feed.** `GET /api/v1/live` is a WebSocket carrying each measurement as it is stored. It is not described below because OpenAPI 3.1 documents request/response pairs and a socket has neither. On connect it sends one `hello` frame with the current totals and observation count; then an `obs` frame per collector flush, each carrying up to 40 completed measurements with their chain, provider, method, region, outcome and total time; and a `count` frame every 60 seconds re-stating the authoritative count. Every event describes a call that has already finished — nothing on the feed describes a call that has not happened yet.","license":{"name":"See rpcnode.io/methodology"},"summary":"Continuously measured performance, correctness and availability of public blockchain RPC endpoints.","title":"RPCNode.IO public API","version":"v0.1.4"},"openapi":"3.1.0","paths":{"/api/v1/chains":{"get":{"description":"Chains being measured, with their block time and the number of providers and endpoints configured.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChainList"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"List chains","tags":["Reference"]}},"/api/v1/chains/{slug}":{"get":{"description":"One chain with its current provider ranking summary.","parameters":[{"description":"Chain slug: ethereum, bsc or solana.","in":"path","name":"slug","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Chain"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Get one chain","tags":["Reference"]}},"/api/v1/decentralisation":{"get":{"description":"The Nakamoto coefficient, the Herfindahl index and the top-N shares for every chain.\n\nEach figure names the unit it counts, and the units differ because the chains do. On Solana and BNB Smart Chain a validator is an operator. On Ethereum it is not — a validator is 32 ETH and one pool runs hundreds of thousands of them, so counting validators would report Ethereum as the most decentralised chain here by a factor of ten thousand, when it is the one where a single party controls the largest share. Ethereum is therefore grouped by withdrawal address.\n\nNothing here is comparable across chains without reading the unit.","parameters":[{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Decentralisation"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"How concentrated each chain's stake is","tags":["Validators"]}},"/api/v1/depth":{"get":{"description":"The deepest point in history each endpoint will answer about, in the chain's own unit — blocks on an EVM chain, slots on Solana.\n\nA capability rather than a performance measurement. Every other scenario on this platform is head-adjacent, which measures the easy case: every provider serves the head competently, and they differ at depth. An archive node and a pruned one look identical at the tip and nothing alike a million blocks back.\n\nThree kinds are asked separately, because providers differ on each: a **block** header, a **state** read at that height, and a **log** range. A node can keep every header and no historical state; a free tier can serve state and cap log ranges.\n\nFixed rungs — 1k, 10k, 100k, 1M, 5M — rather than a binary search for the exact boundary. The exact boundary moves with the head every block and means nothing to a reader; an order of magnitude is the answer, and fixed rungs make two providers directly comparable.\n\nNothing is claimed from an ambiguous refusal. Rate limiting, and a bare 401 or 403, are recorded as not measured: they can mean the provider does not serve that depth or that we asked too often, and those are not distinguishable from here.","parameters":[{"description":"Restrict to one chain.","in":"query","name":"chain","required":false,"schema":{"type":"string"}},{"description":"Restrict to one provider.","in":"query","name":"provider","required":false,"schema":{"type":"string"}},{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DepthLadder"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"How far back each endpoint serves","tags":["Measurements"]}},"/api/v1/events":{"get":{"description":"Slashings, jailings, delinquencies, exits, activations and commission changes, newest first.\n\nDerived from the validator change log rather than stored: the indexer writes a row whenever a validator's status, stake or commission moves, and every event here is a pair of consecutive rows. The feed therefore cannot drift from the history it describes.\n\nTwo things are deliberately not events. A **first sighting** is not one — every validator would produce one on the day indexing began, burying the real events under the whole set. **Stake** is not one either: Solana restakes rewards automatically every epoch, so it changes constantly by construction, and 700 rows per epoch saying \"the chain worked\" is not news. A commission is reported only where the chain gave a value both before and after — learning a figure is not the operator changing it.","parameters":[{"description":"Restrict to one chain.","in":"query","name":"chain","required":false,"schema":{"type":"string"}},{"description":"Restrict to one kind: slashed, jailed, delinquent, exited, deactivated, recovered, activated, status_changed, commission_raised or commission_cut.","in":"query","name":"kind","required":false,"schema":{"type":"string"}},{"description":"Restrict to critical, warning or info.","in":"query","name":"severity","required":false,"schema":{"type":"string"}},{"description":"How far back to look. Defaults to 7.","in":"query","name":"days","schema":{"default":7,"maximum":90,"type":"integer"}},{"in":"query","name":"limit","schema":{"default":100,"maximum":500,"type":"integer"}},{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventList"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"What happened to the validator sets","tags":["Validators"]}},"/api/v1/freshness":{"get":{"description":"How far behind the cohort head each provider's reported head sits, in that chain's own blocks or slots. A chain is required: blocks and slots are different quantities on scales that differ by 27x, so a cross-chain lag table would be arithmetic on incomparable units.","parameters":[{"description":"Chain slug. Required.","in":"query","name":"chain","required":true,"schema":{"type":"string"}},{"description":"Measurement window. Longer windows read coarser rollups, so a 30-day percentile is a genuine merge of daily t-digest states rather than an average of averages.","in":"query","name":"window","schema":{"default":"24h","enum":["5m","15m","1h","6h","24h","7d","30d"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Freshness"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Head lag by provider","tags":["Measurements"]}},"/api/v1/incidents":{"get":{"description":"Outages, degradations and sustained throttling, newest first.\n\nDerived from per-minute measurement aggregates rather than stored, so the feed cannot describe something the observations do not, and a change to what counts as an incident applies retroactively rather than only going forward.\n\nThree exclusions are deliberate. **Our own failures** — an agent that could not reach the network, our own software failing — are our outage, not the provider's, and are removed before anything is judged. **Rate limiting** is reported as its own kind rather than as failure: a provider enforcing its published limits is behaving correctly, though a sustained refusal is still a fact a caller needs. **A single bad minute** is a blip, not an incident.\n\nDurations are floors. Only minutes with evidence are counted, and a gap longer than the tolerance ends an incident rather than being spanned — so an incident is never reported as longer than what was measured inside it.","parameters":[{"description":"Restrict to one provider.","in":"query","name":"provider","required":false,"schema":{"type":"string"}},{"description":"Restrict to one chain.","in":"query","name":"chain","required":false,"schema":{"type":"string"}},{"description":"Restrict to outage, degraded or throttled.","in":"query","name":"kind","required":false,"schema":{"type":"string"}},{"description":"How far back to look. Defaults to 7.","in":"query","name":"days","schema":{"default":7,"maximum":90,"type":"integer"}},{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IncidentList"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Provider incidents","tags":["Measurements"]}},"/api/v1/map":{"get":{"description":"Two kinds of feature, and the difference is the whole point.\n\nA **validator** feature is keyed by validator identity, and exists only where the chain publishes an address per validator — of the three measured here, only Solana does. One feature per validator rather than per address: a Solana validator advertises gossip, TPU, TVU and repair addresses that are almost always the same host.\n\nA **node** feature is a member of the chain's peer-to-peer network, met by walking that network's own discovery protocol and attributed to the chain by the fork identifier in its own record. It says where the network is hosted and nothing about who operates it or what it validates. `summary.crawls` reports what the last walk met and how much of it could be attributed, because a discovery table is always partially explored and a node count without that context reads as the size of the network.\n\nCoordinates are the approximate location of an observed address, never a claim about an operator. Unlocated stays unlocated; no country centroid is substituted.","parameters":[{"description":"Restrict to one chain.","in":"query","name":"chain","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GeoJSON"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Network locations as GeoJSON","tags":["Validators"]}},"/api/v1/methodology":{"get":{"description":"The scoring weights, thresholds and sample floor currently in force, so a published score can be checked rather than taken on trust.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Methodology"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Methodology","tags":["Meta"]}},"/api/v1/methods":{"get":{"description":"RPC methods with a benchmark scenario defined.","parameters":[{"description":"Filter to one chain.","in":"query","name":"chain","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MethodList"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"List methods","tags":["Reference"]}},"/api/v1/methods/catalog":{"get":{"description":"Every documented method with what it does, why it is measured, and the caveats that actually bite. Written against the primary specifications rather than from memory.","parameters":[{"description":"Restrict to `evm` or `solana`.","in":"query","name":"family","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MethodCatalog"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Method reference","tags":["Reference"]}},"/api/v1/methods/matrix":{"get":{"description":"Which providers answer which methods. An unsupported method is a capability fact, not a failure, and is excluded from availability scoring. A method that was called and failed every time is reported as failing rather than as not measured.","parameters":[{"description":"Chain slug.","in":"query","name":"chain","required":false,"schema":{"type":"string"}},{"description":"Measurement window. Longer windows read coarser rollups, so a 30-day percentile is a genuine merge of daily t-digest states rather than an average of averages.","in":"query","name":"window","schema":{"default":"24h","enum":["5m","15m","1h","6h","24h","7d","30d"],"type":"string"}},{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MethodMatrix"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Method support matrix","tags":["Measurements"]}},"/api/v1/methods/{chain}/{method}":{"get":{"description":"The reference entry for a method together with how every provider handles it over time, including a degradation timeline. Degradation is measured against a provider's own median for the method over the window, not against the cohort — a provider that is consistently slower than its peers is not degraded, it is slower, and the ranking already says so.","parameters":[{"description":"Chain slug.","in":"path","name":"chain","required":true,"schema":{"type":"string"}},{"description":"Method name, e.g. `eth_getLogs`.","in":"path","name":"method","required":true,"schema":{"type":"string"}},{"description":"Restrict to one region.","in":"query","name":"region","required":false,"schema":{"type":"string"}},{"description":"Measurement window. Longer windows read coarser rollups, so a 30-day percentile is a genuine merge of daily t-digest states rather than an average of averages.","in":"query","name":"window","schema":{"default":"24h","enum":["5m","15m","1h","6h","24h","7d","30d"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MethodDetail"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"One method in detail","tags":["Measurements"]}},"/api/v1/propagation":{"get":{"description":"How far behind the earliest announcement each provider was, per block or slot. Returned as a list of cohorts — chain, transport and subscription together — which are NOT comparable with each other. Absolute subscribe-to-first-notification time and win counts are deliberately not published; the response says why.","parameters":[{"description":"Restrict to one chain.","in":"query","name":"chain","required":false,"schema":{"type":"string"}},{"description":"ws_jsonrpc or grpc.","in":"query","name":"transport","required":false,"schema":{"type":"string"}},{"description":"Restrict to one region.","in":"query","name":"region","required":false,"schema":{"type":"string"}},{"description":"Measurement window. Longer windows read coarser rollups, so a 30-day percentile is a genuine merge of daily t-digest states rather than an average of averages.","in":"query","name":"window","schema":{"default":"24h","enum":["5m","15m","1h","6h","24h","7d","30d"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Propagation"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"First-seen block and slot propagation","tags":["Measurements"]}},"/api/v1/providers":{"get":{"description":"Providers measured on at least one chain, with the chains and transports each is measured on. `serves` maps a chain slug to the transports held for it, because a provider can serve Yellowstone gRPC on Solana and HTTP JSON-RPC elsewhere. It is a directory fact: the endpoint exists and is measured, not that it answered. Providers marked private are measured but never named here.","parameters":[{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderList"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"List providers","tags":["Reference"]}},"/api/v1/providers/{slug}":{"get":{"description":"One provider's endpoints, per-chain scores and measured latency. Endpoint URLs are sanitised: a URL that carries a credential is shown host-only.","parameters":[{"description":"Provider slug.","in":"path","name":"slug","required":true,"schema":{"type":"string"}},{"description":"Measurement window. Longer windows read coarser rollups, so a 30-day percentile is a genuine merge of daily t-digest states rather than an average of averages.","in":"query","name":"window","schema":{"default":"24h","enum":["5m","15m","1h","6h","24h","7d","30d"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProviderDetail"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Get one provider","tags":["Reference"]}},"/api/v1/rankings":{"get":{"description":"Composite score per provider: availability, latency, correctness and freshness, weighted by the values in settings and normalised against the cohort. Below the minimum sample count a provider is reported as having insufficient data rather than given a good score.","parameters":[{"description":"Chain slug.","in":"query","name":"chain","required":false,"schema":{"type":"string"}},{"description":"Restrict to one method.","in":"query","name":"method","required":false,"schema":{"type":"string"}},{"description":"Restrict to one region.","in":"query","name":"region","required":false,"schema":{"type":"string"}},{"description":"Measurement window. Longer windows read coarser rollups, so a 30-day percentile is a genuine merge of daily t-digest states rather than an average of averages.","in":"query","name":"window","schema":{"default":"24h","enum":["5m","15m","1h","6h","24h","7d","30d"],"type":"string"}},{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ranking"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Rank providers","tags":["Measurements"]}},"/api/v1/regions":{"get":{"description":"Vantage points. Latency is a property of a network path, so where a measurement was taken is part of the measurement. A region that collected nothing in the window is attributed nothing.","parameters":[{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegionList"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"List regions","tags":["Reference"]}},"/api/v1/regions/breakdown":{"get":{"description":"The same measurements broken out by where they were taken. Latency is a property of a network path, so a single global ranking answers \"fastest from where the measurement was taken\" while appearing to answer \"fastest\"; this makes that visible.\n\nPercentiles cover successful responses only. A request that timed out is not a latency sample — it is counted against availability instead.","parameters":[{"description":"Restrict to one chain.","in":"query","name":"chain","required":false,"schema":{"type":"string"}},{"description":"Restrict to one provider.","in":"query","name":"provider","required":false,"schema":{"type":"string"}},{"description":"Restrict to one method.","in":"query","name":"method","required":false,"schema":{"type":"string"}},{"description":"Measurement window. Longer windows read coarser rollups, so a 30-day percentile is a genuine merge of daily t-digest states rather than an average of averages.","in":"query","name":"window","schema":{"default":"24h","enum":["5m","15m","1h","6h","24h","7d","30d"],"type":"string"}},{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegionBreakdown"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Results by region","tags":["Measurements"]}},"/api/v1/regressions":{"get":{"description":"Each provider's 95th percentile over a recent window against its own over the days before it, per method.\n\nCompared against itself rather than against the cohort. The ranking already says who is slower than whom, and a provider that has always been the slowest of nine is not regressing — it is slow, which is a different sentence. This is the principle of ADR 005 applied to time instead of to peers.\n\nA ratio rather than a difference: twenty milliseconds is a catastrophe on a call that normally takes five and noise on one that takes eight hundred. Both spans are merged t-digest states built the same way, which is what makes them divisible by each other at all.\n\nImprovements are reported alongside deteriorations, because the same measurement catches both and publishing only one would make this an accusation service.","parameters":[{"description":"Restrict to one chain.","in":"query","name":"chain","required":false,"schema":{"type":"string"}},{"description":"Restrict to one provider.","in":"query","name":"provider","required":false,"schema":{"type":"string"}},{"description":"Length of the recent window. Defaults to 24.","in":"query","name":"recent_hours","schema":{"default":24,"type":"integer"}},{"description":"How far back the baseline reaches. Defaults to 7.","in":"query","name":"baseline_days","schema":{"default":7,"maximum":30,"type":"integer"}},{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegressionList"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"What got slower","tags":["Measurements"]}},"/api/v1/software":{"get":{"description":"The client software behind every measured endpoint, as the endpoint reports it.\n\n`web3_clientVersion` and `getVersion` are already measured on every cycle for their own sake, and their answers used to be validated and thrown away. Keeping them makes a census of the public RPC estate a by-product of work already being done rather than new load on anybody's servers.\n\n**Self-reported, and unverifiable from outside.** A gateway can return any string. A load balancer in front of forty nodes answers for whichever one took the request. An operator can leave the banner at a default that stopped being true two upgrades ago. Every row therefore carries `identity`, the raw string, alongside the fields parsed out of it, so any derivation can be checked against what was actually said.\n\nAn endpoint naming its own product rather than a client — `kind: \"product\"` — is answering, not evading: it is saying the node behind it is an implementation detail, which for a load-balanced gateway is the truthful answer.\n\n`feature_set` is Solana's and null elsewhere. Two nodes on one version with different feature sets have activated different rules, which the version string alone would hide.","parameters":[{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SoftwareCensus"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"What each endpoint says it runs","tags":["Measurements"]}},"/api/v1/solana/leaders":{"get":{"description":"Who is producing Solana blocks, and who is scheduled next.\n\nSolana is the only chain here that publishes its leader schedule: all 432,000 slots of an epoch are assigned before the epoch begins. Ethereum publishes proposer duties only a short way ahead and BNB Smart Chain's rotation is derived rather than published, so this route is Solana-specific rather than chain-agnostic and mostly empty.\n\n**Two different kinds of number are returned, and they are labelled.** The schedule and `observed_slot` are measured — the slot is sampled from `getEpochInfo` every 30 seconds and `observed_at` says when. `projected_slot` is arithmetic: elapsed time divided by the target slot time, with `projected_slots` giving the size of the leap. Solana does not produce slots on a metronome, so a consumer that wants only measurement should read `observed_slot` and ignore the projection.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SolanaLeaders"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Solana block production, now and next","tags":["Validators"]}},"/api/v1/solana/leaders/{identity}":{"get":{"description":"When a Solana identity next leads, and how many slots it holds this epoch.\n\nReturns an empty list with an explanatory note for an identity that is not in the current schedule: a validator with no active stake produces no blocks, which is a fact about the epoch rather than a missing record.","parameters":[{"description":"Solana identity pubkey, base58.","in":"path","name":"identity","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SolanaValidatorSchedule"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"One validator's upcoming slots","tags":["Validators"]}},"/api/v1/staking/bsc":{"get":{"description":"What delegating to each BNB Smart Chain validator actually paid.\n\nEvery rate here is measured, with none of the fallbacks Solana needs. The difference is where the payment is written down: Solana reports what it paid the validator, so the delegators' share has to be recovered by dividing the commission back out, while each validator's StakeCredit contract records what reached the pool — which is what a delegator's stake grows by, commission already taken.\n\nThere is no protocol rate to check it against. BNB Smart Chain issues nothing for staking: validators are paid out of transaction fees, so the yield follows how busy the chain is rather than an emission schedule.\n\nThe window is seven days. A validator only earns while it is producing and the elected set of 45 rotates, so daily returns vary by roughly 15% around the mean — one day is a sample and a week is a rate.","parameters":[{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BSCStaking"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"BNB Smart Chain staking yields","tags":["Validators"]}},"/api/v1/staking/ethereum":{"get":{"description":"The rate the consensus layer pays for staked ETH, and the queues to get in and out.\n\nThere is no validator leaderboard here, and its absence is the point. Ethereum reports no per-validator reward: earnings arrive as balance changes, and a per-validator figure would have to be modelled. A modelled number among measured ones is worse than none.\n\nWhat is exact is the network rate. Consensus issuance is a closed form in the total active balance — `BASE_REWARD_FACTOR × epochs_per_year ÷ √balance` — so every input is observed and nothing is estimated. The square root is the design: the rate falls as more ETH is staked, without anything changing about any validator.\n\nIt excludes execution-layer tips and MEV, which are paid outside the protocol's accounting and are frequently the larger half. They are named rather than estimated.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EthereumStaking"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Ethereum consensus-layer issuance","tags":["Validators"]}},"/api/v1/staking/solana":{"get":{"description":"What delegating to each Solana validator actually paid, and the network economics behind it.\n\nEvery rate says how it was arrived at. A **measured** rate is recovered from the payment the chain made to the validator's vote account, by inverting the commission that was in force when it was made — the delegators' own rewards are paid into individual stake accounts and are not reported anywhere.\n\nA **derived** rate is the cohort's measured gross rate with this validator's commission and block production applied. It is used where no payment can be inverted, which is more common than it sounds: a validator charging zero commission is paid nothing, and 257 of 684 charge zero.\n\nThe network block carries both the cohort's measured gross rate and the protocol derivation from inflation and supply. They are computed from inputs that share nothing, and they agree — 5.3873% against 5.3784% when measured — which is what makes either publishable.","parameters":[{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SolanaStaking"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Solana staking yields","tags":["Validators"]}},"/api/v1/staking/solana/{identity}":{"get":{"description":"Every completed epoch this validator was paid for, with the commission in force and the stake behind it. The reward is the payment to the vote account, which is the validator's commission rather than its delegators' rewards.","parameters":[{"description":"Validator identity pubkey.","in":"path","name":"identity","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SolanaValidatorStaking"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"One validator's staking record","tags":["Validators"]}},"/api/v1/status/{chain}":{"get":{"description":"One chain's providers as a status page reads them: a current state per provider, a daily uptime history, attainment against a published objective, and the recent incidents.\n\n**This is an SLO, never an SLA.** An SLA is a contract a provider entered into, with a remedy when it is broken. Almost none of the endpoints measured here carry one, so the objective here is a bar we publish and apply identically to everyone; missing it is a fact about that bar as much as about the provider. What a provider does commit to, where it commits to anything, is quoted on its own resource with a source and a date.\n\n**Attainment is a ladder, not a pass mark.** A single threshold says little: set at 99.9% almost every free endpoint fails, set at 99% almost everything passes. Each provider is reported at the highest rung it actually cleared, and `attainment_known` is false — rather than the tier being empty — when too few requests were eligible to say. Not knowing and failing are different findings.\n\n**The chain-wide state is the worst component, not the average.** Nine healthy providers and one in outage average to something that reads fine, while the reader who depends on the tenth is down.\n\n**The error budget is counted in requests.** A budget in minutes would imply continuous observation; this platform samples. It is scaled to the share of the window actually observed, so a provider three days into a ninety-day window is judged against three days of allowance rather than ninety.\n\nA day nothing was measured carries state `unmeasured` and is excluded from every uptime figure rather than counted as a perfect day.","parameters":[{"description":"Chain slug, for example `ethereum`.","in":"path","name":"chain","required":true,"schema":{"type":"string"}},{"description":"How many days of history to return. Defaults to 90.","in":"query","name":"days","schema":{"default":90,"maximum":365,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChainStatus"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Is it working right now","tags":["Measurements"]}},"/api/v1/suggest":{"get":{"description":"Ranks providers for a place, measured from the nearest vantage point we operate.\n\nThis does not predict your latency, and it is not measured from your host. It names the nearest region we have a probe in, reports how far away that is, and returns what that probe actually measured. `confidence` is derived from that distance alone: `high` within 500 km, `moderate` to 2,000 km, `low` beyond it. Read a `low` result as a statement about our coverage rather than about your location.\n\nPass `ip` for the host that will make the calls — usually a server, not the browser asking. Pass `region` to skip geolocation entirely. With neither, the caller's own address is used, which is a convenience for pre-filling a form and is frequently the wrong host.\n\nNo ranking is returned here, on purpose. This answers the caller-specific half; the ranking for a region is identical for everybody who asks about that region, so it stays on `/rankings`, where it is cached once and shared. Take `measured_from` from this response and pass it as `region` there.\n\nGeolocation is a local database lookup. No third party is contacted, and the address is neither logged nor stored. Responses are `no-store`, because the answer is computed for one caller.","parameters":[{"description":"Public IP of the host that will use the endpoint. Private and loopback addresses are reported as unplaceable rather than guessed.","in":"query","name":"ip","required":false,"schema":{"type":"string"}},{"description":"Region code to measure from, instead of geolocating an address.","in":"query","name":"region","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Suggestion"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Suggest endpoints for a location","tags":["Measurements"]}},"/api/v1/summary":{"get":{"description":"Headline counts across the platform: observations, providers, endpoints and regions.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Summary"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Platform totals","tags":["Measurements"]}},"/api/v1/timeseries":{"get":{"description":"Bucketed latency percentiles. The bucket size follows the window so a query reads a few hundred buckets whatever the range.","parameters":[{"description":"Chain slug.","in":"query","name":"chain","required":false,"schema":{"type":"string"}},{"description":"Provider slug.","in":"query","name":"provider","required":false,"schema":{"type":"string"}},{"description":"Method name.","in":"query","name":"method","required":false,"schema":{"type":"string"}},{"description":"Region code.","in":"query","name":"region","required":false,"schema":{"type":"string"}},{"description":"Measurement window. Longer windows read coarser rollups, so a 30-day percentile is a genuine merge of daily t-digest states rather than an average of averages.","in":"query","name":"window","schema":{"default":"24h","enum":["5m","15m","1h","6h","24h","7d","30d"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Timeseries"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Latency over time","tags":["Measurements"]}},"/api/v1/validators":{"get":{"description":"One chain's validator set, ordered by stake. Paged with a hard ceiling — Ethereum has 897,000 validators, and an unbounded query would ask the database to serialise a gigabyte of JSON.","parameters":[{"description":"Chain slug. Required.","in":"query","name":"chain","required":true,"schema":{"type":"string"}},{"in":"query","name":"limit","schema":{"default":50,"maximum":200,"type":"integer"}},{"in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"Search term. Matches the start of the validator's own identifier or display name, and an exact Ethereum withdrawal address. A prefix rather than a substring: a substring match cannot use an index, and this table holds 898,000 rows.","in":"query","name":"q","required":false,"schema":{"type":"string"}},{"description":"Exact status: active, inactive, jailed, slashed, exited, pending, delinquent or unknown.","in":"query","name":"status","required":false,"schema":{"type":"string"}},{"description":"Exact client version, as reported by the node.","in":"query","name":"client","required":false,"schema":{"type":"string"}},{"description":"Sort column: stake (default), name, status, commission or client.","in":"query","name":"sort","required":false,"schema":{"type":"string"}},{"description":"Sort direction: asc or desc.","in":"query","name":"dir","required":false,"schema":{"type":"string"}},{"description":"Response format. `csv` returns the rows of this response as a file, flattening nested objects into dotted column names. The CSV is produced from the same query as the JSON, so the two cannot disagree — but it carries only the rows, not the context around them: the window, the note about what was measured and the facet counts are in the JSON alone.","in":"query","name":"format","schema":{"default":"json","enum":["json","csv"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidatorList"}},"text/csv":{"schema":{"type":"string"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"List validators","tags":["Validators"]}},"/api/v1/validators/summary":{"get":{"description":"Counts per chain, plus how many observed addresses have been located.","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidatorSummary"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Validator totals","tags":["Validators"]}},"/api/v1/validators/{chain}/address/{address}":{"get":{"description":"Every validator that withdraws to one execution address, with the totals for the group. Ethereum only: a BNB Smart Chain validator registers a moniker on StakeHub and a Solana validator has an identity pubkey, so neither needs an address to be identifiable, and the route returns 404 for them.\n\nThis is what makes staking concentration visible — one address holds 31% of the set. The operator is named only where the address is a publicly documented protocol contract, with a link to that documentation; an explorer's crowd-sourced label is not enough to publish a claim about who controls stake.","parameters":[{"description":"Chain slug. Only `ethereum` is supported.","in":"path","name":"chain","required":true,"schema":{"type":"string"}},{"description":"Execution address, 0x followed by 40 hex characters.","in":"path","name":"address","required":true,"schema":{"type":"string"}},{"description":"Validators per page.","in":"query","name":"limit","schema":{"default":50,"maximum":200,"type":"integer"}},{"description":"Rows to skip.","in":"query","name":"offset","schema":{"default":0,"type":"integer"}},{"description":"Search term. Matches the start of a validator index or pubkey. Applied across the whole group, not the current page: the largest staking pools hold hundreds of thousands of validators.","in":"query","name":"q","required":false,"schema":{"type":"string"}},{"description":"Exact validator status.","in":"query","name":"status","required":false,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WithdrawalAddress"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Validators by withdrawal address","tags":["Validators"]}},"/api/v1/validators/{chain}/{id}":{"get":{"description":"A validator with its chain-specific detail and the network addresses it was observed advertising.","parameters":[{"description":"Chain slug.","in":"path","name":"chain","required":true,"schema":{"type":"string"}},{"description":"Validator index, address or vote pubkey.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Validator"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Get one validator","tags":["Validators"]}},"/api/v1/validators/{chain}/{id}/history":{"get":{"description":"Recorded changes to stake and status. Written change-only: a pass over 897,250 Ethereum validators in which nothing moved produces zero rows.","parameters":[{"description":"Chain slug.","in":"path","name":"chain","required":true,"schema":{"type":"string"}},{"description":"Validator identifier.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidatorHistory"}}},"description":"Success"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/RateLimited"}},"summary":"Validator change history","tags":["Validators"]}},"/health":{"get":{"responses":{"200":{"description":"Report"}},"summary":"Full health report","tags":["Meta"]}},"/healthz":{"get":{"description":"Returns 200 while the process is running. Does not check dependencies — a liveness probe that fails on a database blip restarts a healthy process.","responses":{"200":{"description":"Alive"}},"summary":"Liveness","tags":["Meta"]}},"/readyz":{"get":{"description":"Checks every dependency and reports each one. 503 when a critical dependency is down.","responses":{"200":{"description":"Ready"},"503":{"description":"A critical dependency is unavailable"}},"summary":"Readiness","tags":["Meta"]}},"/version":{"get":{"responses":{"200":{"description":"Version, commit and build date"}},"summary":"Build information","tags":["Meta"]}}},"servers":[{"description":"Production","url":"https://api.rpcnode.io"},{"description":"Local development","url":"http://localhost:57080"}],"tags":[{"description":"Chains, providers, methods and regions.","name":"Reference"},{"description":"Aggregated benchmark results.","name":"Measurements"},{"description":"Validator sets indexed from each chain.","name":"Validators"},{"description":"Methodology and service health.","name":"Meta"}]}
