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). Without scanId, URL-encoded full URLs are normalized to their hostname. With scanId, use the exact scanned target; MCP endpoints retain their full URL, including a meaningful trailing slash.
scanId
integer
query
Read the exact managed scan returned by a scan response. Keep the original target and scanId when polling after a release or partner assignment changes. Only published standard or preparation scans are readable; previews are hidden. A missing or mismatched ID never falls back to the current scan.
scoringVersion
integer
query
Optional. The published scoring version to use for this request. Omitted, the request uses the version pinned to your API key, else the public version. An unpublished or unknown version answers 400 `scoring_version_unavailable` listing the published versions; a value that is not a positive integer answers 400 `invalid_scoring_version`. Until release management is active, a request can name 3 (the same opt-in as the older `rankerVersion: 3`) or the version it would run anyway; any other version answers 400. A version other than the one a domain was last scanned under has no stored result yet, so the first scan of a domain under it runs live.
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 `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: the audit schemas declare `siteType` only as a loose object, so its inner fields may change without a version bump, and pinning the contract major does not freeze them. `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
400The scanId is not a positive integer.
404Without scanId, no cached score for this domain. With scanId, code scan_not_found means the target/ID is missing or not public; scan_result_pending means that exact scan has not written a result yet. Neither falls back to the current row. The domain miss body includes `code: "DOMAIN_NOT_SCANNED"` and a `next_action` pointing at `POST /api/scan` (`nextAction`, camelCase, under `?format=audit`).
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
503The partner scoring assignment or deployed scoring rules are 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 catalog follows the public scoring version or the partner API key's assigned version; scoringVersion and rulesUrl identify its rules. 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 those fields are omitted. Sends Access-Control-Allow-Origin: *. Keyed or managed-release responses use private, no-store; anonymous legacy responses are 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).
scoringVersion
integer
query
Optional. The published scoring version to use for this request. Omitted, the request uses the version pinned to your API key, else the public version. An unpublished or unknown version answers 400 `scoring_version_unavailable` listing the published versions; a value that is not a positive integer answers 400 `invalid_scoring_version`. Until release management is active, a request can name 3 (the same opt-in as the older `rankerVersion: 3`) or the version it would run anyway; any other version answers 400. A version other than the one a domain was last scanned under has no stored result yet, so the first scan of a domain under it runs live.
RESPONSES
200The complete check catalog -> CheckCatalog
429RATE_LIMIT_EXCEEDED - max 60 requests per minute per IP. -> ArdErrorResponse