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_…"
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).

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