

## Proxy [#proxy]

POST a single JSON-RPC object to `/rpc/v1/{network}`. Supported networks: `finney` (mainnet) and `test` (testnet). WebSocket upgrade is not available on this HTTP path — use `wss.metagraph.sh` or a public WSS endpoint.

```
POST https://api.metagraph.sh/rpc/v1/{network}
```

* `finney` → `https://api.metagraph.sh/rpc/v1/finney`
* `test` → `https://api.metagraph.sh/rpc/v1/test`

<CodeBlockTabs defaultValue="cURL">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="TypeScript">
      TypeScript
    </CodeBlockTabsTrigger>

    <CodeBlockTabsTrigger value="Python">
      Python
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash
    curl -s 'https://api.metagraph.sh/rpc/v1/finney' \
      -X POST -H 'content-type: application/json' \
      -d '{"jsonrpc":"2.0","id":1,"method":"chain_getHeader","params":[]}'
    ```
  </CodeBlockTab>

  <CodeBlockTab value="TypeScript">
    ```ts
    import { metagraphedRpc } from "@jsonbored/metagraphed";

    const header = await metagraphedRpc("finney", { method: "chain_getHeader" });
    ```
  </CodeBlockTab>

  <CodeBlockTab value="Python">
    ```python
    from metagraphed import metagraphed_rpc

    header = metagraphed_rpc("finney", "chain_getHeader")
    ```
  </CodeBlockTab>
</CodeBlockTabs>

## Catalog & analytics [#catalog--analytics]

Static registry projections and live proxy telemetry. Live explorer UI for pools and traffic lives on [Endpoints](/endpoints).

| Method | Path                    | Summary                               | Notes                                                                                                                             |
| ------ | ----------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| POST   | `/rpc/v1/{network}`     | Read-only JSON-RPC reverse proxy      | Network segment is `finney` (mainnet) or `test` (testnet). Single JSON-RPC object only — no batches, no HTTP WebSocket upgrade.   |
| GET    | `/api/v1/rpc/pools`     | Proxy pool roster + live eligibility  | Serves `rpc/pools.json` with probe-derived health overlaid from KV so dead upstreams show as ineligible.                          |
| GET    | `/api/v1/rpc/endpoints` | Base-layer Subtensor RPC/WSS registry | Filterable catalog (kind, layer, status, provider, pool\_eligible, latency). Pair with the live table on [Endpoints](/endpoints). |
| GET    | `/api/v1/rpc/usage`     | Proxy usage analytics                 | Request volume, latency p50/p95, failover/error/cache rates, per-endpoint and per-network distribution. `?window=7d\|30d`.        |

## Allowlisted methods [#allowlisted-methods]

Only safe read methods pass. Mutating and heavy prefixes are denied. State-query methods need param validation and a separate rate budget.

<Callout title="Denied prefixes get rejected outright">
  `author_`, `state_call`, `sudo_`, `payment_`, and `contracts_` calls never reach the upstream node
  — the proxy rejects them before dispatch, not after.
</Callout>

**Safe methods**

`chain_getBlock` · `chain_getBlockHash` · `chain_getFinalizedHead` · `chain_getHeader` · `rpc_methods` · `state_getRuntimeVersion` · `system_chain` · `system_health` · `system_name` · `system_properties` · `system_version`

**State-query (extra rate budget)**

`state_getStorage` · `state_getKeysPaged`

**Denied prefixes**

`author_`\* · `state_call`\* · `sudo_`\* · `payment_`\* · `contracts_`\*

## Gated fullnode access [#gated-fullnode-access]

An account-gated tier for real fullnode RPC access — not just the read-only proxy above. Requires a wallet-signed login; no invite code, self-serve. Grants the same safe read methods as the public proxy, plus `author_submitExtrinsic` (real transaction broadcast), at a materially higher rate limit than the keyless tier. Every key starts on the default (`free`) tier; a higher tier is granted directly to your account, not by a code you present.

**1. Request a challenge**

<CodeBlockTabs defaultValue="cURL">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash
    curl -s https://api.metagraph.sh/api/v1/auth/wallet/challenge \
      -X POST -H 'content-type: application/json' \
      -d '{"ss58":"YOUR_SS58_ADDRESS"}'
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Returns a `message` to sign and how long it stays valid.

**2. Sign the message with your wallet**

Sign the returned `message` as raw bytes with your own wallet signer (e.g. `@polkadot/extension-dapp`'s `signRaw({ address, data: message, type: "bytes" })`). The signing key material never reaches this API — only the resulting signature does.

**3. Verify and get a session**

<CodeBlockTabs defaultValue="cURL">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash
    curl -s https://api.metagraph.sh/api/v1/auth/wallet/verify \
      -X POST -H 'content-type: application/json' \
      -d '{"ss58":"YOUR_SS58_ADDRESS","signature":"YOUR_HEX_SIGNATURE"}'
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Returns a `session_token` — scoped only to key management (create/list/revoke your own keys), not itself the RPC credential.

**4. Mint your key**

<CodeBlockTabs defaultValue="cURL">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash
    curl -s https://api.metagraph.sh/api/v1/keys \
      -X POST \
      -H "authorization: Bearer YOUR_SESSION_TOKEN"
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Returns the full `mg_...` key exactly once — store it yourself; it's never shown again, along with a `key_id`. Revoke anytime with `DELETE /api/v1/keys/{key_id}`, presenting the same session.

**5. Call the gated proxy**

<CodeBlockTabs defaultValue="cURL">
  <CodeBlockTabsList>
    <CodeBlockTabsTrigger value="cURL">
      cURL
    </CodeBlockTabsTrigger>
  </CodeBlockTabsList>

  <CodeBlockTab value="cURL">
    ```bash
    curl -s 'https://api.metagraph.sh/rpc/v1/fullnode' \
      -X POST -H 'content-type: application/json' \
      -H 'authorization: YOUR_KEY' \
      -d '{"jsonrpc":"2.0","id":1,"method":"chain_getHeader","params":[]}'
    ```
  </CodeBlockTab>
</CodeBlockTabs>

Send the key in the `authorization` **header**. A `?authorization=YOUR_KEY` query parameter is also accepted — matching the convention several hosted-RPC providers use, so existing WSS-shaped client code can point here unchanged — but prefer the header on HTTP: a key in a URL ends up in access logs, proxy logs, and browser history. If both are present the header wins.

| What         | Value                                                    | Notes                                                                                 |
| ------------ | -------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| Endpoint     | `POST /rpc/v1/fullnode`                                  | Isolated from the public `/rpc/v1/{network}` pool — no shared failover state.         |
| Method scope | Safe read methods + `author_submitExtrinsic`             | Every other `author_`/`sudo_`/`payment_`/`contracts_`/`state_call` call is denied.    |
| Key delivery | `authorization` header (preferred), or `?authorization=` | Header wins if both are sent. The query param stays supported for WSS-shaped clients. |

## Limits [#limits]

Hard caps on every proxied POST. Matching constants live in `workers/config.ts` and `workers/request-handlers/rpc-proxy.ts`.

| Limit                     | Value     | Notes                                                                                                          |
| ------------------------- | --------- | -------------------------------------------------------------------------------------------------------------- |
| Rate limit                | 100 / 60s | Per-client IP on `POST /rpc/v1/*` (429 + retry-after). Shared binding policy with GraphQL.                     |
| State-query rate          | 20 / 60s  | Additional budget for `state_getStorage` / `state_getKeysPaged` — does not starve ordinary chain/system reads. |
| Max POST body             | 64 KiB    | HTTP request body size cap for the read-only proxy.                                                            |
| Max state-query response  | 256 KiB   | Decoded upstream body cap for state-query methods after fetch.                                                 |
| `state_getKeysPaged` page | 250       | Caller-supplied count is clamped server-side (not rejected).                                                   |
| Failover attempts         | 3         | Per request across the health-ordered pool before surfacing upstream failure.                                  |

<ApiSources paths="[&#x22;/api/v1/rpc/pools&#x22;, &#x22;/api/v1/rpc/endpoints&#x22;, &#x22;/api/v1/rpc/usage&#x22;]" />
