API Reference

Exchange Status Monitor
← Dashboard

The monitor exposes a small read-oriented HTTP API. All responses are JSON, and every path below is relative to the host serving this page (shown as https://your-host in the examples). Read endpoints are unauthenticated. The two write endpoints (/api/subscribe, /api/unsubscribe) require a shared secret, compared in constant time. Timestamps are unix milliseconds throughout.

GET /api/healthz public

Liveness probe. Always returns 200 while the process is up.

Example

# request
curl https://your-host/api/healthz

# response
{ "ok": true }
GET /api/snapshot public

The full current state powering the dashboard: every observed (exchange, token, network) row, per-exchange poll freshness, active subscriptions, the 24h change count, and the market-cap ranking used to order the token matrix. The dashboard polls this every 30s.

Response fields

FieldTypeDescription
rowsarrayCurrent state rows (see shape below). One per (exchange, token, network).
lastPolledByExchangeobjectMap of exchange id → unix-ms of its last successful poll, or null if it has never completed one.
subscriptionsarrayActive subscriptions: { token, createdAt, note }.
changeCount24hnumberCount of state-change events in the last 24 hours.
topSymbolsstring[]Tokens in market-cap-rank order (drives matrix sorting / implicit top-100).
topSymbolRankobjectMap of token → market-cap rank (1 = largest).
nownumberServer clock (unix-ms) at response time, for freshness math.

Example

# response (truncated)
{
  "rows": [
    {
      "exchange": "binance", "token": "USDT", "network": "TRC20",
      "deposit": 1, "withdraw": 0,
      "depositReason": null, "withdrawReason": "Wallet Maintenance",
      "firstSeen": 1747000000000,
      "depositChangedAt": null, "withdrawChangedAt": 1747200000000
    }
  ],
  "lastPolledByExchange": { "binance": 1747300000000, "bybit": null },
  "subscriptions": [ { "token": "USDC", "createdAt": 1747100000000, "note": "depeg watch" } ],
  "changeCount24h": 42,
  "topSymbols": [ "BTC", "ETH", "USDT" ],
  "topSymbolRank": { "BTC": 1, "ETH": 2, "USDT": 3 },
  "now": 1747300001000
}
Timestamp semantics. deposit/withdraw are 1 (enabled) or 0 (disabled). A *ChangedAt of null means no flip has been observed since firstSeen — the UI shows "Observed since" rather than a confirmed "Changed" timestamp.
GET /api/changes public

The append-only history of status flips, newest first. Every deposit/withdraw change is one row, tagged with the exchange it came from. History is retained for 365 days.

Query parameters

ParamTypeDefaultDescription
tokenstringallFilter to one token symbol, e.g. USDT.
fieldstringallFilter by change type: deposit or withdraw. Any other value is ignored.
sincenumberLower bound, inclusive: only rows with ts ≥ since (unix-ms).
beforenumberUpper bound, exclusive: only rows with ts < before (unix-ms). Use for backward paging.
limitnumber100Max rows returned. Capped at 1000.

Response fields (per row)

FieldTypeDescription
idnumberAuto-increment row id.
tsnumberWhen the change was observed (unix-ms).
exchangestringExchange id, e.g. binance.
tokenstringToken symbol.
networkstringNetwork identifier as reported by the exchange.
fieldstringdeposit or withdraw.
oldValue / newValuenumber0 = disabled, 1 = enabled.
reasonstring?Reason text if the exchange provides one (Binance/MEXC), else null.

Example — a token's withdraw history

# request
curl "https://your-host/api/changes?token=USDT&field=withdraw&limit=500"

# response
{
  "rows": [
    {
      "id": 8123, "ts": 1747200000000,
      "exchange": "binance", "token": "USDT", "network": "TRC20",
      "field": "withdraw", "oldValue": 1, "newValue": 0,
      "reason": "Wallet Maintenance"
    }
  ]
}
Paging a full year. Results are newest-first and capped at 1000 rows. To walk further back, take the smallest ts in a page and pass it as before on the next request: ?before=1747200000000&limit=1000. Repeat until a page returns fewer than limit rows.
GET /api/subscriptions public

Lists active token subscriptions. Each subscribed token's future state changes are pushed to the configured market-alerts webhook.

Example

# response
{
  "subscriptions": [
    { "token": "USDC", "createdAt": 1747100000000, "note": "depeg watch" }
  ]
}
POST /api/subscribe shared secret

Adds (or updates the note on) a token subscription. Token is normalized to upper-case and trimmed. Returns the full updated list.

Request body (JSON)

FieldTypeDescription
tokenstringrequiredToken symbol to subscribe.
secretstringrequiredThe shared secret required for write endpoints.
notestringOptional free-text note shown in the subscriptions panel.

Responses

StatusMeaning
200Subscribed. Body is { subscriptions: [...] }.
400{ error: "token required" } — empty/missing token.
401Missing or wrong secret. Empty body.

Example

curl -X POST https://your-host/api/subscribe \
  -H "Content-Type: application/json" \
  -d '{ "token": "USDC", "note": "depeg watch", "secret": "<shared-secret>" }'
POST /api/unsubscribe shared secret

Removes a token subscription. Returns the full updated list.

Request body (JSON)

FieldTypeDescription
tokenstringrequiredToken symbol to unsubscribe.
secretstringrequiredThe shared secret required for write endpoints.

Responses

StatusMeaning
200Removed (idempotent — 200 even if it wasn't subscribed). Body is { subscriptions: [...] }.
400{ error: "token required" } — empty/missing token.
401Missing or wrong secret. Empty body.

Example

curl -X POST https://your-host/api/unsubscribe \
  -H "Content-Type: application/json" \
  -d '{ "token": "USDC", "secret": "<shared-secret>" }'
GET /api/lark-health public

Recent webhook delivery results for each dispatcher — one for market alerts, one for health alerts — for debugging. If both dispatchers are configured to share a single webhook, the two fields describe the same one. The dashboard surfaces a warning banner when either dispatcher has 3+ consecutive delivery failures.

Example

# response
{
  "market": { "consecutiveFailures": 0, "recent": [ { "ts": 1747300000000, "ok": true, "status": 200 } ] },
  "health": { "consecutiveFailures": 0, "recent": [ ... ] }
}
The exact shape of each dispatcher record may vary; the fields above are indicative.