Public API
Authentication
Every Public API request is authenticated with a key prefix and an HMAC-SHA256 signature over a canonical string. Signatures older or newer than 300 seconds are rejected.
Headers
X-Api-Key — key prefix from the dashboard (e.g. pk_live_…).
X-Timestamp — Unix time in seconds (integer). Must be within ±300 seconds of server time.
X-Signature — lowercase hex HMAC-SHA256 of the canonical string, keyed with your API key secret.
Canonical string
Join four lines with a single newline (\n) between them. Do not add a trailing newline after the body hash.
METHOD is uppercase (GET, POST, PUT, PATCH, DELETE).
path is the request path starting with /v1/… (no /api prefix), for example /v1/account, /v1/endpoints, or /v1/address-watch/account. Leading slash is required. Query string is NOT part of the signed path.
body is the raw request body bytes as sent. For requests with no body, use an empty string — sha256_hex("") of the empty string.
Formula
{timestamp}\n{METHOD}\n{path}\n{sha256_hex(body)}
X-Signature = hex(hmac_sha256(secret, canonical))Notes
Compare signatures in constant time when verifying (server uses hash_equals).
If the API key has an IP allowlist, the client IP must be listed or the gateway returns 403 with error "IP address not allowed."
Common auth errors: Missing X-Api-Key, Invalid API key, Missing or invalid X-Timestamp, Missing X-Signature, Invalid signature.
curl (GET account + POST address)
Shell
# Prerequisites: openssl, curl. Replace KEY_PREFIX and SECRET.
BASE=https://api.rpcnode.dev
KEY_PREFIX=pk_live_xxxxxxxx
SECRET=your_api_key_secret
# --- GET /v1/account (empty body) ---
PATH=/v1/account
METHOD=GET
BODY=
TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $2}')
CANONICAL=$(printf '%s\n%s\n%s\n%s' "$TS" "$METHOD" "$PATH" "$BODY_HASH")
SIG=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')
curl -sS "$BASE$PATH" \
-H "X-Api-Key: $KEY_PREFIX" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG"
# --- GET /v1/account/usage?days=30 (sign path without query) ---
PATH=/v1/account/usage
METHOD=GET
BODY=
TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $2}')
CANONICAL=$(printf '%s\n%s\n%s\n%s' "$TS" "$METHOD" "$PATH" "$BODY_HASH")
SIG=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')
curl -sS "$BASE$PATH?days=30" \
-H "X-Api-Key: $KEY_PREFIX" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG"
# --- POST /v1/address-watch/addresses ---
PATH=/v1/address-watch/addresses
METHOD=POST
BODY='{"network_slug":"ethereum","address":"0x0000000000000000000000000000000000000000"}'
TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $2}')
CANONICAL=$(printf '%s\n%s\n%s\n%s' "$TS" "$METHOD" "$PATH" "$BODY_HASH")
SIG=$(printf '%s' "$CANONICAL" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $2}')
curl -sS -X POST "$BASE$PATH" \
-H "Content-Type: application/json" \
-H "X-Api-Key: $KEY_PREFIX" \
-H "X-Timestamp: $TS" \
-H "X-Signature: $SIG" \
-d "$BODY"Node.js (GET account + POST address)
JavaScript
import crypto from 'node:crypto'
const API_KEY = process.env.PUBLIC_API_KEY // key prefix, e.g. pk_live_…
const SECRET = process.env.PUBLIC_API_SECRET // shown once at key creation
const BASE = process.env.PUBLIC_API_BASE || 'https://api.rpcnode.dev'
function sha256Hex(body) {
return crypto.createHash('sha256').update(body, 'utf8').digest('hex')
}
function sign({ method, path, body = '' }) {
const timestamp = Math.floor(Date.now() / 1000)
const canonical = [String(timestamp), method.toUpperCase(), path, sha256Hex(body)].join('\n')
const signature = crypto.createHmac('sha256', SECRET).update(canonical, 'utf8').digest('hex')
return { timestamp, signature }
}
async function publicApi(method, path, bodyObj) {
const body = bodyObj === undefined ? '' : JSON.stringify(bodyObj)
const { timestamp, signature } = sign({ method, path, body })
const res = await fetch(`${BASE}${path}`, {
method,
headers: {
'Content-Type': 'application/json',
'X-Api-Key': API_KEY,
'X-Timestamp': String(timestamp),
'X-Signature': signature,
},
body: body === '' ? undefined : body,
})
return res.json()
}
// GET plan + usage snapshot (empty body → sha256 of "")
console.log(await publicApi('GET', '/v1/account'))
// GET usage series
console.log(await publicApi('GET', '/v1/account/usage?days=30'))
// Sign path WITHOUT query string: /v1/account/usage
// POST add address
console.log(
await publicApi('POST', '/v1/address-watch/addresses', {
network_slug: 'ethereum',
address: '0x0000000000000000000000000000000000000000',
}),
)Tips
- Production host is https://api.rpcnode.dev. The signed path is always /v1/… (host is not part of the canonical string).
- Sign the exact body string you send. Pretty-printing or re-serializing JSON after signing will break the signature.
- For DELETE /addresses, include the JSON body with watch_id and sign that body.
- Outbound webhook delivery is documented under Webhook delivery (payload shape and egress IP allowlisting).