Public API
Endpoints
Endpoints are your JSON-RPC URLs (a UUID in the URL path), the same resources as in the cabinet. An endpoint is identified by its uuid everywhere: in the path of usage / status / allowed-ips and in responses. The Public API never shows numeric ids, so endpoint objects have no id, network_id, network_environment_id, or plan_id, and a network is named by its slug. All routes require signed headers (see Authentication). Base URL: see Overview. There is no DELETE: pause an endpoint via status instead. Success responses use ok: true plus the fields below; a field without a value is left out of the JSON (it is never null).
Authentication
Every request needs X-Api-Key, X-Timestamp, and X-Signature (HMAC-SHA256); the signing scheme is described in Authentication.
Sign the request target exactly as you send it: the path and, when present, the ?query (the query string is part of the signed text). For POST/PATCH, hash the raw JSON body bytes you send; for GET use the empty body.
Endpoints belong to the billing workspace owner: a key acts on the workspace owner's endpoints and plan limits.
Errors
Failures use ok: false with error_code and error. A request that cannot be accepted (bad query value, bad JSON body, missing or unknown field) gets 400 and an errors array that names every wrong field: { field, error_code, error }. With one wrong field the top-level error_code is that field's code, with several it is common.validation_failed.
Unknown fields in a body are rejected, not ignored: a typo or a removed field (for example network_id) is reported as "… is not a field of this request".
Typical statuses: 400 validation, 401 / 403 / 429 from authentication and rate limits (see Authentication), 403 endpoint.network_not_allowed and endpoint.limit_reached (plan), 404 endpoint.not_found / endpoint.network_not_found / endpoint.environment_not_found, 503 endpoint.no_upstream, 500 endpoint.gateway_sync_failed and endpoint.host_prefix_exhausted.
Validation error example
{
"ok": false,
"error_code": "common.validation_failed",
"error": "Validation failed",
"errors": [
{ "field": "name", "error_code": "endpoint.name_required", "error": "Name is required." },
{ "field": "env_slug", "error_code": "endpoint.env_slug_required", "error": "env_slug is required (for example mainnet)." }
]
}GET /v1/endpoints
List the JSON-RPC endpoints of the account (paginated).
Paramètres de requête
| Nom | Type | Description |
|---|---|---|
| page | integer | Page number, 1 to 10000 (default 1). (facultatif) |
| per_page | integer | Page size, 1 to 100 (default 30). (facultatif) |
Champs de la réponse
| Nom | Type | Description |
|---|---|---|
| endpoints | object[] | Endpoint objects of this page. |
| endpoints[].uuid | string | Endpoint identifier. Use it in the path of usage / status / allowed-ips; it is also the last segment of the JSON-RPC URL. |
| endpoints[].name | string | Display name. |
| endpoints[].status | string | active | paused. |
| endpoints[].network_name | string | Network display name. |
| endpoints[].network_slug | string | Network slug (e.g. ethereum). |
| endpoints[].env_slug | string | Environment slug (e.g. mainnet). |
| endpoints[].url | string | Public JSON-RPC URL: https://[{host_prefix}-]{network_slug}-{env_slug}.{domain}/{uuid} (the host prefix is optional). The only address the Public API returns; gRPC / WebSocket addresses are not included. |
| endpoints[].rps_limit | integer | RPS limit of your plan applied to this endpoint (0 if the account has no plan). |
| endpoints[].created_at | string | ISO-8601 timestamp with offset (UTC). |
| endpoints[].version | string | Node version of the environment. Optional: absent when unknown. (facultatif) |
| endpoints[].block_height | integer | Latest known block height of the environment. Optional: absent when unknown. (facultatif) |
| endpoints[].allowed_ips | string[] | Client IP allowlist of the endpoint. Empty array = no restriction. Always present. |
| pagination.page | integer | Current page. |
| pagination.per_page | integer | Page size. |
| pagination.total | integer | Total endpoints. |
| pagination.has_more | boolean | True if more pages exist. |
- Out-of-range or non-numeric page / per_page are rejected with 400 and errors[] naming the parameter; they are not clamped.
- The query string is part of the signed path.
Exemple de requête
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('GET', '/v1/endpoints'))La requête est signée dans l’extrait lui-même — voir Authentification pour les règles de signature.
Exemple de réponse
{
"ok": true,
"endpoints": [
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Prod Ethereum",
"status": "active",
"network_name": "Ethereum",
"network_slug": "ethereum",
"env_slug": "mainnet",
"url": "https://ethereum-mainnet.rpcnode.dev/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"rps_limit": 50,
"created_at": "2026-08-01T12:00:00Z",
"version": "v1.14.0",
"block_height": 21000000,
"allowed_ips": []
}
],
"pagination": {
"page": 1,
"per_page": 30,
"total": 1,
"has_more": false
}
}GET /v1/endpoints/available-networks
Networks (and their environment slugs) an endpoint can be created on under your plan.
Champs de la réponse
| Nom | Type | Description |
|---|---|---|
| networks[].slug | string | Network slug (value for network_slug at create). |
| networks[].name | string | Network display name. |
| networks[].environment_kinds | string[] | Environment slugs available for create (e.g. mainnet, sepolia). Value for env_slug. |
| network_limit | integer | Maximum number of networks your plan allows. Optional: absent when unlimited. (facultatif) |
| allowed_network_slugs | string[] | Plan allowlist of network slugs. Optional: absent when the plan has no allowlist. (facultatif) |
- Use networks[].slug as network_slug and one of networks[].environment_kinds as env_slug in the create call.
- Numeric network ids are not returned; the list is already filtered by your plan.
Exemple de requête
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('GET', '/v1/endpoints/available-networks'))La requête est signée dans l’extrait lui-même — voir Authentification pour les règles de signature.
Exemple de réponse
{
"ok": true,
"networks": [
{
"slug": "ethereum",
"name": "Ethereum",
"environment_kinds": ["mainnet", "sepolia"]
}
]
}POST /v1/endpoints
Create a JSON-RPC endpoint on a network environment. Counts against the endpoints_limit of your plan.
Corps de la requête
| Nom | Type | Description |
|---|---|---|
| name | string | Display name (required, max 255 characters; surrounding spaces are trimmed). |
| network_slug | string | Network slug from GET /v1/endpoints/available-networks, e.g. ethereum. |
| env_slug | string | Environment slug from networks[].environment_kinds, e.g. mainnet (case-insensitive). |
Champs de la réponse
| Nom | Type | Description |
|---|---|---|
| endpoint | object | The created endpoint. |
| endpoint.uuid | string | Endpoint identifier. Use it in the path of usage / status / allowed-ips; it is also the last segment of the JSON-RPC URL. |
| endpoint.name | string | Display name. |
| endpoint.status | string | active | paused. |
| endpoint.network_name | string | Network display name. |
| endpoint.network_slug | string | Network slug (e.g. ethereum). |
| endpoint.env_slug | string | Environment slug (e.g. mainnet). |
| endpoint.url | string | Public JSON-RPC URL: https://[{host_prefix}-]{network_slug}-{env_slug}.{domain}/{uuid} (the host prefix is optional). The only address the Public API returns; gRPC / WebSocket addresses are not included. |
| endpoint.rps_limit | integer | RPS limit of your plan applied to this endpoint (0 if the account has no plan). |
| endpoint.created_at | string | ISO-8601 timestamp with offset (UTC). |
| endpoint.version | string | Node version of the environment. Optional: absent when unknown. (facultatif) |
| endpoint.block_height | integer | Latest known block height of the environment. Optional: absent when unknown. (facultatif) |
| endpoint.allowed_ips | string[] | Client IP allowlist of the endpoint. Empty array = no restriction. Always present. |
- Example body: {"name":"Prod Ethereum","network_slug":"ethereum","env_slug":"mainnet"}
- No other fields are accepted: network_id and network_environment_id do not exist in the Public API and are rejected as unknown fields.
- Errors: endpoint.name_required / endpoint.name_too_long / endpoint.network_required / endpoint.env_slug_required (400, listed in errors[]); endpoint.network_not_found and endpoint.environment_not_found (404); endpoint.no_plan (400); endpoint.network_not_allowed (403, network is not on your plan); endpoint.limit_reached (403, endpoints_limit of the plan reached); endpoint.no_upstream (503, the environment is temporarily unavailable).
- Success is HTTP 200 (not 201) and returns the created endpoint.
Exemple de requête
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('POST', '/v1/endpoints', '{"name":"string","network_slug":"string","env_slug":"string"}'))La requête est signée dans l’extrait lui-même — voir Authentification pour les règles de signature.
Exemple de réponse
{
"ok": true,
"endpoint": {
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Prod Ethereum",
"status": "active",
"network_name": "Ethereum",
"network_slug": "ethereum",
"env_slug": "mainnet",
"url": "https://ethereum-mainnet.rpcnode.dev/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"rps_limit": 50,
"created_at": "2026-08-01T12:00:00Z",
"version": "v1.14.0",
"block_height": 21000000,
"allowed_ips": []
}
}GET /v1/endpoints/{uuid}/usage
Requests and credits of one endpoint for the last N calendar days, with top methods and webhook totals.
Paramètres de requête
| Nom | Type | Description |
|---|---|---|
| days | integer | Window length: 7, 30 or 90. Default 30. Any other value is rejected with 400 (not replaced by the default). (facultatif) |
Champs de la réponse
| Nom | Type | Description |
|---|---|---|
| usage.days | integer | Window length: 7, 30 or 90. |
| usage.total_requests | integer | Requests in the window. |
| usage.total_credits | integer | Credits in the window. |
| usage.series[] | object[] | One point per calendar day (UTC), oldest first, exactly `days` points; days without usage are zeros. |
| usage.series[].date | string | YYYY-MM-DD. |
| usage.series[].requests | integer | Requests that day. |
| usage.series[].credits | integer | Credits that day. |
| usage.methods[] | object[] | Top 20 methods of the window, sorted by credits (then requests) descending. Webhook deliveries appear here as webhook:* rows (e.g. webhook:block). |
| usage.methods[].method | string | RPC method name (or webhook:* kind). |
| usage.methods[].requests | integer | Requests for that method. |
| usage.methods[].credits | integer | Credits for that method. |
| usage.webhooks | object | Webhook usage over the whole window (not limited to the top 20 methods). All counters are 0 when there is none. |
| usage.webhooks.requests | integer | Webhook deliveries, all kinds. |
| usage.webhooks.credits | integer | Credits for webhook deliveries, all kinds. |
| usage.webhooks.address_watch_requests | integer | Address Watch webhook deliveries. |
| usage.webhooks.address_watch_credits | integer | Credits for Address Watch webhooks. |
| usage.webhooks.block_requests | integer | Block webhook deliveries. |
| usage.webhooks.block_credits | integer | Credits for block webhooks. |
| usage.webhooks.payment_requests | integer | Payment webhook deliveries. |
| usage.webhooks.payment_credits | integer | Credits for payment webhooks. |
| usage.webhooks.tx_watch_requests | integer | Tx Watch webhook deliveries. |
| usage.webhooks.tx_watch_credits | integer | Credits for Tx Watch webhooks. |
- {uuid} is the endpoint uuid from list / create (not a numeric id). An unknown uuid, a malformed one, or an endpoint of another account gives 404 endpoint.not_found.
- The query string is part of the signed path.
- There is no account-wide endpoints usage route; for the account total across all endpoints see Account (GET /v1/usage).
Exemple de requête
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('GET', '/v1/endpoints/00000000-0000-0000-0000-000000000000/usage'))La requête est signée dans l’extrait lui-même — voir Authentification pour les règles de signature.
Exemple de réponse
{
"ok": true,
"usage": {
"days": 7,
"total_requests": 100,
"total_credits": 250,
"series": [
{ "date": "2026-07-31", "requests": 0, "credits": 0 },
{ "date": "2026-08-06", "requests": 100, "credits": 250 }
],
"methods": [
{ "method": "eth_getBalance", "requests": 50, "credits": 50 }
],
"webhooks": {
"requests": 0,
"credits": 0,
"address_watch_requests": 0,
"address_watch_credits": 0,
"block_requests": 0,
"block_credits": 0,
"payment_requests": 0,
"payment_credits": 0,
"tx_watch_requests": 0,
"tx_watch_credits": 0
}
}
}PATCH /v1/endpoints/{uuid}/status
Set the endpoint status to active or paused (paused stops JSON-RPC traffic).
Corps de la requête
| Nom | Type | Description |
|---|---|---|
| status | string | active | paused (case-insensitive). |
Champs de la réponse
| Nom | Type | Description |
|---|---|---|
| endpoint | object | The updated endpoint. |
| endpoint.uuid | string | Endpoint identifier. Use it in the path of usage / status / allowed-ips; it is also the last segment of the JSON-RPC URL. |
| endpoint.name | string | Display name. |
| endpoint.status | string | active | paused. |
| endpoint.network_name | string | Network display name. |
| endpoint.network_slug | string | Network slug (e.g. ethereum). |
| endpoint.env_slug | string | Environment slug (e.g. mainnet). |
| endpoint.url | string | Public JSON-RPC URL: https://[{host_prefix}-]{network_slug}-{env_slug}.{domain}/{uuid} (the host prefix is optional). The only address the Public API returns; gRPC / WebSocket addresses are not included. |
| endpoint.rps_limit | integer | RPS limit of your plan applied to this endpoint (0 if the account has no plan). |
| endpoint.created_at | string | ISO-8601 timestamp with offset (UTC). |
| endpoint.version | string | Node version of the environment. Optional: absent when unknown. (facultatif) |
| endpoint.block_height | integer | Latest known block height of the environment. Optional: absent when unknown. (facultatif) |
| endpoint.allowed_ips | string[] | Client IP allowlist of the endpoint. Empty array = no restriction. Always present. |
- Example body: {"status":"paused"}
- Missing or other status values give 400 endpoint.invalid_status. An unknown uuid gives 404 endpoint.not_found (checked before the body).
- There is no DELETE — use paused to stop traffic.
Exemple de requête
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('PATCH', '/v1/endpoints/00000000-0000-0000-0000-000000000000/status', '{"status":"string"}'))La requête est signée dans l’extrait lui-même — voir Authentification pour les règles de signature.
Exemple de réponse
{
"ok": true,
"endpoint": {
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Prod Ethereum",
"status": "paused",
"network_name": "Ethereum",
"network_slug": "ethereum",
"env_slug": "mainnet",
"url": "https://ethereum-mainnet.rpcnode.dev/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"rps_limit": 50,
"created_at": "2026-08-01T12:00:00Z",
"version": "v1.14.0",
"block_height": 21000000,
"allowed_ips": []
}
}PATCH /v1/endpoints/{uuid}/allowed-ips
Replace the client IP allowlist of the endpoint. An empty array clears the restriction (allow all).
Corps de la requête
| Nom | Type | Description |
|---|---|---|
| allowed_ips | string[] | Full list of IPv4 / IPv6 literal addresses (max 50). The key is required; [] clears the list. CIDR ranges and hostnames are not accepted. |
Champs de la réponse
| Nom | Type | Description |
|---|---|---|
| endpoint | object | The updated endpoint. |
| endpoint.uuid | string | Endpoint identifier. Use it in the path of usage / status / allowed-ips; it is also the last segment of the JSON-RPC URL. |
| endpoint.name | string | Display name. |
| endpoint.status | string | active | paused. |
| endpoint.network_name | string | Network display name. |
| endpoint.network_slug | string | Network slug (e.g. ethereum). |
| endpoint.env_slug | string | Environment slug (e.g. mainnet). |
| endpoint.url | string | Public JSON-RPC URL: https://[{host_prefix}-]{network_slug}-{env_slug}.{domain}/{uuid} (the host prefix is optional). The only address the Public API returns; gRPC / WebSocket addresses are not included. |
| endpoint.rps_limit | integer | RPS limit of your plan applied to this endpoint (0 if the account has no plan). |
| endpoint.created_at | string | ISO-8601 timestamp with offset (UTC). |
| endpoint.version | string | Node version of the environment. Optional: absent when unknown. (facultatif) |
| endpoint.block_height | integer | Latest known block height of the environment. Optional: absent when unknown. (facultatif) |
| endpoint.allowed_ips | string[] | Client IP allowlist of the endpoint. Empty array = no restriction. Always present. |
- Example body: {"allowed_ips":["203.0.113.10","2001:db8::1"]}
- Clear the restriction: {"allowed_ips":[]}
- The list replaces the previous one. Blank entries are skipped, duplicates are removed, and addresses are returned in canonical form, so an IPv6 address may come back written out in full (2001:db8:0:0:0:0:0:1).
- Errors: missing allowed_ips gives 400 with errors[]; endpoint.too_many_ips (more than 50) and endpoint.invalid_ip (not an IP literal) give 400; an unknown uuid gives 404 endpoint.not_found (checked before the body).
Exemple de requête
import { createHash, createHmac } from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY! // cabinet UUID
const SECRET = process.env.PUBLIC_API_SECRET! // sk_live_…
const BASE = 'https://api.rpcnode.dev'
// path: as the server receives it, with the query; body: the exact string that is sent
async function call(method: string, path: string, body = '') {
const timestamp = Math.floor(Date.now() / 1000)
const bodyHash = createHash('sha256').update(body).digest('hex')
const signature = createHmac('sha256', SECRET)
.update([timestamp, method, path, bodyHash].join('\n'))
.digest('hex')
const res = await fetch(BASE + path, {
method,
headers: {
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
body: body || undefined,
})
return res.json()
}
console.log(await call('PATCH', '/v1/endpoints/00000000-0000-0000-0000-000000000000/allowed-ips', '{"allowed_ips":[]}'))La requête est signée dans l’extrait lui-même — voir Authentification pour les règles de signature.
Exemple de réponse
{
"ok": true,
"endpoint": {
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Prod Ethereum",
"status": "active",
"network_name": "Ethereum",
"network_slug": "ethereum",
"env_slug": "mainnet",
"url": "https://ethereum-mainnet.rpcnode.dev/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"rps_limit": 50,
"created_at": "2026-08-01T12:00:00Z",
"version": "v1.14.0",
"block_height": 21000000,
"allowed_ips": ["203.0.113.10"]
}
}Conseils
- Sign every request with X-Api-Key, X-Timestamp, and X-Signature (see Authentication); the query string is part of the signed path.
- Get network_slug and env_slug from GET /v1/endpoints/available-networks before creating an endpoint.
- JSON-RPC calls go to endpoint.url — do not send Public API HMAC headers on those URLs.
- There is no DELETE — use PATCH …/status with paused to stop traffic.
- Plan caps: endpoints_limit (count) is checked at create; rps_limit applies per endpoint. See Account for your limits.
- Read an absent optional field (version, block_height, network_limit, allowed_network_slugs) as "no value", not as null.
- The Public API itself is rate limited per account (see Authentication); this is separate from the endpoint rps_limit.