DNS Doctor Tools API

The tools API from DNS Doctor — 3 operation(s) for tools.

Operations 3

POST /bulk-registration Bulk Registration #
POST /bulk-propagation Bulk Propagation #
POST /bulk-dmarc-reports Bulk Dmarc Reports #

Work with this as data

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 Specification

dnsdoctor-dev-tools-api-openapi.yml Raw ↑
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'