Error handling
The public API uses standard HTTP status codes and JSON bodies. Clients should distinguish "request failed", "succeeded but no data yet", and "node offline".
Status codes
| Status | Meaning | Client handling |
|---|---|---|
200 | success | parse JSON; accept null, empty arrays, new fields |
204 | CORS preflight success | no body |
400 | missing or invalid parameter | fix the request, do not retry as-is |
401 | invalid token/session | protected endpoints only; never log credentials |
403 | origin, permission, or policy rejection | check the exact origin and scope |
404 | resource or target missing | check ID, enabled state, deployment version |
409 | state conflicts with precondition | refresh admin data and re-confirm |
413 | body or upload too large | shrink the request or ZIP |
415 | unsupported Content-Type | use the allowed ZIP Content-Type |
429 | rate limited | honor Retry-After if present, back off exponentially |
500–504 | transient server/upstream error | retry a bounded number of times with jitter |
Retry strategy
Retry only idempotent GET requests, with a cap:
async function getJson(url, attempts = 3) {
for (let attempt = 0; attempt < attempts; attempt += 1) {
const response = await fetch(url, { credentials: 'omit' })
if (response.ok) return response.json()
if (![429, 500, 502, 503, 504].includes(response.status)) {
throw new Error(`Request rejected: HTTP ${response.status}`)
}
const retryAfter = Number(response.headers.get('retry-after') || 0) * 1000
const backoff = Math.max(retryAfter, 500 * (2 ** attempt))
const jitter = Math.floor(Math.random() * 250)
await new Promise(resolve => setTimeout(resolve, backoff + jitter))
}
throw new Error('API temporarily unavailable')
}Empty data is not an error
These are all normal:
- A new Agent has not reported yet:
latestisnull. - No pings in the window:
pingsis empty. - External latency sources passed the stale window:
sourcesis empty. - The target hides its public address: Cloudflare and external latency are not shown.
- Sensors unavailable in virtualization: temperature is
null.
Show "no data yet" instead of throwing. warnings[] means the main payload returned but an optional source failed; keep the content and show a non-blocking notice. Conversely, HTTP 200 does not guarantee success: the latency compat path may return { "ok": false, "error": "..." }, so check both the status and ok.
Compatibility rules
v1 may add optional fields but never silently deletes or renames existing ones. Clients should: check api_version; ignore unknown fields; not rely on JSON key order; sort timestamps explicitly; define defaults for null, missing fields, and empty arrays; and never inject error text via innerHTML.
Browser CORS failures
If curl works but the browser fails, the origin is missing from DEVELOPER_API_ORIGINS. The allowlist needs full origins like https://status.example.com without paths and without *. HTTP is allowed only for localhost, 127.0.0.1, and [::1] development addresses.
Update check 429
When the update card hits 429 reading the official manifest, it falls back to a cache or the bundled manifest and marks the source. Do not treat the degraded state as an Agent update failure, and do not hammer refresh to bypass the 15-minute backoff. Treat it as a transient server error only when official, cache, and bundled manifests are all unavailable.
Collecting diagnostics safely
Record: app/Agent version, timestamp, method and path, HTTP status, X-NIE-SLA-API-Version, X-NIE-SLA-Cache (or their v1 legacy aliases), and sanitized error/warnings. Never record: Authorization, x-admin-session, TOTP, full Agent install commands, backup passwords, or NQ image-host URLs/tokens.