XRPL Analytics · Docs
← Home
Examples in

Documentation

Base URL https://keystoneprotocol.io · all responses are JSON · IDs are returned as strings.

Quickstart

1

Get a free API key

Create an account on the signup page — you get a Starter key instantly, no card. Keep it somewhere safe; it's shown once.

2

Make your first call

Pass the key as a bearer token. This returns index totals and the current lag:

curl https://keystoneprotocol.io/v1/stats \
  -H "Authorization: Bearer $KEY"
const res = await fetch("https://keystoneprotocol.io/v1/stats", {
  headers: { Authorization: `Bearer ${process.env.XRPL_KEY}` },
});
const data = await res.json();
console.log(data);
import os, requests

r = requests.get(
    "https://keystoneprotocol.io/v1/stats",
    headers={"Authorization": f"Bearer {os.environ['XRPL_KEY']}"},
)
print(r.json())
3

Query an account, or subscribe to webhooks

Pull an account's recent activity (below), or on Pro+ get a signed webhook the instant something happens. Check your usage anytime on your dashboard.

Authentication

Every request except /v1/health, /v1/plans, and /v1/public-stats needs your key as a bearer token. Usage is metered per your plan; over-limit requests return 429.

Query the index

GET /v1/accounts/{account}/activity?limit=&before_ledger=

Recent transactions touching an account (from tx metadata), most-recent first.

curl "https://keystoneprotocol.io/v1/accounts/rYourAccount.../activity?limit=5" \
  -H "Authorization: Bearer $KEY"
const account = "rYourAccount...";
const res = await fetch(
  `https://keystoneprotocol.io/v1/accounts/${account}/activity?limit=5`,
  { headers: { Authorization: `Bearer ${process.env.XRPL_KEY}` } },
);
const { activity } = await res.json();
for (const tx of activity) console.log(tx.ledger_index, tx.tx_type, tx.tx_hash);
import os, requests

account = "rYourAccount..."
r = requests.get(
    f"https://keystoneprotocol.io/v1/accounts/{account}/activity",
    headers={"Authorization": f"Bearer {os.environ['XRPL_KEY']}"},
    params={"limit": 5},
)
for tx in r.json()["activity"]:
    print(tx["ledger_index"], tx["tx_type"], tx["tx_hash"])
GET /v1/accounts/{account}/payments?limit=

Payments to or from an account — XRP or issued-currency.

GET /v1/tx/{hash}
GET /v1/ledgers/{index}
GET /v1/stats

Index totals + current indexing lag.

Webhooks (Pro+)

Subscribe an HTTPS endpoint; we POST a signed event whenever a matching transaction is validated.

POST /v1/subscriptions
fieldmeaning
urlyour HTTPS endpoint (required)
watch_accountfire only for this account (optional)
watch_tx_typee.g. Payment (optional)
directionany · incoming · outgoing
min_dropsminimum XRP amount in drops (optional)
curl -X POST https://keystoneprotocol.io/v1/subscriptions \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://you.example/hook","watch_tx_type":"Payment","min_drops":1000000000}'
# → { "id":"…", "secret":"whsec_…" }   (the secret signs your webhooks — shown once)
const res = await fetch("https://keystoneprotocol.io/v1/subscriptions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.XRPL_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://you.example/hook",
    watch_tx_type: "Payment",
    min_drops: 1_000_000_000, // 1000 XRP
  }),
});
const { id, secret } = await res.json();
console.log("store this signing secret:", secret);
import os, requests

res = requests.post(
    "https://keystoneprotocol.io/v1/subscriptions",
    headers={"Authorization": f"Bearer {os.environ['XRPL_KEY']}"},
    json={
        "url": "https://you.example/hook",
        "watch_tx_type": "Payment",
        "min_drops": 1_000_000_000,  # 1000 XRP
    },
)
data = res.json()
print("store this signing secret:", data["secret"])

Verify each delivery

We sign every webhook: X-XRPL-Signature: sha256=HMAC_SHA256(secret, rawBody). Compute the same HMAC over the raw request body and reject anything that doesn't match. Deliveries are at-least-once — dedupe on X-XRPL-Delivery.

# Signature verification is done in your webhook receiver — see the
# JavaScript or Python tab for a copy-paste verifier.
import crypto from "node:crypto";

// Express: use express.raw({ type: "application/json" }) so req.body is the raw Buffer.
function verify(rawBody, header, secret) {
  const expected =
    "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return header === expected;
}

app.post("/hook", express.raw({ type: "*/*" }), (req, res) => {
  if (!verify(req.body, req.get("X-XRPL-Signature"), process.env.WHSEC))
    return res.status(401).end();
  const event = JSON.parse(req.body.toString());
  console.log(event.event, event.tx_hash, event.data);
  res.json({ received: true });
});
import hmac, hashlib
from flask import Flask, request, abort

app = Flask(__name__)
WHSEC = "whsec_..."

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(header or "", expected)

@app.post("/hook")
def hook():
    if not verify(request.get_data(), request.headers.get("X-XRPL-Signature"), WHSEC):
        abort(401)
    event = request.get_json()
    print(event["event"], event["tx_hash"], event["data"])
    return {"received": True}
GET /v1/subscriptions
DELETE /v1/subscriptions/{id}
GET /v1/deliveries?limit=

Bulk export (Enterprise)

GET /v1/export/payments?from_ledger=&to_ledger=&limit=

Payments over a ledger range. Row count is metered against your monthly export quota.

curl "https://keystoneprotocol.io/v1/export/payments?from_ledger=105000000&to_ledger=105001000&limit=10000" \
  -H "Authorization: Bearer $KEY" -o payments.json
const res = await fetch(
  "https://keystoneprotocol.io/v1/export/payments?from_ledger=105000000&to_ledger=105001000&limit=10000",
  { headers: { Authorization: `Bearer ${process.env.XRPL_KEY}` } },
);
const { count, payments } = await res.json();
console.log(`${count} payments`);
import os, requests

r = requests.get(
    "https://keystoneprotocol.io/v1/export/payments",
    headers={"Authorization": f"Bearer {os.environ['XRPL_KEY']}"},
    params={"from_ledger": 105000000, "to_ledger": 105001000, "limit": 10000},
)
data = r.json()
print(data["count"], "payments")

Your account

GET /v1/usage

Your plan, limits, and current-month usage — or manage it all on your dashboard.

Status codes

codemeaning
401missing / invalid API key
403feature not on your plan (upgrade)
429rate limit or monthly quota exceeded
404not found

← Back to home · Dashboard