docs · SaaS public API · v1

REST API

Start and stop scans, track their status, and export findings from your own code.

Base URL: https://www.xalgorix.com/api/public/v1

This reference covers the SaaS customer API. The self-hosted engine has a separate local REST API; its routes and credentials do not apply here.

Authentication and scope

Create a personal key on the API keys page. Its secret starts with xlg_live_, followed by 48 hexadecimal characters, and is shown only once. Send it in the Authorization header:

Authorization: Bearer <your-api-key>
Content-Type: application/json

Keys act as the user who created them, inherit that user's personal plan and credit balance, and access only scans and findings owned by that user. Organization membership does not grant access to another member's scans through a personal key. Starting scans through v1 uses your personal workspace.

There are no per-key read/write scopes. Protect each key as an account credential, keep it on your server or in CI secrets, and revoke it on the same page when compromised. Do not include keys in URLs, browser bundles or source control. These routes require a Bearer key; a signed-in browser cookie is insufficient.

Authenticated responses use private, no-store caching. Browser preflights are supported for scans, stopping and findings. Shared-report tokens and public Trust Page badges use their own access rules, described below.

Endpoints

POST/api/public/v1/scansBearer key required

Start a scan in your personal workspace. Returns running or queued; a matching active target may reuse its existing scan ID.

JSON request body

{
  "target": "https://example.com",
  "mode": "single",
  "severity_filter": "low",
  "phases": [1, 2, 3, 4, 5, 6, 7, 8]
}

Request fields and behavior

target: required string, 3–2048 characters; a bare host defaults to HTTPS. mode: single (default) or wildcard. severity_filter: info | low (default) | medium | high | critical; minimum reported severity. phases: optional array of 1–22 integer phase IDs, each 1–22; omitted = all 22 phases. This endpoint uses active reconnaissance and testing. It does not accept organization_id, target authentication, files, or other web-form settings.

Response

202 Accepted
{
  "id": "0f5d31b5-8c0a-4a66-9b29-5e23e6bfc9b1",
  "target": "https://example.com",
  "status": "queued",
  "status_url": "https://www.xalgorix.com/api/public/v1/scans?id=0f5d31b5-8c0a-4a66-9b29-5e23e6bfc9b1",
  "stop_url": "https://www.xalgorix.com/api/public/v1/scans/stop",
  "web_url": "https://www.xalgorix.com/scans/0f5d31b5-8c0a-4a66-9b29-5e23e6bfc9b1"
}
GET/api/public/v1/scansBearer key required

Read an owned scan's persisted status, assessment metadata and exact per-severity finding counts. Status updates may lag ongoing work briefly.

Query parameters and access

id: required scan UUID (the database ID returned by POST /scans).

Response

200 OK
{
  "scan": {
    "id": "0f5d31b5-8c0a-4a66-9b29-5e23e6bfc9b1",
    "target": "https://example.com",
    "mode": "single",
    "status": "running",
    "current_phase": 6,
    "started_at": "2026-10-02T10:00:00.000Z",
    "completed_at": null,
    "engine_completion": null,
    "engine_stop_reason": null,
    "plan_tasks_total": null,
    "plan_tasks_completed": null,
    "plan_tasks_skipped": null,
    "plan_tasks_unfinished": null
  },
  "counts": { "critical": 0, "high": 1, "medium": 2, "low": 4, "info": 0 }
}
POST/api/public/v1/scans/stopBearer key required

Stop an owned queued, running or paused scan while retaining saved findings and report data. No additional fields are accepted.

JSON request body

{
  "id": "0f5d31b5-8c0a-4a66-9b29-5e23e6bfc9b1"
}

Request fields and behavior

id: required scan UUID. Repeating a stop returns 200 with already_terminal: true. Already completed or failed scans return their existing status; no work or credits are changed. A concurrent state change can return 409 state_changed; poll status, then retry. 503 stop_unconfirmed means success was not verified; poll status and retry shortly.

Response

200 OK
{
  "id": "0f5d31b5-8c0a-4a66-9b29-5e23e6bfc9b1",
  "ok": true,
  "state_changed": false,
  "already_terminal": false,
  "status": "stopped"
}
GET/api/public/v1/findingsBearer key required

Read findings from one owned scan or across the key owner's scans. Evidence is structured report data; handle it as sensitive information.

Query parameters and access

All query parameters are optional: scan_id: scan UUID; must belong to the key owner. severity: exact severity filter (not a minimum). limit: integer 1–500, default 100. offset: integer ≥ 0, default 0. order: newest (default), oldest, or severity (critical first, then newest). Sorting happens before pagination; ties use finding ID. total is the filtered count across all pages. Increase offset by the number of returned rows until offset reaches total. Live results may change between pages.

Response

200 OK
{
  "findings": [{
    "id": "4e899609-96f0-48b0-a89c-6a041a9ae406",
    "scan_id": "0f5d31b5-8c0a-4a66-9b29-5e23e6bfc9b1",
    "severity": "high",
    "title": "Verified access-control weakness",
    "description": "...",
    "category": "Authentication",
    "cve": null,
    "cvss": 8.1,
    "phase": 5,
    "scan_target": "https://example.com",
    "scan_mode": "single",
    "evidence": { "poc": "...", "request": "...", "response": "..." },
    "remediation": "...",
    "created_at": "2026-10-02T10:05:00.000Z"
  }],
  "total": 7,
  "limit": 100,
  "offset": 0,
  "scan_id": "0f5d31b5-8c0a-4a66-9b29-5e23e6bfc9b1",
  "severity": null,
  "order": "newest"
}
GET/api/public/v1/shareNo API key

Read an existing shared report. No API key is required. Password-protected shares require the x-share-password header; never put the password in the URL.

Query parameters and access

token: required shr_ followed by 48 hexadecimal characters. x-share-password: request header required when the report is password protected. 401 password_required / invalid_password; 429 too_many_attempts with Retry-After. 404 for an unknown, revoked, expired or missing report. 400 for malformed tokens. This is a read endpoint; create/revoke shares in the web app.

Response

200 OK
{ scan, findings, counts, branding, canCustomizeBranding, customer? }

findings contains up to 1000 report rows; counts cover those returned rows.
Use authenticated GET /findings for full paginated exports.
Password-protected responses: Cache-Control: private, no-store.
Unprotected responses may be cached for 60 seconds.
GET/api/public/v1/badgeNo API key

Read an SVG badge for a public Trust Page. This endpoint returns an image, not JSON, and requires no API key.

Query parameters and access

slug: required public Trust Page slug. metric: status (default) | findings | severity | lastscan. label: optional left-hand label, default xalgorix.

Response

200 OK — image/svg+xml, normally cached for 300 seconds.
Unknown/unpublished slugs return an SVG fallback; malformed, throttled or unavailable requests use a 60-second fallback. No private scan data is exposed.

Quick start

Set XALGORIX_API_KEY securely in your shell environment. Start a scan only against a target you own or are authorized to test:

curl --fail-with-body --silent --show-error \
  -X POST https://www.xalgorix.com/api/public/v1/scans \
  -H "Authorization: Bearer $XALGORIX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"target":"https://example.com","mode":"single"}'

Save the returned id as SCAN_ID. Poll its status; a 202 start response means accepted, not completed:

curl --fail-with-body --silent --show-error \
  "https://www.xalgorix.com/api/public/v1/scans?id=$SCAN_ID" \
  -H "Authorization: Bearer $XALGORIX_API_KEY"

Stop that scan with the same user's key:

jq -n --arg id "$SCAN_ID" '{id:$id}' | \
  curl --fail-with-body --silent --show-error \
    -X POST https://www.xalgorix.com/api/public/v1/scans/stop \
    -H "Authorization: Bearer $XALGORIX_API_KEY" \
    -H "Content-Type: application/json" --data-binary @-

Read findings, including those retained after a stop. Use limit and offset for additional pages:

curl --fail-with-body --silent --show-error \
  "https://www.xalgorix.com/api/public/v1/findings?scan_id=$SCAN_ID&limit=100&offset=0&order=severity" \
  -H "Authorization: Bearer $XALGORIX_API_KEY"

Scan lifecycle and stopping

Live states are queued, running and paused. Terminal states are completed, failed and stopped. Legacy records can report pending (waiting) or cancelled (terminal). Pending records can be stopped using the same endpoint. A queued scan waits for capacity; paused is resumable and is not a successful completion. Poll every 10–15 seconds, with backoff on temporary failures.

The status field describes the lifecycle. It does not certify testing coverage. engine_completion and engine_stop_reason carry the saved assessment outcome; either may be null until available. The four plan_tasks_* fields are nullable task counters. Completed, skipped and unfinished tasks add up to the total when recorded; skipped tasks were not executed. Integrations should examine assessment metadata before treating a terminal scan as a fully finished assessment.

Stopping preserves saved findings, chats and reports. A cancellation before dispatch refunds its upfront reservation. Dispatched work remains billable; wildcard cancellation reconciles dispatched subdomains and its reservation. Stopping does not promise a refund for work already performed. Credit reconciliation can finish separately from the stop response.

A confirmed stop returns status: stopped. Repeating it is safe. A scan that completed or failed before your request returns its existing terminal status and already_terminal: true. state_changed: true on a successful response means a concurrent request made it terminal. A 409 means it remains nonterminal or changed execution ownership; refresh before retrying. A 503 stop_unconfirmed must not be treated as success.

Rate limits and retries

Start, status, findings and stop each have a separate per-key request budget. Stop requests remain available when a start or polling budget is exhausted. The burst capacity and refill per minute are 30 for Hacker or credits-only accounts, 90 for Pro, and 300 for Max (Team). These are request throttles, not additional scan or credit allowances.

A rate-limited response includes retry_after_ms and a Retry-After header in seconds. Honor the header and add backoff with jitter. The throttle is best effort; do not rely on a fixed number of requests being accepted. Running and queued capacity limits are separate and can also return 429 concurrency_limit.

429 Too Many Requests
Retry-After: 2
{"error":"rate_limited","retry_after_ms":2000}

GET requests and stop requests can be retried. Scan creation has no general idempotency-key support. Matching running/queued targets can reuse an ID, but that is not a guarantee across concurrent requests or target variants. Retain accepted IDs and avoid blindly retrying a start after a lost response.

Completion webhooks

Register an HTTPS endpoint on the Webhooks page. Completion synchronization delivers a signed scan.completed event. This does not guarantee full assessment coverage, and there is no scan.stopped webhook; use the stop response and status polling for cancellation.

X-Xalgorix-Event: scan.completed
X-Xalgorix-Timestamp: 1790928000
X-Xalgorix-Signature: sha256=<64 hexadecimal characters>

{
  "event": "scan.completed",
  "scan_id": "0f5d31b5-8c0a-4a66-9b29-5e23e6bfc9b1",
  "target": "https://example.com",
  "status": "completed",
  "counts": {"critical":0,"high":1,"medium":0,"low":0,"info":0},
  "total_findings": 1,
  "completed_at": "2026-10-02T10:15:00.000Z",
  "url": "https://www.xalgorix.com/scans/0f5d31b5-8c0a-4a66-9b29-5e23e6bfc9b1"
}

Verify the signature over the timestamp, a dot, and the exact raw request body before parsing JSON. Reject malformed signatures and timestamps outside a five-minute window; compare equal-length bytes in constant time:

import { createHmac, timingSafeEqual } from "node:crypto";

function verifyWebhook(rawBody, headers, secret) {
  const ts = headers["x-xalgorix-timestamp"];
  const sig = headers["x-xalgorix-signature"];
  if (typeof ts !== "string" || !/^\d{1,11}$/.test(ts)) return false;
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  if (typeof sig !== "string" || !/^sha256=[a-f0-9]{64}$/i.test(sig)) return false;
  const expected = createHmac("sha256", secret)
    .update(ts + ".")
    .update(rawBody)
    .digest();
  const received = Buffer.from(sig.slice(7), "hex");
  return received.length === expected.length && timingSafeEqual(received, expected);
}

Keep your endpoint signing secret private. Make processing idempotent by event and scan ID, handle retries safely, and use status polling for recovery if a delivery fails. The web app's Send test button and delivery log help verify your integration.

CI integration

Use the curl requests above from any CI runner. Store XALGORIX_API_KEY as a masked secret and pass a publicly reachable deployed test URL. Skip forked pull requests that cannot access secrets. Keep scan IDs as job outputs, poll until a terminal state, and inspect findings at your chosen severity threshold.

A cancelled or timed-out CI job does not automatically stop its scan. Add a cleanup step that calls POST /scans/stop with the saved ID, and verify its result. Only a 200 response with a terminal status confirms that request's outcome. In GitHub Actions, run cleanup with always() and a nonempty scan ID; skip it after successful scan completion.

Scanning uses your account's normal credits. Never scan a fork's untrusted URL automatically with your key. Review the deployed target and authorization before enabling scans on pull requests.

Errors

Authenticated routes return JSON with an error code, and sometimes a safe detail or retry_after_ms. Check HTTP status before treating a response as successful. Shared-report password errors and SVG badge fallbacks are documented with their endpoints.

HTTPError codeMeaning and recovery
400invalid_body / missing_id / invalid_id / invalid_queryMalformed JSON, missing or invalid scan UUID, or unsupported query value.
401unauthorizedMissing, invalid or revoked personal API key. Cookies alone do not authorize these endpoints.
402insufficient_creditsNot enough personal credits to start the scan.
403plan_forbiddenThe requested scan mode is unavailable to the account.
404not_found / scan_not_foundThe scan does not exist or belongs to another user. Both return the same response.
409state_changedScan state or execution ownership changed during the stop. Poll status, then retry.
422target_unreachableTarget preflight could not reach the requested site.
429rate_limited / concurrency_limitRequest budget exhausted, or the running and queued scan allowance is full. Honor Retry-After when present.
502scan_create_failedScan creation failed. Do not retry starts blindly; the outcome can be uncertain.
503stop_unconfirmedThe stop could not be verified. This is not a successful stop; poll status and retry shortly.
503authentication_unavailable / lookup_failed / service_unavailable / scans_pausedA dependency is unavailable, or new scans are temporarily paused. Retry with backoff.