Every API 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 apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.
get_api_artifactsOne API's artifacts, grouped by type.
get_openapiThe primary OpenAPI for this API.
find_similar_apisAPIs that look like this one.
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.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/dnsdoctor-dev:dnsdoctor-dev-tools-api"
All apis
curl "https://apis.io/api/v1/apis?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.
openapi: 3.2.0
info:
title: Dnsdoctor Dev Tools API
version: '1'
contact:
name: DNS Doctor
email: hello@dnsdoctor.dev
x-guidance: 'DNS Doctor scans, fixes and verifies a domain''s DNS (SPF, DMARC, DKIM, propagation, MX, DNS health, blacklists, domain/SSL expiry) and returns deterministic verdicts plus copy-paste fix records from a validating engine — never a guessed record. Scan with POST /scan or GET /report/{domain}; read verdicts failing-first (''temperror'' is transient, not a failure; a report with not_registered: true means the domain does not resolve, so propose nothing for it). Present any returned record VERBATIM and never compose one yourself; SPF is diagnose-only. A human approves every DNS change — nothing is applied automatically. Anonymous calls are free up to a per-caller limit; past it the API answers 402 (x402, USDC on Base, $0.01 per call). POST /bulk-scan is paid on every call at $0.005 per domain; POST /bulk-registration (RDAP) at $0.002 per domain; POST /bulk-propagation at $0.008 per check; POST /bulk-dmarc-reports parses DMARC aggregate reports at $0.005 per report; POST /watch/record watches one DNS record for up to 24 hours at $0.10; POST /watch/lookalikes watches a domain''s lookalike names hourly for one fixed 24-hour day at $0.50. The five token-gated reads (GET /domains, /alerts, /readiness, /lookalikes and /email-tests) need a DNS Doctor API token the account owner mints at /dashboard/settings — never ask a user to paste one.'
description: 'Operations tagged tools across 2 of this provider''s published API definitions: dnsdoctor-dev-openapi.json, dnsdoctor-dev-openapi.json. Each path carries the servers of the definition it was published in.'
servers:
- url: https://dnsdoctor.dev/api/v1
tags:
- name: Tools
paths:
/bulk-registration:
post:
tags:
- Tools
summary: Bulk Registration
description: 'RDAP registration for 2-50 domains in one paid call. **402 on every call.**
Priced per domain from the batch. Settled only when at least one row is a
registry answer (`registered` / `not_registered`); an all-`unknown` batch is
returned unsettled.'
operationId: bulk_registration_bulk_registration_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/BulkRegistrationIn'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/BulkRegistrationOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
'402':
description: Payment required — retry with an x402 payment header.
x-payment-info:
payTo: '0x6fDc1f22520151DfBc6273d6C48E413D434C7916'
price:
mode: dynamic
currency: USD
min: '0.004'
max: '0.1'
protocols:
- x402: {}
network: base
servers:
- url: https://dnsdoctor.dev/api/v1
/bulk-propagation:
post:
tags:
- Tools
summary: Bulk Propagation
description: 'Propagation of 2-8 names across the vantage fleet in one paid call.
**402 on every call.**
Priced per check from the batch. Settled only when at least one check''s
verdict is not `unknown`; an all-`unknown` batch is returned unsettled.'
operationId: bulk_propagation_bulk_propagation_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/BulkPropagationIn'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/BulkPropagationOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
'402':
description: Payment required — retry with an x402 payment header.
x-payment-info:
payTo: '0x6fDc1f22520151DfBc6273d6C48E413D434C7916'
price:
mode: dynamic
currency: USD
min: '0.016'
max: '0.064'
protocols:
- x402: {}
network: base
servers:
- url: https://dnsdoctor.dev/api/v1
/bulk-dmarc-reports:
post:
tags:
- Tools
summary: Bulk Dmarc Reports
description: 'Parse 2-20 DMARC aggregate reports in one paid call. **402 on every call.**
Priced per report. Per-report decode/parse failures are rows, not errors;
the batch is settled only when at least one report parsed, so an all-error
batch is returned unsettled.'
operationId: bulk_dmarc_reports_bulk_dmarc_reports_post
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/BulkReportsIn'
required: true
responses:
'200':
description: Successful Response
content:
application/json:
schema:
$ref: '#/components/schemas/BulkReportsOut'
'422':
description: Validation Error
content:
application/json:
schema:
$ref: '#/components/schemas/HTTPValidationError'
'402':
description: Payment required — retry with an x402 payment header.
x-payment-info:
payTo: '0x6fDc1f22520151DfBc6273d6C48E413D434C7916'
price:
mode: dynamic
currency: USD
min: '0.01'
max: '0.1'
protocols:
- x402: {}
network: base
servers:
- url: https://dnsdoctor.dev/api/v1
components:
schemas:
BulkReportIn:
properties:
content_base64:
type: string
maxLength: 2870811
title: Content Base64
filename:
anyOf:
- type: string
maxLength: 255
- type: 'null'
title: Filename
type: object
required:
- content_base64
title: BulkReportIn
description: 'One report: base64 of the raw XML / .gz / .zip, plus an optional name.
The filename is a parser hint (parsedmarc reads the extension) and is echoed
on the row so a caller can match rows to files.'
BulkPropagationIn:
properties:
checks:
items:
$ref: '#/components/schemas/BulkPropagationCheckIn'
type: array
maxItems: 8
title: Checks
type: object
required:
- checks
title: BulkPropagationIn
description: '2-8 checks. Bounded at the model so pydantic refuses an oversized array
during parse; `_normalize` re-checks the floor on the DEDUPED count, which
is what runs and what is priced.'
ReportSourceOut:
properties:
ip:
type: string
title: Ip
total:
type: integer
title: Total
spf_aligned_pct:
type: number
title: Spf Aligned Pct
dkim_aligned_pct:
type: number
title: Dkim Aligned Pct
aligned_pct:
type: number
title: Aligned Pct
type: object
required:
- ip
- total
- spf_aligned_pct
- dkim_aligned_pct
- aligned_pct
title: ReportSourceOut
description: One sending source, aggregated across the report's per-source records.
PropagationResponse:
properties:
name:
type: string
title: Name
record_type:
type: string
title: Record Type
expected_value:
anyOf:
- type: string
- type: 'null'
title: Expected Value
mode:
$ref: '#/components/schemas/CheckMode'
verdict:
$ref: '#/components/schemas/Verdict'
vantages:
items:
$ref: '#/components/schemas/VantageRow'
type: array
title: Vantages
vantage_total:
type: integer
title: Vantage Total
vantage_reached:
type: integer
title: Vantage Reached
answer_sets:
items:
items:
type: string
type: array
type: array
title: Answer Sets
checked_at:
type: string
format: date-time
title: Checked At
type: object
required:
- name
- record_type
- expected_value
- mode
- verdict
- vantages
- vantage_total
- vantage_reached
- answer_sets
- checked_at
title: PropagationResponse
description: 'The whole grid plus its verdict. Deterministic throughout — no LLM ever,
no composed record, no remediation field.
`vantage_total` / `vantage_reached` are the coverage honesty: they are
serialized beside the verdict so a consumer can say "4 of 6" rather than
implying the world. `answer_sets` lists every DISTINCT answer set observed
across reached cells (sorted, deduped) — with more than one entry the name
is mid-flight somewhere, which is the finding a single verdict word cannot
carry on its own.'
BulkPropagationRow:
properties:
name:
type: string
title: Name
record_type:
type: string
title: Record Type
status:
$ref: '#/components/schemas/RowStatus'
result:
anyOf:
- $ref: '#/components/schemas/PropagationResponse'
- type: 'null'
error:
anyOf:
- type: string
- type: 'null'
title: Error
type: object
required:
- name
- record_type
- status
title: BulkPropagationRow
description: 'One check''s row: the core''s response verbatim, or a fixed error reason.'
BulkPropagationCheckIn:
properties:
name:
type: string
maxLength: 1012
title: Name
record_type:
type: string
maxLength: 16
title: Record Type
default: A
expected_value:
anyOf:
- type: string
maxLength: 1024
- type: 'null'
title: Expected Value
type: object
required:
- name
title: BulkPropagationCheckIn
description: 'One check — `PropagationRequest`''s fields, bounded at parse.
Validation beyond the bounds is the core''s own normalizers'', run by
`_normalize` for the whole batch before the lane opens.'
VantageRow:
properties:
region:
type: string
title: Region
label:
type: string
title: Label
reached:
type: boolean
title: Reached
cells:
items:
$ref: '#/components/schemas/ResolverCell'
type: array
title: Cells
detail:
anyOf:
- type: string
- type: 'null'
title: Detail
type: object
required:
- region
- label
- reached
- cells
title: VantageRow
description: 'One vantage point''s row of the grid.
`reached` is True when the probe answered at all — a reached vantage may
still hold `no_answer` cells (an honest "nothing there"), and an unreached
one contributes nothing to the verdict but is still rendered, because a
silently dropped row reads as "we checked and it was fine".
An unreached row carries no cells and puts the reason in `detail`: the loss
belongs to the vantage point, not to a resolver it never got to ask.'
BulkRegistrationIn:
properties:
domains:
items:
type: string
maxLength: 253
type: array
maxItems: 50
title: Domains
type: object
required:
- domains
title: BulkRegistrationIn
description: '2-50 domains, canonicalized and deduped server-side.
Bounded at the model as bulk-scan''s body is, so pydantic refuses an oversized
array during parse; `_normalize` re-checks the floor on the DEDUPED count,
which is what runs and what is priced.'
RegistrationResponse:
properties:
domain:
type: string
title: Domain
status:
$ref: '#/components/schemas/RegistrationStatus'
reason:
anyOf:
- type: string
- type: 'null'
title: Reason
registration:
anyOf:
- $ref: '#/components/schemas/Registration'
- type: 'null'
queried_at:
type: string
format: date-time
title: Queried At
type: object
required:
- domain
- status
- queried_at
title: RegistrationResponse
description: One registry reading. `registration` is present only for `REGISTERED`.
BulkRegistrationOut:
properties:
domains_requested:
type: integer
title: Domains Requested
domains_resolved:
type: integer
title: Domains Resolved
results:
items:
$ref: '#/components/schemas/RegistrationResponse'
type: array
title: Results
type: object
required:
- domains_requested
- domains_resolved
- results
title: BulkRegistrationOut
description: 'The batch. `domains_resolved` is what the settle gate reads.
Every requested name gets a row, in request order — `unknown` with a reason
when it could not be read, so "missing" is never mistaken for an answer.'
ReportParseResponse:
properties:
org_name:
anyOf:
- type: string
- type: 'null'
title: Org Name
report_id:
anyOf:
- type: string
- type: 'null'
title: Report Id
domain:
anyOf:
- type: string
- type: 'null'
title: Domain
published_policy:
anyOf:
- type: string
- type: 'null'
title: Published Policy
date_begin:
anyOf:
- type: string
- type: 'null'
title: Date Begin
date_end:
anyOf:
- type: string
- type: 'null'
title: Date End
total_messages:
type: integer
title: Total Messages
aligned_messages:
type: integer
title: Aligned Messages
aligned_pct:
type: number
title: Aligned Pct
dispositions:
$ref: '#/components/schemas/ReportDispositionMix'
sources:
items:
$ref: '#/components/schemas/ReportSourceOut'
type: array
title: Sources
type: object
required:
- org_name
- report_id
- domain
- published_policy
- date_begin
- date_end
- total_messages
- aligned_messages
- aligned_pct
- dispositions
- sources
title: ReportParseResponse
description: 'One parsed DMARC aggregate report — metadata + per-source aggregates.
Zero-persistence: assembled straight from the shared parser''s output and
discarded after the response. No record strings, no LLM — the read-only
acquisition twin of the authenticated backfill upload.'
HTTPValidationError:
properties:
detail:
items:
$ref: '#/components/schemas/ValidationError'
type: array
title: Detail
type: object
title: HTTPValidationError
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.
Every field is optional because every field is redactable: a registry that
publishes only status codes and nameservers is the normal post-GDPR case,
not a failure. `status` carries the registry''s own strings verbatim (RFC
9083 spells them `client transfer prohibited`; some registries still emit
EPP camelCase) — normalizing them would invent a vocabulary neither side
uses.'
ValidationError:
properties:
loc:
items:
anyOf:
- type: string
- type: integer
type: array
title: Location
msg:
type: string
title: Message
type:
type: string
title: Error Type
input:
title: Input
ctx:
type: object
title: Context
type: object
required:
- loc
- msg
- type
title: ValidationError
BulkReportsOut:
properties:
reports:
items:
$ref: '#/components/schemas/BulkReportRow'
type: array
title: Reports
combined:
items:
$ref: '#/components/schemas/ReportParseResponse'
type: array
title: Combined
reports_parsed:
type: integer
title: Reports Parsed
type: object
required:
- reports
- combined
- reports_parsed
title: BulkReportsOut
description: 'The batch. `reports_parsed` is what the settle gate reads.
`combined` is one entry per reported domain (a report naming none is grouped
under `domain: null`), computed over the pooled rows of every parsed report
for it — a report sent twice (same reporter + report id) is pooled once, as
intake''s duplicate rule — `org_name`/`report_id` are null, the dates are the
batch''s min/max.'
BulkReportsIn:
properties:
reports:
items:
$ref: '#/components/schemas/BulkReportIn'
type: array
maxItems: 20
minItems: 2
title: Reports
type: object
required:
- reports
title: BulkReportsIn
description: 2-20 reports, bounded at the model so an oversize body is a 422 at parse.
RowStatus:
type: string
enum:
- ok
- error
title: RowStatus
description: Whether the core returned a grid for this check (`ok`) or not (`error`).
Verdict:
type: string
enum:
- propagated
- partial
- not_propagated
- consistent
- inconsistent
- unknown
title: Verdict
description: 'The overall reading. Expected-value mode yields the first three,
consistency mode the next two (or `NOT_PROPAGATED` when every reading agrees
on no answer at all), and either downgrades to `UNKNOWN` when
fewer than `MIN_VANTAGES_FOR_VERDICT` vantage points were reached.'
RegistrationStatus:
type: string
enum:
- registered
- not_registered
- unknown
title: RegistrationStatus
description: 'What the registry told us. `UNKNOWN` is the honest third answer.
Deliberately three-valued: a registry that did not answer has said nothing
about the domain, and collapsing that into `NOT_REGISTERED` would tell a
caller a name is free on the strength of a 429.'
BulkReportRow:
properties:
index:
type: integer
title: Index
filename:
anyOf:
- type: string
- type: 'null'
title: Filename
status:
type: string
enum:
- ok
- bad_base64
- too_large
- not_a_report
- unparseable
- deadline
title: Status
report:
anyOf:
- $ref: '#/components/schemas/ReportParseResponse'
- type: 'null'
type: object
required:
- index
- filename
- status
title: BulkReportRow
description: One report's outcome, in request order. `report` is set only when `ok`.
CellStatus:
type: string
enum:
- match
- mismatch
- answered
- no_answer
- unavailable
title: CellStatus
description: 'What one resolver at one vantage point contributed.
`MATCH`/`MISMATCH` exist only in expected-value mode; `ANSWERED` is their
consistency-mode counterpart (we were given nothing to compare against, so
the cell reports that it answered and the grid shows what it said).
`NO_ANSWER` is an answer — the name resolves to nothing there.
`UNAVAILABLE` is the absence of one, and never counts toward a verdict.'
CheckMode:
type: string
enum:
- expected
- consistency
title: CheckMode
description: Which question was asked — the caller supplied an expected value, or not.
BulkPropagationOut:
properties:
checks_requested:
type: integer
title: Checks Requested
checks_answered:
type: integer
title: Checks Answered
results:
items:
$ref: '#/components/schemas/BulkPropagationRow'
type: array
title: Results
type: object
required:
- checks_requested
- checks_answered
- results
title: BulkPropagationOut
description: 'The batch. `checks_answered` (verdict not `unknown`) is what the settle
gate reads. Every requested check gets a row, in request order.'
ReportDispositionMix:
properties:
none:
type: integer
title: None
quarantine:
type: integer
title: Quarantine
reject:
type: integer
title: Reject
type: object
required:
- none
- quarantine
- reject
title: ReportDispositionMix
description: Report-wide message counts by evaluated disposition.
ResolverCell:
properties:
resolver:
type: string
title: Resolver
status:
$ref: '#/components/schemas/CellStatus'
answers:
items:
type: string
type: array
title: Answers
ttl:
anyOf:
- type: integer
- type: 'null'
title: Ttl
rtt_ms:
anyOf:
- type: integer
- type: 'null'
title: Rtt Ms
detail:
anyOf:
- type: string
- type: 'null'
title: Detail
type: object
required:
- resolver
- status
- answers
title: ResolverCell
description: 'One resolver''s reading at one vantage point — a single grid cell.
`answers` is deterministically ordered by the probe and re-sorted here, so
two cells holding the same record set compare equal regardless of the order
the wire happened to deliver them in. `detail` carries the reason a cell is
`unavailable`; it is never prose about the record.'
securitySchemes:
apiKey:
type: http
scheme: bearer
description: 'A DNS Doctor API token: `Authorization: Bearer dnsd_…`.'
x-refined-from:
- dnsdoctor-dev-openapi.json
- dnsdoctor-dev-openapi.json
x-discovery:
ownershipProofs:
- '0xeb49cc7d9ac04add7ee0fbe51bc806b684ebdb6bfcdc8c2d7aa7eebc02032b93403764b0986d036ed68ad7e5914edc5403209f19806f4a87e15b1a5bdb73e9ab1b'