ReadinessOut
The serialized `ReadinessVerdict` — `next_record` only when ready. D125/D130: `record_withheld` marks a ready `p=reject` step whose record the plan does not include; `withheld_reason` (`"plan"`) and `pricing_url` (a `/pricing` page, never a checkout) say why and where, null otherwise. D167: on the agent read (`GET /v1/readiness`, `get_readiness`) a hosted domain's ready step is withheld too, `withheld_reason` `"hosted"`: we serve its record, so there is nothing for the owner to publish (relay `hosted.note`).
Properties
| Name | Type | Description |
|---|---|---|
| ready | boolean | |
| current_step | object | |
| next_step | object | |
| next_record | object | |
| blockers | array | |
| window_days | integer | |
| total_messages | integer | |
| rejected_unknown | object | |
| progress | object | |
| enrollment | object | |
| evidence | object | |
| record_withheld | boolean | |
| withheld_reason | object | |
| pricing_url | object | |
| aligned_pct | object | |
| hosted | object |
JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://artifacts.apis.io/dnsdoctor-dev/json-schema/dnsdoctor-dev-readiness-out-schema.json",
"title": "ReadinessOut",
"description": "The serialized `ReadinessVerdict` — `next_record` only when ready.\n\nD125/D130: `record_withheld` marks a ready `p=reject` step whose record the\nplan does not include; `withheld_reason` (`\"plan\"`) and `pricing_url` (a\n`/pricing` page, never a checkout) say why and where, null otherwise. D167: on\nthe agent read (`GET /v1/readiness`, `get_readiness`) a hosted domain's ready\nstep is withheld too, `withheld_reason` `\"hosted\"`: we serve its record, so\nthere is nothing for the owner to publish (relay `hosted.note`).",
"x-generated": "2026-11-05",
"x-method": "derived",
"x-generator": "derive-json-schema.py",
"x-source": "openapi/dnsdoctor-dev-openapi.json#/components/schemas/ReadinessOut",
"properties": {
"ready": {
"type": "boolean",
"title": "Ready"
},
"current_step": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Current Step"
},
"next_step": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Next Step"
},
"next_record": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Next Record"
},
"blockers": {
"items": {
"$ref": "#/$defs/BlockerOut"
},
"type": "array",
"title": "Blockers"
},
"window_days": {
"type": "integer",
"title": "Window Days"
},
"total_messages": {
"type": "integer",
"title": "Total Messages"
},
"rejected_unknown": {
"anyOf": [
{
"$ref": "#/$defs/RejectedUnknownOut"
},
{
"type": "null"
}
]
},
"progress": {
"$ref": "#/$defs/ProgressOut"
},
"enrollment": {
"$ref": "#/$defs/EnrollmentStatusOut"
},
"evidence": {
"$ref": "#/$defs/dns_doctor__rua__readiness_out__EvidenceOut"
},
"record_withheld": {
"type": "boolean",
"title": "Record Withheld",
"default": false
},
"withheld_reason": {
"anyOf": [
{
"type": "string",
"enum": [
"plan",
"hosted"
]
},
{
"type": "null"
}
],
"title": "Withheld Reason"
},
"pricing_url": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Pricing Url"
},
"aligned_pct": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Aligned Pct"
},
"hosted": {
"anyOf": [
{
"$ref": "#/$defs/HostedReadinessOut"
},
{
"type": "null"
}
]
}
},
"type": "object",
"required": [
"ready",
"current_step",
"next_step",
"next_record",
"blockers",
"window_days",
"total_messages",
"progress",
"enrollment",
"evidence"
],
"$defs": {
"BlockerOut": {
"properties": {
"source": {
"type": "string",
"title": "Source"
},
"metric": {
"type": "string",
"title": "Metric"
},
"fix_hint": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Fix Hint"
},
"ip": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Ip"
},
"senders": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Senders"
},
"ips": {
"anyOf": [
{
"items": {
"type": "string"
},
"type": "array"
},
{
"type": "null"
}
],
"title": "Ips"
}
},
"type": "object",
"required": [
"source",
"metric",
"fix_hint"
],
"title": "BlockerOut",
"description": "One reason the next policy step stays locked."
},
"EnrollmentState": {
"type": "string",
"enum": [
"not_published",
"published_waiting",
"published_silent",
"unknown",
"receiving"
],
"title": "EnrollmentState",
"description": "Whether a monitored domain's owner has actually published our rua= address.\n\n`total_messages == 0` alone conflates three very different situations; this\nenum separates them so the dashboard can stop promising reports that can\nnever arrive (design §3.2):\n\n- `NOT_PUBLISHED`: our address is absent from the live rua= list.\n- `PUBLISHED_WAITING`: present, no reports yet, < `silent_hours` since first seen.\n- `PUBLISHED_SILENT`: present, no reports yet, ≥ `silent_hours` since first seen.\n- `RECEIVING`: reports arrived in the window (decided by volume, so it can\n never contradict the readiness verdict derived from the same read).\n- `UNKNOWN` (D165): the DMARC record could not be read just now and no rescan\n ever saw our address — neither \"published\" nor \"not published\"."
},
"EnrollmentStatusOut": {
"properties": {
"state": {
"$ref": "#/$defs/EnrollmentState"
},
"published_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Published At"
},
"checked_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Checked At"
}
},
"type": "object",
"required": [
"state",
"published_at",
"checked_at"
],
"title": "EnrollmentStatusOut",
"description": "The four-state RUA enrollment verdict (design §3.4).\n\nNamed `EnrollmentStatusOut` because `EnrollmentOut` is already the enrollment\nroute's model (`rua/enroll.py`). Carries **no record field by construction**,\nso an LLM-shaped record string can never reach this surface (the invariant)."
},
"HostedReadinessOut": {
"properties": {
"note": {
"type": "string",
"title": "Note"
},
"next_step_at": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "Next Step At"
}
},
"type": "object",
"required": [
"note"
],
"title": "HostedReadinessOut",
"description": "D167 M3/M6: DNS Doctor hosts this row's DMARC record, so the next step is\napplied from the dashboard, never published by hand. `next_step_at` is when a\nscheduled automatic step runs (ISO), null when none is scheduled."
},
"ProgressOut": {
"properties": {
"progress_pct": {
"type": "number",
"title": "Progress Pct"
},
"step_base_pct": {
"type": "integer",
"title": "Step Base Pct"
},
"evidence_fraction": {
"type": "number",
"title": "Evidence Fraction"
},
"days_with_data": {
"type": "integer",
"title": "Days With Data"
},
"worst_source_aligned_pct": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Worst Source Aligned Pct"
},
"unknown_volume_pct": {
"type": "number",
"title": "Unknown Volume Pct"
},
"stage": {
"type": "string",
"title": "Stage"
},
"eta_days": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Eta Days"
}
},
"type": "object",
"required": [
"progress_pct",
"step_base_pct",
"evidence_fraction",
"days_with_data",
"worst_source_aligned_pct",
"unknown_volume_pct",
"stage"
],
"title": "ProgressOut",
"description": "The serialized D48 progress bar — the dashboard renders, never recomputes."
},
"RejectedUnknownOut": {
"properties": {
"sources": {
"type": "integer",
"title": "Sources"
},
"messages": {
"type": "integer",
"title": "Messages"
}
},
"type": "object",
"required": [
"sources",
"messages"
],
"title": "RejectedUnknownOut",
"description": "D113 — unaligned unknown mail the published `p=reject` already rejects.\n\nPresent only at the terminal rung, where the unknown-sources blocker is not\nemitted: the same D50 aggregate, read as the policy's receipt rather than as\na reason to withhold a step that does not exist."
},
"dns_doctor__rua__readiness_out__EvidenceOut": {
"properties": {
"trusted_reports": {
"type": "integer",
"title": "Trusted Reports"
},
"untrusted_reports": {
"type": "integer",
"title": "Untrusted Reports"
},
"reporters": {
"items": {
"type": "string"
},
"type": "array",
"title": "Reporters"
},
"dominant_report_share": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
],
"title": "Dominant Report Share"
},
"days_with_trusted_data": {
"type": "integer",
"title": "Days With Trusted Data"
},
"concentrated": {
"type": "boolean",
"title": "Concentrated"
}
},
"type": "object",
"required": [
"trusted_reports",
"untrusted_reports",
"reporters",
"dominant_report_share",
"days_with_trusted_data",
"concentrated"
],
"title": "EvidenceOut",
"description": "What the verdict was computed from (D111) — counts and names, no prose.\n\nAdditive: every readiness surface (dashboard, `GET /v1/readiness`, the\n`get_readiness` MCP tool) serializes through `serialize_readiness`, so this\nobject appears on all three at once and can never differ between them.\n`concentrated` is a flag, not a gate — one reporter supplying nearly all the\nevidence is worth saying, and nothing is withheld for it."
}
}
}
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-readiness-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.