---
title: "Bulk Propagation"
method: POST
path: "/bulk-propagation"
tags: ["tools"]
---

# Bulk Propagation

`POST /bulk-propagation`

## Request

- Base URL: `https://dnsdoctor.dev/api/v1`
- URL: `https://dnsdoctor.dev/api/v1/bulk-propagation`
- Auth: none declared

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.

## Request body

- BulkPropagationIn: 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.
  - `checks` BulkPropagationCheckIn[], required
    - `name` string, required
    - `record_type` string
    - `expected_value` string, nullable

## Response `200`

Successful Response

- BulkPropagationOut: The batch. `checks_answered` (verdict not `unknown`) is what the settle gate reads. Every requested check gets a row, in request order.
  - `checks_requested` integer, required
  - `checks_answered` integer, required
  - `results` BulkPropagationRow[], required
    - `name` string, required
    - `record_type` string, required
    - `status` 'ok' | 'error', required: Whether the core returned a grid for this check (`ok`) or not (`error`).
    - `result` PropagationResponse: 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.
      - `name` string, required
      - `record_type` string, required
      - `expected_value` string, nullable, required
      - `mode` 'expected' | 'consistency', required: Which question was asked — the caller supplied an expected value, or not.
      - `verdict` 'propagated' | 'partial' | 'not_propagated' | 'consistent' | 'inconsistent' | 'unknown', required: The overall reading. Expected-value mode yields the first three, consistency mode the next two, and either downgrades to `UNKNOWN` when fewer than `MIN_VANTAGES_FOR_VERDICT` vantage points were reached.
      - `vantages` VantageRow[], required
        - `region` string, required
        - `label` string, required
        - `reached` boolean, required
        - `cells` ResolverCell[], required
          - `resolver` string, required
          - `status` 'match' | 'mismatch' | 'answered' | 'no_answer' | 'unavailable', required: 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.
          - `answers` string[], required
          - `ttl` integer, nullable
          - `rtt_ms` integer, nullable
          - `detail` string, nullable
        - `detail` string, nullable
      - `vantage_total` integer, required
      - `vantage_reached` integer, required
      - `answer_sets` array[], required
        - string[]
      - `checked_at` string, date-time, required
    - `error` string, nullable

## Other responses

- `402`: Payment required — retry with an x402 payment header.
- `422`: Validation Error

---

[API](https://skmtc.dev/dnsdoctor/apis/dns-doctor-api.md) · [All operations](https://skmtc.dev/dnsdoctor/apis/dns-doctor-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dnsdoctor/apis/dns-doctor-api/revisions/1d0114806e76?raw)
