Scores & checks

Read cached scores, embed badges, and browse the full check catalog with weights and applicability.

GET/api/score/{domain}

Get cached score for a domain

Returns the most recent cached scan result for the given domain. Read-only: never triggers a scan. On miss (404) or when the previous scan got stuck mid-flight (200 with `analysisStatus: "stuck"`), the response carries a structured `next_action` envelope pointing at `POST /api/scan` so agent callers have a machine-parseable next step. Successful responses are cached for 1 hour; stuck, 404, and ephemeral (disposable, `urlKind: "ephemeral"`) responses are uncached (`Cache-Control: no-store`) so a successful re-scan is observable immediately and a deleted disposable row is never served from cache. Rate limited to 10 requests per minute per IP - returns 429 if exceeded. A scan API key exempts the caller, since this is the poll target for keyed scanning.

PARAMETERS
domain
string
path
required
The domain to look up (e.g. stripe.com). URL-encoded full URLs are normalized to their hostname.
competitors
string
query
Pass 1 to include a `competitors` object in the response: the category top-5 leaders plus the neighbor window (2 above / self / 2 below) drawn from the leaderboard. Returned only for domains with a market category - unclassified domains (Community, or no leaderboard row) get `competitors: null`. Omitted by default so the plain response stays lean.
format
string
query
Pass `audit` to receive the versioned, allowlisted audit shape (AuditScanResult / AuditScoreResult) instead of the default body: every field is documented, carries a `contractVersion`, and internal fields are dropped. Omit it and the response is unchanged from previous releases. On GET /api/scan/stream, only the terminal `scan_complete` event's `result` is projected - all other events are identical to the default stream.
include
string
query
Opt-in expansion list (comma-separated). `essentials` adds one response key, `essentials`: an alternate reading of the same scan carrying its own `score` (0-100 or null - required checks share 80 points, recommended 20, forward-looking signals upside-only), the required/recommended buckets, label copy, per-surface sub-scores, access signals, and a `checks` map keyed by check id. That map holds the essentials INTERPRETATION only (tier, bonus, fraction, occurrences, `essentialsGain`) - name, status, details, ora's recommendation, and `estScoreGain` for the same id stay in `layers[].checks[]`, so nothing serializes twice; join on the id. The pre-sorted `issues` list and `scoreEvidence` are arrays of ids resolving in that map. Omit the parameter and the response is byte-identical to previous releases. On GET /api/checks, `essentials` instead adds `essentialsTier`, `essentialsBonusOnly`, and `essentialsExcluded` to every catalog check (an excluded check never enters the essentials model; since 1.19.1 that is the two robots.txt policy checks). Experimental: `siteType` sits outside the versioned contract, so its shape may change without a version bump, and pinning the contract major does not freeze it. `siteType` adds one response key, `siteType`: the same scan scored against what the site is (`content`, `business`, `app`, or `store`). It carries `type`, `source` (`declared`, `self-declared`, `classified`, `default`, `blocked`, or `unavailable`), `confidence` (`high` or `low`), `basis`, `score` (0-100 or null), per-layer `layers` (`id`, `score`, `maxScore`), per-check `states` (`req`, `bonus`, `gated`, or `emerging`), and `gatedOut` (each excluded check id with the gate that excluded it). A site-type score uses a different rubric from the canonical `score` and can land above or below it: never compare the two, only another `siteType.score` of the same `type`. When no type resolves, the key is `{ type: null, source: "unavailable", confidence: "low", score: null, layers: [] }` with a `basis` saying why.
siteType
string
query
Read-time lens for `include=siteType`: scores the scan against this type instead of the resolved one, without rescanning or storing anything. An unrecognized value is ignored, not rejected.
RESPONSES
200Cached scan result. With `?competitors=1`, also carries a `competitors` object (category leaders + neighbor window drawn from the leaderboard). When `analysisStatus` is `"stuck"`, the body also includes a `next_action` envelope. With `?format=audit` the body is `#/components/schemas/AuditScoreResult` and the recovery envelope is the camelCase `nextAction`. -> ScanResult
404No cached score for this domain. Body includes `code: "DOMAIN_NOT_SCANNED"` and a `next_action` pointing at `POST /api/scan` (`nextAction`, camelCase, under `?format=audit`). -> NotScannedResponse
422MCP authentication is required. No score or grade was produced. The failed attempt does not overwrite a previous measured scan. -> McpAuthRequiredResponse
429Rate limit exceeded - max 10 requests per minute per IP. The response carries a Retry-After header with the seconds until the oldest request in the window ages out. -> ErrorResponse
500Database unavailable
GET/api/badge/{domain}

Get SVG badge for a domain

Returns an SVG badge showing the domain's ora score and grade. Embed in READMEs or websites. Cached for 1 hour.

PARAMETERS
domain
string
path
required
The domain to get a badge for
RESPONSES
200SVG badge image
404No score found for this domain
GET/api/checks

Get the complete catalog of scanner checks

Returns every check the ora scanner can run - stable id, scored layer, max score, applicability, eligible scan kinds, tier, maturity, and fix guidance per check, plus the four scored layers with their weights. Check ids are stable: gate CI on an explicit id list, not on tiers (the required set can grow on a minor version). Ids are also what POST /api/scan/checks takes, and every check carries a `beta` boolean for building check pickers - a beta check runs but cannot affect any score. The document is static and byte-stable between check-set changes, so diffing it detects catalog updates. One optional parameter: `?include=essentials` adds `essentialsTier`, `essentialsBonusOnly`, and `essentialsExcluded` (the essentials-model classification; excluded checks are ignored by that model outright) to every check; without it the body is byte-identical to previous releases. Sends Access-Control-Allow-Origin: * and is CDN-cached for 1 hour. Rate limited to 60 requests per minute per IP - returns 429 with a Retry-After header if exceeded.

PARAMETERS
include
string
query
Opt-in expansion list (comma-separated). `essentials` adds one response key, `essentials`: an alternate reading of the same scan carrying its own `score` (0-100 or null - required checks share 80 points, recommended 20, forward-looking signals upside-only), the required/recommended buckets, label copy, per-surface sub-scores, access signals, and a `checks` map keyed by check id. That map holds the essentials INTERPRETATION only (tier, bonus, fraction, occurrences, `essentialsGain`) - name, status, details, ora's recommendation, and `estScoreGain` for the same id stay in `layers[].checks[]`, so nothing serializes twice; join on the id. The pre-sorted `issues` list and `scoreEvidence` are arrays of ids resolving in that map. Omit the parameter and the response is byte-identical to previous releases. On GET /api/checks, `essentials` instead adds `essentialsTier`, `essentialsBonusOnly`, and `essentialsExcluded` to every catalog check (an excluded check never enters the essentials model; since 1.19.1 that is the two robots.txt policy checks).
RESPONSES
200The complete check catalog -> CheckCatalog
429RATE_LIMIT_EXCEEDED - max 60 requests per minute per IP. -> ArdErrorResponse