monad-ops API reference
Public REST API
Read-only JSON endpoints over a single validator's execution-log telemetry.
All data paths accept GET/HEAD/OPTIONS and set
Access-Control-Allow-Origin: * — external dashboards may fetch directly
from the browser.
Base URL for this instance: https://ops.rustemar.dev
- Responses are JSON except where noted. All timestamps are milliseconds since Unix epoch in UTC unless suffixed with
_sec. retry_pct/rtp— share of a block's transactions re-executed due to conflicts in Monad's parallel execution engine (reported bymonad-execution).- UI renders timestamps in the viewer's local timezone; API returns UTC milliseconds.
- Rate-limit: 50 requests / 10 s per IP at the edge. Cache your client if you poll.
- No auth. This is operational telemetry for one node — treat it as public data.
Endpoints
| Path | Purpose |
|---|---|
GET /api/state |
Live snapshot: uptime, blocks seen, rolling averages (1min/5min retry, TPS, gas), current epoch, reference-chain lag. |
GET /api/blocks/sampled?from_ts_ms=&to_ts_ms=&points=300 |
Server-aggregated time-series for charts. Returns ≤points bins regardless of span, so an arbitrary window is safe. |
GET /api/blocks?limit=N |
Recent blocks from the in-memory buffer (last ~5 min). For historical blocks, use window_summary with include_blocks=true. |
GET /api/contracts/top_retried?since_ts_ms=&until_ts_ms=&min_appearances=&limit= |
Contracts ranked by appearance in blocks with retry_pct>0. Reads the hourly rollup — sub-50 ms for any window up to the data-retention horizon. |
GET /api/contracts/labels |
Human-name registry for known contracts (empty unless the operator has populated labels.json). |
GET /api/window_summary?from_ts_ms=&to_ts_ms=&top_contracts_limit=&include_blocks= |
Single-call report. Default = aggregate metrics + top contracts, any span ≤ 30 days, ~3.6 kB. include_blocks=true adds per-block series; span capped at 2 h (the length of one stress-test batch). |
GET /api/alerts?limit=N |
Recent in-memory alerts. |
GET /api/alerts/history?from_ts_ms=&to_ts_ms=&severity=&limit= |
Persisted alerts from SQLite. Filter by severity (warn/critical/recovered). |
GET /api/reorgs?limit=N |
All observed reorgs (same-block-number, different-block-id events), newest-first. Each row reconstructs both block_ids (before = original observation, after = winning fork) plus the reorged block's metrics. |
GET /api/reorgs/{block_number}?window=N |
Forensic trace around a specific reorg — the reorged block plus ±window neighbors (default 30, max 500). Returns chain-derived fields (block_number, block_id, timestamp_ms, tx_count, retried, retry_pct, gas_used). Add &level=full to include per-block execution phase timings in microseconds (tx_exec_us, commit_us, chunks). 404 if no reorg alert exists for this block. |
GET /api/reorgs/{block_number}/journal |
Sanitized monad-bft journal lines around a reorg event — gzipped JSONL covering ~10 s before and after the block's wall-clock timestamp. Peer IPs and the local OTLP loopback are scrubbed at write time; validator pubkeys, block ids, rounds, votes and base fees pass through unchanged. 404 when the reorg pre-dates the capture feature or the journal had already rotated past the event window when the snapshot ran. |
GET /api/blocks/range?from_block=&to_block=&from_ts_ms=&to_ts_ms=&limit= |
Historical block query backed by persistent storage. Use for post-event analysis (e.g. querying a stress-test window by block_number or ms timestamp). |
GET /api/enrichment/status |
Operator status of the receipts-enrichment worker — enabled, attempts/succeeded/failed/dropped counters. |
GET /api/probes |
Sanitized host-probe status (service/key/disk/fd) — name, status, summary. |
GET /healthz |
Liveness — returns {"ok": true}. |
Stress-test replay (Foundation, 2026-04-20)
Replay the three 2-hour stress batches Monad Foundation ran on testnet (epochs 532 / 533 / 534, ~3–5 k TPS at ~400 M gas/s, block utilisation touching 100 %). Each curl below pulls the aggregate summary for the epoch window this node observed: retry-rate profile, peak / average TPS and gas, top retried contracts, and an optional per-block series. Timestamps are fixed so a fresh reader gets reproducible numbers without looking up epoch boundaries.
curl -s "https://ops.rustemar.dev/api/window_summary\
?from_ts_ms=1776643200000&to_ts_ms=1776663420000&top_contracts_limit=20" \
| jq '.aggregate'
curl -s "https://ops.rustemar.dev/api/window_summary\
?from_ts_ms=1776663420000&to_ts_ms=1776683640000&top_contracts_limit=20" \
| jq '.aggregate'
curl -s "https://ops.rustemar.dev/api/window_summary\
?from_ts_ms=1776683640000&to_ts_ms=1776703800000&top_contracts_limit=20" \
| jq '.aggregate'
Add &include_blocks=true to any of the above for the per-block
series within a batch (capped at 2 h span on the server, so split a full epoch
in half if you want the full trace). The .top_contracts array on
every response ranks the heaviest re-execution contributors for that window.
Examples
Replace the base URL below with your own if running a fork.
Note: date +%s%3N is GNU coreutils. On macOS/BSD use python3 -c 'import time; print(int(time.time()*1000))' or gdate +%s%3N (from coreutils).
curl -s https://ops.rustemar.dev/api/state | jq '{rtp_1m, rtp_5m, epoch}'
NOW=$(date +%s%3N)
DAY=$((24 * 3600 * 1000))
curl -s "https://ops.rustemar.dev/api/window_summary?from_ts_ms=$((NOW - DAY))&to_ts_ms=$NOW" \
| jq '.aggregate'
curl -s "https://ops.rustemar.dev/api/window_summary\
?from_ts_ms=1776531600000&to_ts_ms=1776538800000\
&include_blocks=true&top_contracts_limit=50" \
| jq '{agg: .aggregate, contracts: (.top_contracts|length), blocks: (.blocks|length)}'
SINCE=$(( $(date +%s%3N) - 3600000 ))
curl -s "https://ops.rustemar.dev/api/contracts/top_retried?since_ts_ms=$SINCE&limit=10" \
| jq '.rows[] | {addr: .to_addr, ret: .retried_blocks, rtp: .avg_rtp_of_blocks}'
curl -s "https://ops.rustemar.dev/api/blocks/sampled\
?from_ts_ms=1776531600000&to_ts_ms=1776538800000&points=300" \
| jq '.bins | length'
curl -s https://ops.rustemar.dev/api/reorgs \
| jq '.reorgs[] | {n: .block_number, ts: .alert_ts_ms, tx: .block.tx_count, rtp: .block.retry_pct}'
curl -s "https://ops.rustemar.dev/api/reorgs/26543283?window=30" \
| jq '{before: .block_id_before, after: .block_id_after, neighbors: (.blocks|length)}'
curl -s -o reorg-27868954.jsonl.gz "https://ops.rustemar.dev/api/reorgs/27868954/journal"
zcat reorg-27868954.jsonl.gz | head -3
Response shapes — selected fields
/api/state
last_block— most-recent block number seen on this node.last_block_seen_ms— wall-clock when the block landed in our tailer.blocks_per_sec_1m,tx_per_sec_1m,gas_per_sec_1m— 60 s moving averages.rtp_avg_1m,rtp_avg_5m,rtp_max_1m— retry-rate rolling stats (0–100).tps_effective_peak_1m,gas_per_sec_effective_peak_1m— peak per-block throughput, not averaged over idle gaps between blocks.reorg_count,last_reorg_number,last_reorg_old_id,last_reorg_new_id— chain-integrity counters.epoch—{number, blocks_in, typical_length, eta_sec}.typical_lengthis learned empirically from closed epochs.
/api/window_summary (aggregate tier)
window—{from_ts_ms, to_ts_ms, span_sec}, echoed back.aggregate.blocks— total blocks in the window on this node.aggregate.peak_rtp,avg_rtp— retry percent, single-block peak and arithmetic mean.aggregate.peak_tps,avg_tps— effective TPS (peak is a single-block figure, avg is total_tx / span_sec).aggregate.peak_gps,avg_gps— effective gas-per-sec.aggregate.total_gas,total_tx,total_retried,retried_fraction— cumulative volumes.top_contracts[]—{to_addr, label, blocks_appeared, retried_blocks, retried_ratio, avg_rtp_of_blocks, tx_count, total_gas}. Ranked byretried_blocks.blocks[]— only wheninclude_blocks=true. Compact keys:n(block_number),t(timestamp_ms),tx,rt(retried count),rtp,gas,tpse(effective TPS),gpse(effective gas/s).
/api/blocks/sampled
bins[]— time-ordered. Each bin carriest(first timestamp_ms in bin),samples, block-number range, and AVG / P95 / MAX for retry_pct plus AVG / MAX for TPS and gas — enough to chart without re-deriving.
Error responses
400— validation error (e.g., span too large, from > to). Body:{"error": "<message>"}.404— resource not found (no such reorg, unknown path). Body:{"detail": "<message>"}.405— method not allowed.422— parameter type/range error (e.g.,points=0orseverity=foo). Body: Pydantic validation error list in{"detail":[...]}.429— rate limited. Retry after theRetry-Afterheader (seconds).
Scope and limitations
- One-node view. All counters are what this node observed. Reorgs, stalls and retry rates at other validators may differ — use a reference RPC for chain-wide truth.
- Window contract-ranking accuracy. For
/api/contracts/top_retriedandwindow_summaryspans > 6 hours, the ranking is served from an hourly rollup with ablocks_appeared ≥ 10per-hour filter. Top-20 is identical to raw, top-100 preserves ~92%. Shorter windows stay on the raw table for full sparse-contract accuracy. - Data retention. Raw block storage is one-node-local. The operator may prune older data at any time — don't build on "this endpoint returns all history forever".
- Schema stability. Field names in this document are the intended contract. If you integrate, pin a git tag of the backing repo and re-test on minor-version bumps.
Source: github.com/rustemar/monad-ops. Issues / PRs welcome.