# Machine-readable findings

> Source: https://www.allsweb.com/sixpreflight/docs/findings-api
> Markdown for agents: https://www.allsweb.com/sixpreflight/docs/findings-api.md
> Publisher: AllsWeb (www.allsweb.com)

Part of: SixPreflight documentation

If you already run a dashboard or a monitoring tool, SixPreflight can hand it the result of your last scan as plain JSON — no scraping the page, and no scan triggered just to check in.

## Why it never starts a scan itself

The endpoint always returns the **last saved** check. Nothing about calling it starts a new one.

A monitor that triggered a full scan on every dashboard refresh — one step of which fetches several URLs off your own live site — would be a load generator wearing a monitor's clothes, rather than something you would actually want polling every minute.

The response carries the age of that saved run in seconds, so your dashboard can say "this reading is two weeks old" instead of quietly presenting stale news as current.

## Authentication

The same password you sign in with, sent over HTTP Basic auth. Any username works — there is no separate credential to create or to leak.

```bash
curl -s -u "sixpreflight:YOUR-PASSWORD" \
  "https://your-domain/preflight/?api=findings"
```

This shares the same password hash and the same lockout counter as the sign-in page, so there is nothing extra to configure. No session is opened for this call, so polling it every minute leaves nothing behind on disk. A Basic-auth sign-in can read this one endpoint and nothing else on the tool. If you have set an IP allow-list in the config file, it still applies here too.

## The response shape

A version number identifies the shape of the response itself. Fields may be added at any time; a field is never renamed or removed without that number going up — so it is safe to key your integration off the presence of the result list and that version, rather than trying to detect shape changes some other way.

```json
{
  "contract": 1,
  "tool": { "name": "SixPreflight" },
  "generated_at": 1786726822,
  "embedded": true,
  "page": "/preflight/?page=fix",
  "run": {
    "id": "20260814-101500-a1b2c3",
    "finished_at": 1786438160,
    "age_seconds": 288662,
    "deep": false,
    "score": 34,
    "grade": "F"
  },
  "counts": { "critical": 10, "warning": 11, "ok": 54, "unknown": 8 },
  "checks": [
    {
      "id": "env_app_debug",
      "severity": "critical",
      "scope": "app",
      "title": "Debug mode",
      "setting": "APP_DEBUG",
      "why": "Debug mode is on, so any error page shows your database password to whoever triggered it.",
      "impact": "Serious risk",
      "now": "true",
      "want": "APP_DEBUG=false",
      "fix_target": ".env",
      "fix_owner": "operator",
      "url": "/preflight/?page=fix"
    }
  ]
}
```

| Field | Meaning |
|---|---|
| `contract` | Integer. The shape of this document. |
| `tool.name` | The product name, for display only — key your integration off `contract` and the presence of the result list, not this string. |
| `run` | `null` when no check has ever been run; a `note` field explains that case. |
| `run.age_seconds` | How old the answer is. Treat anything over a few days as stale. |
| Each result's `id` | A stable key for the check — safe to store and to deduplicate on across runs. |
| Each result's `severity` | `critical` or `warning` only. Rows that could not be measured show up in the unknown count, not in this list — a monitor that raised a ticket for every unreadable value on a locked-down host is a monitor people switch off. |
| Each result's `scope` | `app` — the shop, its settings, its data. `stack` — the server underneath it. |
| Each result's `impact` | What ignoring it costs, in plain words: `Stops orders`, `Serious risk`, `Slows the site`, `Worth fixing`, `Tidying`. |
| Each result's `fix_target` | Where it is put right — a file, or a place named in words. |
| Each result's `fix_owner` | `sixpanel` when SixPanel manages that file on this server, `operator` when it is yours to change. Always `operator` on a standalone install. |
| Each result's `url` | A deep link to the page that explains this specific check. |

Errors come back as an error object with a status code: 401 for a bad or missing password, 403 if Basic auth is used against a different endpoint, or 419 if the session has expired.

## Where to go next

- [Reading the report](https://www.allsweb.com/sixpreflight/docs/reading-the-report) — what each severity and impact label means when you are reading it as a person, not as JSON
- [Security and privacy](https://www.allsweb.com/sixpreflight/docs/security-and-privacy) — how the password and lockout counter behave
