REST API

API reference

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).

Overview

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.

Authentication

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.

Conventions

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

GET/sites
List all sites visible to the API key.
POST/sites
Add a new site. Required: url (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"}'
GET/sites/:id
Site detail: score, scan history, current findings counts, linked server.
PATCH/sites/:id
Update label, scanning enabled flag, or linked serverId.
POST/sites/:id/scan
Queue an on-demand scan. Returns { jobId } immediately; results land via subsequent GETs (or webhooks).
GET/sites/:id/findings
All findings for this site. Add ?status=OPEN to filter.
DELETE/sites/:id
Remove the site. Findings and scan history are deleted with it.

Servers

GET/servers
List servers visible to the API key.
POST/servers
Provision a new server entry. Returns agentToken exactly once — needed for the install command.
GET/servers/:id
Server detail: score, agent online status, scan tool status.
GET/servers/:id/findings
Findings for this server.
POST/servers/:id/scan
Queue an on-demand scan. Body: { "scanType": "full" | "trivy" | "clamav" | "rkhunter" | "maldet" | "fim" }.
POST/servers/:id/diagnose
Queue a forensic diagnostic snapshot. Returns { diagnosticId }.
GET/servers/:id/diagnostics/:diagId
The completed diagnostic — boot history, OOMs, failed units, scoped log excerpts.
DELETE/servers/:id
Remove the server entry. The agent on the server keeps running but its check-ins will be rejected.

Findings

GET/findings
List findings across all assets. Query parameters:
  • · severity = CRITICAL | HIGH | MEDIUM | LOW | INFO
  • · status = OPEN | RESOLVED | SUPPRESSED
  • · category = e.g. WP_PLUGIN_CVE, SRV_PACKAGE_CVE (see /docs/faq for the list)
  • · siteId / serverId
  • · limit (max 200) / offset

GET /findings — open critical only

curl "https://api.astrari.io/findings?status=OPEN&severity=CRITICAL" \
  -H "Authorization: Bearer iw_live_…"

Where vendor matching is enabled, package findings matched against vendor advisories also carry vendorPublishedAt (ISO 8601 timestamp or null: the earliest distribution publication date among the vendor advisories still unfixed for that package on that server), upstreamPublishedAt (ISO 8601 timestamp or null: Red Hat's original date where an AlmaLinux or Rocky Linux advisory cites a Red Hat advisory), vendorAdvisoryIds (array of Astrari advisory record ids, not the public ALSA-/USN- ids; the readable advisory names are in the finding detail) and inventoryAt (ISO 8601 timestamp or null: the package list the match used). They are null or empty on other findings.

GET/findings/:id
Full finding detail including remediation steps and the underlying scan evidence.
PATCH/findings/:id
Update finding status. Body: { "status": "RESOLVED" | "SUPPRESSED", "note": "…" }. Suppression survives rescans (re-detections roll up into the same suppressed finding).

Reports

GET/reports
List generated reports.
POST/reports
Generate a new report. Body specifies type, title, periodStart, periodEnd, serverIds, siteIds.
GET/reports/:id
Report metadata, including status (PENDING / GENERATING / READY / FAILED).
GET/reports/:id/file
Download the rendered PDF (when status is READY).

Audit feeds

Read-only feeds for copying evidence into your own audit archive, for example with a nightly job. An organisation key sees the whole organisation; a client key sees only that client's servers and that client's log review records. Vault feeds return oldest first, up to limit rows (default 500, max 1000); pass the returned nextCursor as cursor for the next page, and since (ISO date) to start from a point in time. These are the only Vault and log review routes an API key can call.

GET/vault/audit/attestations
Integrity checks and restore tests: kind, depth, status, timestamps, snapshots checked, error and result detail, server and storage provider.
GET/vault/audit/snapshots
Every backup run, files and each database: restic snapshot id, scope, status, timestamps, bytes and files added, error.
GET/log-review/signoffs
Weekly review periods with their sign-offs (who signed, when, and what was reviewed).
GET/log-review/evidence
Monthly log review evidence packs and their status.
GET/log-review/evidence/:id/csv
Download one evidence pack as CSV (when status is READY).
GET/compliance/exports
Compliance export packs; download with /compliance/exports/:id/download.

GET /vault/audit/attestations — first page from 1 October

curl "https://api.astrari.io/vault/audit/attestations?since=2026-10-01&limit=500" \
  -H "Authorization: Bearer iw_live_…"

Webhooks (outbound)

Astrari 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.

Rate limits

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.

Versioning

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.

API reference — servers, sites, findings and scans | Astrari