ScanReportOut
What a scan endpoint actually returns: the report plus the A8 hand-off. Declared as a response model rather than by widening `ScanReport` itself: `ScanReport` round-trips through the persisted `report_json`, and a field there would be frozen into stored rows. This subclass is never handed to `save_report`, so the non-persistence property is untouched while `/api/v1/openapi.json` documents the real response shape — which the agent channel (`/llms-full.txt`, U7's api-catalog) depends on being accurate.
Properties
| Name | Type | Description |
|---|---|---|
| domain | string | |
| checks | array | |
| scanned_at | string | |
| explanations_pending | boolean | |
| dns_provider_key | object | |
| not_registered | boolean | |
| agent_scanned | boolean | |
| registration | object | |
| next_steps | object | |
| sender_requirements | object | |
| indexable | boolean | Thin-page shield: worth indexing only if mail-capable AND substantive. Mail-capable = any of mx/spf/dmarc observed a record; substantive = at least `_MIN_RESOLVED_CHECKS` checks resolved (non-TEMPERRO |
| dns_provider | object | The serialized assist-only NS provider, looked up by key at read time. Mirrors the domain-row `dns_provider` object: the key is stored, the full object (display name, console URL, guide anchor, collis |
JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://artifacts.apis.io/dnsdoctor-dev/json-schema/dnsdoctor-dev-scan-report-out-schema.json",
"title": "ScanReportOut",
"description": "What a scan endpoint actually returns: the report plus the A8 hand-off.\n\nDeclared as a response model rather than by widening `ScanReport` itself:\n`ScanReport` round-trips through the persisted `report_json`, and a field\nthere would be frozen into stored rows. This subclass is never handed to\n`save_report`, so the non-persistence property is untouched while\n`/api/v1/openapi.json` documents the real response shape — which the agent\nchannel (`/llms-full.txt`, U7's api-catalog) depends on being accurate.",
"x-generated": "2026-11-05",
"x-method": "derived",
"x-generator": "derive-json-schema.py",
"x-source": "openapi/dnsdoctor-dev-openapi.json#/components/schemas/ScanReportOut",
"properties": {
"domain": {
"type": "string",
"title": "Domain"
},
"checks": {
"items": {
"$ref": "#/$defs/CheckResult"
},
"type": "array",
"title": "Checks"
},
"scanned_at": {
"type": "string",
"format": "date-time",
"title": "Scanned At"
},
"explanations_pending": {
"type": "boolean",
"title": "Explanations Pending",
"default": false
},
"dns_provider_key": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Dns Provider Key"
},
"not_registered": {
"type": "boolean",
"title": "Not Registered",
"default": false
},
"agent_scanned": {
"type": "boolean",
"title": "Agent Scanned",
"default": false
},
"registration": {
"anyOf": [
{
"$ref": "#/$defs/Registration"
},
{
"type": "null"
}
]
},
"next_steps": {
"anyOf": [
{
"$ref": "#/$defs/NextSteps"
},
{
"type": "null"
}
]
},
"sender_requirements": {
"anyOf": [
{
"$ref": "#/$defs/SenderRequirements"
},
{
"type": "null"
}
]
},
"indexable": {
"type": "boolean",
"title": "Indexable",
"description": "Thin-page shield: worth indexing only if mail-capable AND substantive.\n\nMail-capable = any of mx/spf/dmarc observed a record; substantive = at\nleast `_MIN_RESOLVED_CHECKS` checks resolved (non-TEMPERROR). NXDOMAIN,\nparked, and garbage domains fail the first gate; half-broken scans fail\nthe second. Non-indexable reports stay fully viewable — they are only\nnoindex + sitemap-excluded. Serializes into `report_json`; older\npersisted JSON without the key still validates (computed on read).",
"readOnly": true
},
"dns_provider": {
"anyOf": [
{
"additionalProperties": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"type": "object"
},
{
"type": "null"
}
],
"title": "Dns Provider",
"description": "The serialized assist-only NS provider, looked up by key at read time.\n\nMirrors the domain-row `dns_provider` object: the key is stored, the full\nobject (display name, console URL, guide anchor, collision note) is\nresolved from the `DNS_PROVIDERS` table on serialize, so a URL/copy fix\nnever needs a re-scan. A key that no longer exists in the table (or None)\nserializes as None — the report page then omits the provider mention.",
"readOnly": true
}
},
"type": "object",
"required": [
"domain",
"checks",
"indexable",
"dns_provider"
],
"$defs": {
"CachedLookalikes": {
"properties": {
"summary": {
"type": "string",
"title": "Summary"
},
"resolving": {
"type": "integer",
"title": "Resolving"
},
"accepts_mail": {
"type": "integer",
"title": "Accepts Mail"
},
"checked_at": {
"type": "string",
"title": "Checked At"
}
},
"type": "object",
"required": [
"summary",
"resolving",
"accepts_mail",
"checked_at"
],
"title": "CachedLookalikes",
"description": "A still-fresh `check_lookalikes` result's counts — read from its cache, never probed."
},
"CheckResult": {
"properties": {
"check": {
"type": "string",
"title": "Check"
},
"status": {
"$ref": "#/$defs/CheckStatus"
},
"title": {
"type": "string",
"title": "Title"
},
"raw": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Raw"
},
"explanation": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Explanation"
},
"fix_record": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Fix Record"
},
"details": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Details"
},
"unknown": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Unknown"
},
"withheld_code": {
"anyOf": [
{
"type": "string",
"const": "hosted"
},
{
"type": "null"
}
],
"title": "Withheld Code"
}
},
"type": "object",
"required": [
"check",
"status",
"title"
],
"title": "CheckResult",
"description": "The outcome of one check (e.g. SPF, DMARC, expiry)."
},
"CheckStatus": {
"type": "string",
"enum": [
"pass",
"warn",
"fail",
"info",
"temperror"
],
"title": "CheckStatus"
},
"LookalikesPointer": {
"properties": {
"tool": {
"type": "string",
"const": "check_lookalikes",
"title": "Tool",
"default": "check_lookalikes"
},
"url": {
"type": "string",
"title": "Url"
},
"cached": {
"anyOf": [
{
"$ref": "#/$defs/CachedLookalikes"
},
{
"type": "null"
}
]
}
},
"type": "object",
"required": [
"url"
],
"title": "LookalikesPointer",
"description": "D130: the scan's pointer to the separate `check_lookalikes` call.\n\nFilled by `api/lookalike_pointer.py` on the AGENT surfaces only (MCP\n`scan_domain`/`get_report`, v1 `/report` + `/scan`). The web `GET /report`\nthat feeds the indexable `/scan/[domain]` page never fills it (the D128\nno-teaser rule), so it serializes there as null."
},
"NextSteps": {
"properties": {
"summary": {
"type": "string",
"title": "Summary"
},
"report_url": {
"type": "string",
"title": "Report Url"
},
"monitor_url": {
"type": "string",
"title": "Monitor Url"
},
"signup_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Signup Url"
},
"email_test_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Email Test Url"
},
"lookalikes": {
"anyOf": [
{
"$ref": "#/$defs/LookalikesPointer"
},
{
"type": "null"
}
]
}
},
"type": "object",
"required": [
"summary",
"report_url",
"monitor_url"
],
"title": "NextSteps",
"description": "The hand-off block's wire shape (documentation only — see `build_next_steps`)."
},
"Provider": {
"properties": {
"key": {
"type": "string",
"title": "Key"
},
"name": {
"type": "string",
"title": "Name"
},
"source_url": {
"type": "string",
"title": "Source Url"
},
"bulk_threshold": {
"type": "string",
"title": "Bulk Threshold"
}
},
"type": "object",
"required": [
"key",
"name",
"source_url",
"bulk_threshold"
],
"title": "Provider"
},
"Registration": {
"properties": {
"registrar": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Registrar"
},
"registrar_iana_id": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Registrar Iana Id"
},
"registered_at": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
],
"title": "Registered At"
},
"updated_at": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
],
"title": "Updated At"
},
"expires_at": {
"anyOf": [
{
"type": "string",
"format": "date-time"
},
{
"type": "null"
}
],
"title": "Expires At"
},
"status": {
"items": {
"type": "string"
},
"type": "array",
"title": "Status",
"default": []
},
"nameservers": {
"items": {
"type": "string"
},
"type": "array",
"title": "Nameservers",
"default": []
},
"dnssec": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "null"
}
],
"title": "Dnssec"
},
"abuse_email": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Abuse Email"
},
"redacted": {
"type": "boolean",
"title": "Redacted",
"default": false
}
},
"type": "object",
"title": "Registration",
"description": "What a registry publishes about one domain. Observation only.\n\nEvery field is optional because every field is redactable: a registry that\npublishes only status codes and nameservers is the normal post-GDPR case,\nnot a failure. `status` carries the registry's own strings verbatim (RFC\n9083 spells them `client transfer prohibited`; some registries still emit\nEPP camelCase) — normalizing them would invent a vocabulary neither side\nuses."
},
"RequirementItem": {
"properties": {
"key": {
"type": "string",
"title": "Key"
},
"label": {
"type": "string",
"title": "Label"
},
"providers": {
"additionalProperties": {
"type": "string",
"enum": [
"all",
"bulk"
]
},
"type": "object",
"title": "Providers"
},
"status": {
"type": "string",
"enum": [
"pass",
"fail",
"unknown",
"email_test",
"not_visible"
],
"title": "Status"
},
"evidence": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Evidence"
},
"provider_status": {
"anyOf": [
{
"additionalProperties": {
"type": "string",
"enum": [
"pass",
"fail",
"unknown",
"email_test",
"not_visible"
]
},
"type": "object"
},
{
"type": "null"
}
],
"title": "Provider Status"
}
},
"type": "object",
"required": [
"key",
"label",
"providers",
"status"
],
"title": "RequirementItem"
},
"SenderRequirements": {
"properties": {
"note": {
"type": "string",
"title": "Note"
},
"providers": {
"items": {
"$ref": "#/$defs/Provider"
},
"type": "array",
"title": "Providers"
},
"items": {
"items": {
"$ref": "#/$defs/RequirementItem"
},
"type": "array",
"title": "Items"
}
},
"type": "object",
"required": [
"note",
"providers",
"items"
],
"title": "SenderRequirements"
}
}
}
Work with this as data
Every JSON Schema here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for schemas
4 MCP tools reach this
find_json_schemasBrowse and filter every JSON Schema in the catalog.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.
Call it yourself
curl for this page
curl "https://apis.io/api/v1/json-schemas/dnsdoctor-dev-scan-report-out"
curl "https://apis.io/api/v1/json-schemas?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.