Docs
API quick start
~10 min read · v1 stable
Every Blackglass surface — push-agent ingest, fleet scans, baseline capture, drift events, evidence bundles — is reachable through the same v1 REST API the console uses. This page covers the four flows you’ll hit on day one.
1. Authentication
The API uses two credential models depending on which surface you’re hitting:
- Agent endpoints (
/api/v1/ingest/*,/api/v1/agent/*) accept a Bearer token fromINGEST_API_KEY(shared) orINGEST_HOST_KEYS_JSON(per-host, preferred). Generate keys in Settings → Ingest credentials. - Operator endpoints (
/api/v1/scans,/api/v1/baselines,/api/v1/hosts) authenticate via Clerk session cookie (when called from the console) or a Personal Access Token from Settings → API tokens in the Bearer slot.
2. Push a host snapshot
The push-agent on each host POSTs the bundle output to /api/v1/ingest/agent. You almost never need to call this directly — the bundled agent script does it on a 60-second timer. But if you’re testing or building a custom collector, it looks like:
# curl
curl -X POST https://blackglasssec.com/api/v1/ingest/agent \
-H "Authorization: Bearer $BLACKGLASS_INGEST_KEY" \
-H "Content-Type: application/json" \
--data @- <<JSON
{
"hostId": "host-10-0-0-7",
"hostname": "web-prod-7.acme.io",
"collectedAt": "2026-05-09T11:30:00Z",
"bundle": "=BGS:ss\nLISTEN 0 128 :22\n=BGS:passwd\n..."
}
JSON// node-fetch (Node 18+, native fetch)
await fetch("https://blackglasssec.com/api/v1/ingest/agent", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BLACKGLASS_INGEST_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
hostId: "host-10-0-0-7",
hostname: "web-prod-7.acme.io",
collectedAt: new Date().toISOString(),
bundle: bundleStringFromYourCollector(),
}),
});On success you get 200 OK with { "ok": true, "hostId": "host-..." }. The push automatically computes drift against the host’s baseline and stores any findings.
3. Capture a baseline
Before drift detection means anything, each host needs a pinned baseline.
curl -X POST https://blackglasssec.com/api/v1/baselines \
-H "Authorization: Bearer $BLACKGLASS_PAT" \
-H "Content-Type: application/json" \
-d '{"hostIds":["host-10-0-0-7"]}'
# → 202 Accepted
# {"jobId":"baseline-…","status":"queued"}Poll GET /api/v1/baselines/jobs/{jobId} for completion (or skip the wait — once ingest sees a snapshot for a host with no baseline, it auto-bootstraps one).
4. Run a scan + poll for completion
# Kick off
SCAN=$(curl -sS -X POST https://blackglasssec.com/api/v1/scans \
-H "Authorization: Bearer $BLACKGLASS_PAT" \
-H "Content-Type: application/json" -d '{}' \
| jq -r .scanId)
# Poll every 3s until terminal
while true; do
RESP=$(curl -sS https://blackglasssec.com/api/v1/scans/$SCAN \
-H "Authorization: Bearer $BLACKGLASS_PAT")
STATUS=$(printf '%s' "$RESP" | jq -r .status)
printf '[%s] %s\n' "$STATUS" "$(printf '%s' "$RESP" | jq -r .detail)"
if [ "$STATUS" = "succeeded" ] || [ "$STATUS" = "failed" ]; then break; fi
sleep 3
doneThe poll response includes detail— you’ll see messages like “Waiting for fresh agent snapshot (47s remaining)…” when the SSH-fail fallback kicks in. See the snapshot freshness model for what’s happening under the hood.
5. Read drift events
# Latest events for one host
curl -sS \
"https://blackglasssec.com/api/v1/drift?hostId=host-10-0-0-7&limit=20" \
-H "Authorization: Bearer $BLACKGLASS_PAT" \
| jq '.events[] | {title,severity,detectedAt,category}'# Python — same idea
import os, requests
r = requests.get(
"https://blackglasssec.com/api/v1/drift",
params={"hostId": "host-10-0-0-7", "limit": 20},
headers={"Authorization": f"Bearer {os.environ['BLACKGLASS_PAT']}"},
timeout=10,
)
r.raise_for_status()
for e in r.json()["events"]:
print(e["severity"], e["title"])6. Force-push a host
When SSH to a host doesn’t work and you don’t want to wait up to 60s for the next agent push:
curl -X POST \
https://blackglasssec.com/api/v1/hosts/host-10-0-0-7/wake \
-H "Authorization: Bearer $BLACKGLASS_PAT"
# → 200 OK
# {"ok": true, "hostId": "host-10-0-0-7", "storage": "redis",
# "message": "Wake flag set. The host's push-agent will publish its
# next snapshot within ~10 seconds (provided the wake-check
# timer is installed; see /docs/snapshot-freshness)."}7. Error envelope & rate limits
Every error response is the same shape — easy to handle generically:
{
"error": "rate_limited",
"detail": "Too many checkout requests. Please wait before trying again.",
"request_id": "req_01H…"
}The request_id field (also returned in the x-request-id response header) is the same string our server logs use — quote it when you ask support for help and we can pull the trace in seconds.
Rate limits are per-IP for unauthenticated routes and per-tenant for authenticated ones. A 429 means retry after a brief backoff; a 5xx should trigger an exponential retry up to ~30s.
8. Full OpenAPI spec
Every route, payload, and response code is described in the canonical OpenAPI 3.1 document at /openapi.yaml. Drop it into Postman, openapi-generator, or your IDE for typed clients in any language.
Have a route you wish existed? Tell us what you need — we ship API surface fast when there’s a clear use case.