HTTP API reference
Every endpoint returns JSON unless noted. Timestamps named ts are Unix milliseconds. The dashboard UI is a static page that only uses this public API — anything the UI can do, you can script.
curl is enough. All examples below use -u admin:$AUTH_PASS for Basic auth and assume http://localhost:8080.Authentication
Every endpoint except /healthz requires HTTP Basic auth when AUTH_PASS is set (the default posture). Credentials are compared in constant time.
curl -s -u admin:'change-me' http://localhost:8080/api/apps
# or with a header
curl -s -H 'Authorization: Basic YWRtaW46Y2hhbmdlLW1l' http://localhost:8080/api/containers
Health
Returns the plain text ok. The image carries a matching Docker HEALTHCHECK, so use the same path on your platform's HTTP health check.
Containers and apps
{"containers":[{"id":"...","name":"srv-captain--shop.1.x","app":"shop","replica":"1",
"image":"shop:1.4","started":1793158700000,"cpu":12.5,"mem":104857600,
"mem_limit":536870912,"sample_ts":1793158700000}]}
Includes apps whose containers are gone — that is the point. Each row carries a one-hour error and warning count.
{"apps":[{"app":"shop","containers":1,"last_seen":1793158700437,
"errors_1h":8,"warns_1h":14}]}
Logs
| Param | Meaning |
|---|---|
app | App name. |
container | Container name or id prefix. |
q | Search text, case-insensitive. Uses the FTS5 token/prefix index as a fast path (each word matches as a prefix, so dat finds database); when the index finds nothing it falls back to LIKE, so mid-word searches still work. |
regex | 1 treats q as a case-insensitive regular expression. Invalid patterns return 400. |
level | Comma list of error, warn, info, debug. |
stream | stdout or stderr. |
since | Relative (15m, 2h, 1d) or epoch milliseconds. |
until | Epoch milliseconds. |
after_id | Only rows with a larger id, ascending — use for catching up. |
before_id | Only rows with a smaller id — use for paging back. |
limit | Default 500, max 5000. Without after_id you get the newest limit rows. |
{"id":260,"ts":1793158659437,"container_id":"...","container":"srv-captain--shop.1.x",
"app":"shop","stream":"o","level":"error","line":"ERROR ..."}
stream is o for stdout and e for stderr. level may be empty when the heuristic finds no level word.
A Server-Sent Events stream of new rows matching the same filters (except paging and time params). Each data: line is one row. A dropped event reports how many rows this client lost to back-pressure. Comment lines (: ping) keep the connection alive.
curl -N -u admin:$AUTH_PASS 'http://localhost:8080/api/logs/stream?app=shop&level=error'
Streams matching rows as capscope-logs.csv, honoring the same filters as /api/logs (app, q, level, since, …). It paginates internally, so exports reach up to 200,000 lines regardless of the normal 5000-row cap.
Access logs (GoAccess-style)
Parsed web-server access-log rows — nginx combined/common, CapRover's captain-nginx defaults and most proxy formats — are collected automatically:
- Containers whose app name looks like a reverse proxy are parsed from the first line.
- Any other container is probed and switched on once most of its recent stdout parses as access logs.
- Containers matched by
EXCLUDEare followed only for this purpose: no rows inlogs, no sidebar entry, but their requests still feed the dashboard. ACCESS_LOGselectsauto(default),alloroff;ANON_IPS=falsestores original IPs (default anonymizes: last octet zeroed, IPv6 cut to /48).- Retention follows
LOG_RETENTION.
| Param | Default | Meaning |
|---|---|---|
range | 1h | Window such as 1h, 24h, 7d. |
app | all | Restrict to one app. |
top | 20 | Rows returned per path/IP/referrer list. |
{"since_ms":...,"until_ms":0,"bucket_ms":12000,
"totals":{"requests":900,"client_err":41,"server_err":9,"unique_ips":3,"bytes":88401},
"codes":[{"code":200,"n":600}],
"paths":[{"key":"/api/items","hits":212,"bytes":9901,"errors":3}],
"ips":[{"key":"203.0.113.0","hits":400,"bytes":41000,"errors":2}],
"refs":[{"key":"https://google.com/","hits":12,"errors":0}],
"timeline":[{"ts":1793157492000,"hits":30,"errors":1,"bytes":2955}]}
refs excludes empty and - referrers.
Streams raw parsed rows as capscope-access.csv: ts, ts_ms, container, app, ip, method, path, status, bytes, referer, user_agent. Default range 24 h.
SQL queries
| Param | Meaning |
|---|---|
query | A single SELECT (or WITH … SELECT) statement. |
limit | Default 1000, max 5000. truncated is true when more rows matched. |
{"columns":["level","n"],"rows":[["error",3],["info",41]],"truncated":false,"ms":1}
Rules, enforced in order
- The statement must start with
SELECTorWITH, contain no stacked statements (;), and carry no write/DDL keywords outside string literals and comments. Violations return400. - The read connection pool opens with
_query_only=1, so any write that slipped through still fails at SQLite level. - A 5-second deadline and the row cap keep heavy scans from pinning the pool.
Tables: logs, metrics, containers, snapshots (plus logs_fts for MATCH joins). Column notes: logs.ts is unix nanoseconds; metrics.ts and snapshots.ts are unix milliseconds; snapshots.die_ts is the death time in milliseconds (0 for rows saved before v0.5); logs.stream is o/e.
SELECT app, level, count(*) AS n FROM logs
WHERE ts > (unixepoch('now') - 3600) * 1000000000
GROUP BY app, level HAVING n > 5 ORDER BY n DESC
The UI exposes this as the SQL option in the search-mode select.
Metrics
| Param | Meaning |
|---|---|
app | App name — CPU and memory are summed across replicas. |
range | Window such as 15m, 1h, 7d. |
container | Container id prefix, for a single replica. |
Expect about 300 points per range with a minimum bucket of 5 s. net_rx/net_tx are cumulative counters.
{"bucket_ms":12000,"points":[{"ts":1793157492000,"cpu":10,"mem":104857600,
"mem_limit":536870912,"net_rx":0,"net_tx":0,"blk_r":0,"blk_w":0,"pids":0}]}
Crash snapshots
?app=shop&limit=100 — metadata only, no embedded logs or metrics.
Metadata, summary, logs, metrics and a trimmed inspect object — plus the newest deploy before the crash as a deploy object when one exists.
A Markdown crash report suitable for pasting into an incident channel, including exit code, restart window, last deploy and the log tail.
| Field | Meaning |
|---|---|
kind | One of oom, crash, killed, clean_exit. |
die_ts | Death time taken from the Docker event — a snapshot rebuilt after CapScope was down keeps the real death time, not the assembly time. |
restarts_window | How many times that container had died inside the restart-loop window at that moment (0 when detection is disabled). The UI marks rows at or above the threshold with ↻. |
Deaths that happened while CapScope was down are replayed from the persisted event cursor on startup and snapshotted like live ones, as long as the container still exists and the gap is under 24 hours.
Alerts
Rules from ALERTS_FILE are evaluated every ALERTS_INTERVAL (default 1m). Every firing is recorded, and the most recent firing per rule and app doubles as the silence window that prevents re-notifying on every tick — a repeat on the rule (default 30m) sets that silence.
?app=shop&limit=100 — newest first. Each row is {id, ts, rule, app, kind, message}, where ts is Unix ms and kind is the rule kind.
{"rules":[{"name":"api-mem","kind":"metric","apps":["api"],"for":"5m",
"metric":"mem_pct","op":"gt","value":85,"repeat":"30m"}],
"channels":["webhook"],"enabled":true}
enabled is false when no rules file exists.
Sends a test message through every configured channel and returns {results: {webhook: "sent" | "failed: …"}}. Returns 400 when no channels are configured or alerting is off.
curl -s -X POST -u admin:$AUTH_PASS http://localhost:8080/api/alerts/test
Deploys
Deploy markers correlate a crash or a metric spike with the release that preceded it. Record them from CI (POST) or with the Record deploy button in the Metrics tab.
Body {app, version, note?, ts?}. app is required; ts is optional Unix ms and defaults to now (a far-future value is clamped to now). Returns {id, ts, app, version, note}.
curl -s -X POST -u admin:$AUTH_PASS \
-H 'Content-Type: application/json' \
-d '{"app":"shop","version":"v1.4.2","note":"rollout"}' \
http://localhost:8080/api/deploys
?app=shop&since=1700000000000&until=&limit=200 — app, since and until (Unix ms) are optional; an empty app returns every app's markers.
Markers appear as dashed vertical lines with a version label on the CPU and memory charts, and as inline rows in the log timeline. The crash detail and its Markdown export carry the newest marker before the death as Last deploy.
curl -s -G -u admin:$AUTH_PASS http://localhost:8080/api/sql \
--data-urlencode "query=SELECT s.die_ts, s.app, s.kind, d.version FROM snapshots s LEFT JOIN deploys d ON d.app = s.app AND d.ts = s.die_ts ORDER BY s.die_ts DESC LIMIT 50"