If your trading bot depends on MadeOnSol's KOL feed or DEX firehose, you probably want to know within 30 seconds when one of them goes degraded — not 30 minutes later when you notice your alert volume dropped to zero. The standard way to wire that up is an external monitor (UptimeRobot, StatusCake, Better Uptime, Pingdom) that pings a known endpoint and pages you on failure.
GET /api/v1/status is built for exactly that. It's public (no auth), returns structured JSON, includes per-service status flags, and exposes 24-hour and 7-day uptime percentages. This post walks through what's in the response, the monitoring patterns that work cleanly with it, and the integration steps for the common monitors.
The endpoint and response shape
curl https://madeonsol.com/api/v1/status
{
"status": "operational",
"services": {
"kol_tracker": {
"status": "healthy",
"last_check": "2026-05-14T11:42:03Z",
"response_ms": 84,
"last_trade_age_s": 7
},
"deployer_listener": {
"status": "healthy",
"last_check": "2026-05-14T11:42:03Z",
"response_ms": 41
},
"nextjs_app": {
"status": "healthy",
"last_check": "2026-05-14T11:42:04Z",
"response_ms": 112
},
"supabase_db": {
"status": "healthy",
"last_check": "2026-05-14T11:42:03Z",
"response_ms": 8
},
"websocket": {
"status": "healthy",
"last_check": "2026-05-14T11:42:04Z",
"connections": 142
}
},
"uptime": {
"last_24h": 99.9,
"last_7d": 99.7
},
"checked_at": "2026-05-14T11:42:08Z"
}
Five core services are covered:
kol_tracker — the gRPC subscriber that ingests KOL wallet swaps and writes kol_trades. The last_trade_age_s field reports seconds since the most recent insert. A healthy value is single-digit to low-double-digit; sustained >60s means the stream is broken.
deployer_listener — the gRPC subscriber that watches tracked deployer wallets and emits alerts. Restarts are expected (gRPC reconnects) — the status flag captures actual liveness.
nextjs_app — the API surface itself. If this is degraded, the status endpoint may not respond at all, so a separate uptime monitor is still useful even with this field.
supabase_db — Postgres health. Slow responses here cascade to every endpoint.
websocket — the WS streaming server. connections reports active subscriber connections.
Each service's status is one of:
healthy — operating normally.
degraded — operating but with elevated latency or partial functionality. Worth investigating but not page-worthy.
down — not operating. Page immediately.
unknown — no recent check available. Treat as degraded.
The top-level status aggregates the five:
operational — all services healthy.
degraded — at least one service degraded, none down.
incident — at least one service down.
Uptime numbers
uptime.last_24h and uptime.last_7d are computed as the percentage of historical health checks where the status was healthy over the rolling window. The underlying check runs every 5 minutes per service via the api-monitor cron, so over 24 hours that's ~1,440 checks across 5 services = ~7,200 data points. The percentage is computed at one-decimal precision (99.9, not 99.872).
A value of 99.9 over the last 24h means about 1.4 minutes of cumulative degraded/down time. A value of 99.0 over 24h means about 14 minutes — anything below 99 is a bad day. The 7d number smooths out single-incident days.
Caching and rate limits
- Server-side cache: 30 seconds. Multiple monitors hitting the endpoint in rapid succession see the same response.
- CDN cache header:
Cache-Control: public, max-age=30, s-maxage=60, stale-while-revalidate=300 — friendly to status pages embedding the endpoint.
- Per-IP rate limit: 30 requests per 60 seconds. More than enough for a monitor on a 60-second interval, with headroom for ad-hoc polling.
If you have multiple monitors hitting the endpoint, they share the per-IP bucket. For most setups (one monitor checking every 60s), you're nowhere near the limit.
Monitoring patterns
Three patterns cover most use cases, in increasing sophistication:
1. Simple alive check
The cheapest setup: monitor that GET /api/v1/status returns HTTP 200 within 5 seconds. UptimeRobot, StatusCake, Better Uptime all support this out of the box.
Failure case: the endpoint itself doesn't respond. That's a strong signal — the entire API is down or the network is partitioned. Page immediately.
Limitation: this doesn't catch service degradation. The endpoint will return 200 with status: "degraded" even when half the services are sick.
2. JSON body check
A small step up: pull the JSON and assert that status === "operational" (or whichever threshold you want). UptimeRobot's "Keyword" monitoring checks for a substring in the response body — set it to look for "status":"operational" and fail when missing.
# StatusCake / Better Uptime support response-body checks too
# Set the expected pattern: "status":"operational"
# Alert when the pattern is missing
This catches the case where the endpoint responds with "status":"degraded" or "status":"incident". You'll still get false-positives during brief degraded windows that auto-resolve; pair with an alert delay (e.g., "page after 2 consecutive failures") to filter noise.
3. Per-service fan-out
The richest setup: monitor each service field independently. Most external monitors support JSON-path extraction; configure separate checks for:
services.kol_tracker.status === "healthy"
services.deployer_listener.status === "healthy"
services.websocket.status === "healthy"
services.supabase_db.status === "healthy"
uptime.last_24h >= 99 (catches sustained-but-not-current degradation)
This way you get distinct alerts: "KOL tracker down" pages immediately, "DB slow" pages with a different priority. Useful if you want to route alerts by component.
For the kol_tracker service specifically, also check last_trade_age_s if your monitor supports numerical comparisons. A value above 60 means the stream has stalled even if the service status hasn't flipped yet.
Building your own status indicator
The endpoint is a clean data source for embedding a status indicator into your own dashboard:
async function fetchMadeOnSolStatus() {
const res = await fetch("https://madeonsol.com/api/v1/status");
const data = await res.json();
return {
overall: data.status,
kol: data.services.kol_tracker.status,
ws: data.services.websocket.status,
db: data.services.supabase_db.status,
uptime_24h: data.uptime.last_24h,
last_trade_age_s: data.services.kol_tracker.last_trade_age_s ?? null,
};
}