The scan API

One endpoint. No account, no key, no signup. Send a URL, get back the same JSON security report the website produces. It is read-only and free, because understanding your own app is never the thing we gate.

Endpoint

POST https://xlogs.com/api/v1/scan   { "url": "<your-url>" }GET  https://xlogs.com/api/v1/scan?url=<your-url>   with the header  x-xlogs-scan: 1

Both forms run the same scan. A GET runs only when it carries the header x-xlogs-scan: 1; without it the response is a 400 that says so and nothing is scanned. That header is the whole difference between a request you sent and a link somebody pasted: a chat preview, a crawler or an image tag cannot add it, so no one can make xlogs scan a site by posting a URL somewhere. The same rule holds everywhere on this site: a GET never scans on its own.

Example

curl -X POST https://xlogs.com/api/v1/scan -H "content-type: application/json" -d '{"url":"https://your-app.com"}'

curl -H "x-xlogs-scan: 1" "https://xlogs.com/api/v1/scan?url=https://your-app.com"

Returns a JSON object with the scan result: findings (each with severity, a confidence label, CWE and OWASP tags, and a copy-paste fix), a coverage receipt, a plain verdict, the platform fingerprint, the shipped-library list, and the measured scan time. It is the exact shape the website renders.

Response shape

{
  "ok": true,
  "apiVersion": "1",
  "scannedAt": "2026-08-13T00:00:00.000Z",
  "url": "https://your-app.com",
  "reachable": true,
  "counts": { "critical": 0, "high": 0, "medium": 1, "low": 0 },
  "verdict": { "level": "issues", "headline": "1 table anyone can read: review", "review": 1 },
  "findings": [
    {
      "vulnId": "supabase-rls",
      "severity": "medium",
      "confidence": "confirmed",
      "observationConfidence": "confirmed",
      "interpretationConfidence": "potential",
      "interpretationBasis": "intent-unknown",
      "reviewPriority": "elevated",
      "reviewReasons": ["email is named like private personal data", "the values were not inspected"],
      "cwe": "CWE-284",
      "owasp": "A01:2021 Broken Access Control",
      "title": "Anyone can read this table; confirm that is deliberate",
      "statements": { "observed": "...", "notTested": "Not tested: ...", "recommended": "..." },
      "fixSteps": ["..."],
      "verify": "..."
    }
  ],
  "receipt": [ { "id": "database", "status": "found" } ],
  "sbom": { "libraries": [ { "name": "react", "version": "18.2.0" } ] }
}

Rules

  • Free and unauthenticated. No key, no account. Every field is returned; nothing is gated.
  • Read-only. The API runs the same checks as the site: ordinary GET requests, no writes, no login, no exploitation.
  • Public URLs only. Private, loopback, and cloud-metadata addresses are refused, the same as the website scanner.
  • Rate limited to 60 scans per IP per hour, so it cannot be used as a scanning amplifier. Over that returns HTTP 429.
  • CORS open. You can call it from a browser tool or a dashboard.
  • Not a compliance product. The CWE and OWASP tags are for matching against other tools, not evidence for SOC 2 or PCI.
  • Two confidences, from rules 1.9. observationConfidence says how sure the scan is of what it saw; interpretationConfidence says how sure it is of what that means, with interpretationBasis naming what it rests on. A table anyone can read is a review item: severity medium, basis intent-unknown, and a reviewPriority with its reasons. verdict.level keeps its values (a result with review items reads issues), and verdict.review counts the review items beside it.

Prefer the command line?

The live-site checks also run from a zero-dependency CLI you can drop into CI, with a deploy gate and SARIF output. The command line tool is released on its own: the release these pages run is v0.2.4 (2026-10-06), and the copy on npm, xlogs-scanner 0.1.0 (2026-08-13), is older still, so either can report differently from a scan on this site. See the methodology for the full list of requests either path sends.