REST API
Astrari exposes a JSON-over-HTTPS REST API for everything the dashboard does — list sites and servers, query findings, trigger scans, pull reports, and receive webhooks when new findings appear. API keys are available on the Professional and Agency plans (see pricing).
Base URL: https://api.astrari.io
Format: JSON request and response bodies. Content-Type: application/json on every write request.
Encoding: UTF-8. Dates are ISO 8601 strings in UTC unless otherwise stated.
Use a Bearer-token API key in the Authorization header. API keys are issued from the dashboard at Settings → Organisation → API keys. Agency-plan customers can additionally issue per-client API keys at Clients → [client] → API keys; those keys are automatically scoped to that client's assets.
curl example
curl https://api.astrari.io/sites \ -H "Authorization: Bearer iw_live_xxxxxxxxxxxxxxxxxxxxxxxx"
Keys begin with iw_live_ followed by 32 random base64url bytes. We only store a SHA-256 hash of each key — the raw value is shown to you exactly once at creation. Lose it, rotate it.
Multi-tenancy. Every endpoint that returns data is automatically scoped to the organisation (and client, if your key is client-scoped) that owns the API key. You never need to pass an org or client identifier — it's derived from the key.
Pagination. List endpoints accept limit (max 200, default 50) and offset query parameters.
Errors. Non-2xx responses share a common shape:
error response
{
"statusCode": 404,
"code": "NOT_FOUND",
"message": "Site not found"
}Common codes: UNAUTHORIZED, FORBIDDEN, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, PLAN_LIMIT.
/sites/sitesurl (full URL including scheme). Optional: label, scanEnabled.POST /sites — create
curl -X POST https://api.astrari.io/sites \
-H "Authorization: Bearer iw_live_…" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","label":"Marketing site"}'/sites/:id/sites/:idserverId./sites/:id/scan{ jobId } immediately; results land via subsequent GETs (or webhooks)./sites/:id/findings?status=OPEN to filter./sites/:id/servers/serversagentToken exactly once — needed for the install command./servers/:id/servers/:id/findings/servers/:id/scan{ "scanType": "full" | "trivy" | "clamav" | "rkhunter" | "maldet" | "fim" }./servers/:id/diagnose{ diagnosticId }./servers/:id/diagnostics/:diagId/servers/:id/findingsseverity = CRITICAL | HIGH | MEDIUM | LOW | INFOstatus = OPEN | RESOLVED | SUPPRESSEDcategory = e.g. WP_PLUGIN_CVE, SRV_PACKAGE_CVE (see /docs/faq for the list)siteId / serverIdlimit (max 200) / offsetGET /findings — open critical only
curl "https://api.astrari.io/findings?status=OPEN&severity=CRITICAL" \ -H "Authorization: Bearer iw_live_…"
/findings/:id/findings/:id{ "status": "RESOLVED" | "SUPPRESSED", "note": "…" }. Suppression survives rescans (re-detections roll up into the same suppressed finding)./reports/reportstype, title, periodStart, periodEnd, serverIds, siteIds./reports/:idstatus (PENDING / GENERATING / READY / FAILED)./reports/:id/fileAstrari can POST a JSON digest to a URL of your choosing whenever new findings appear on an asset. Configure webhooks in the dashboard at Settings → Alert integrations → Generic webhook and route them per-asset under each server / site's alert recipients.
Verifying signatures. If you set a webhook secret when creating the integration, every request includes an X-Astrari-Signature header containing sha256=<hex digest> of the raw request body, computed with HMAC-SHA-256 keyed on the secret.
example payload
{
"org": "Allen Digital Studio",
"asset": {
"type": "site",
"label": "shop.allendigital.example",
"subLabel": null,
"url": "https://app.astrari.io/sites/abc123"
},
"scan": { "id": "scan_xyz", "completedAt": "2026-05-03T10:14:22Z" },
"findings": [
{
"id": "find_456",
"severity": "HIGH",
"category": "WP_PLUGIN_CVE",
"title": "WooCommerce 8.4.0 — CVE-2024-XXXX",
"detail": "…",
"remediation": "Upgrade WooCommerce to 8.6.1 or later",
"url": "https://app.astrari.io/findings/find_456"
}
],
"sentAt": "2026-05-03T10:14:25Z"
}verify in Node.js
import { createHmac, timingSafeEqual } from "node:crypto";
function verifyAstrariWebhook(rawBody: Buffer, header: string, secret: string): boolean {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
// Both sides must be the same length for timingSafeEqual.
if (header.length !== expected.length) return false;
return timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}Astrari retries with exponential backoff (max 3 attempts) on 5xx responses or network errors. 4xx responses are treated as a permanent failure. Each finding fans out to each routed webhook at most once per occurrence — re-scans that re-see the same issue do not re-fire.
The API enforces 100 requests per minute, keyed on the first 20 characters of your API key (or remote IP for unauthenticated requests). Bursts above the limit return HTTP 429 with a RATE_LIMITED error code and a Retry-After header.
Request bodies are capped at 1 MB. Endpoints that accept lists (recipients, IDs) cap the array length per endpoint — you'll get a VALIDATION_ERROR if you exceed it.
The API is currently unversioned in the URL path. Backwards-incompatible changes will be communicated via the changelog at least 30 days before they ship; we'll publish a /v2 base URL when that becomes necessary so existing integrations continue to work.
Spotted something missing or unclear? Email [email protected] — we keep this page accurate.