Skip to main content
WeaveBit
Aug 18, 2026 21 min read WeaveBit Team Updated Oct 11, 2026

Bulk DNS and GFW Checks with the WeaveBit API

Run batch DNS poisoning and GFW checks for mainland China with WeaveBit API v2, including estimates, idempotency, pagination, storage and a Python example.

Before checking a domain list, decide whether you need a DNS poisoning assessment or a combined check of DNS poisoning, HTTP Host and TLS SNI. WeaveBit API v2 supports both as asynchronous batch workflows: prepare targets, estimate the cost, create a batch, then query and retain results by batch ID. This suits domain inventory checks, pre-release checks and integration into your own operational tools.

The examples demonstrate a minimal batch workflow. Running /task/create creates a batch that can incur charges. Configure environment variables on your controlled server, replace the example domains and review the estimate first. The API documentation is the reference for the complete contract.

Choose a DNS batch or a GFW Full batch

Workflowcheck_typeNode parameterResult location
Batch DNS poisoning checksdns_pollution_checkOmit nodesdatas[].result
Batch GFW Full domain checksgfw_full_domain_checkSupply currently valid nodesEach node result inside datas[].results[]

GFW Full covers DNS poisoning, HTTP Host and TLS SNI, plus overall_result. It is not a complete browser test or all seven API methods. See the GFW domain check guide for choosing the appropriate scope.

Each estimate or creation request accepts 1–1000 targets. target is an array of objects. Domains must be plain hostnames without schemes, paths, query strings, ports or spaces. Deduplicate before submission: repeated targets can become separately charged tasks. Internationalized names normalize to lowercase ASCII Punycode; returned check_id values let you associate results with your business records.

A DNS batch uses this body:

{
  "check_type": "dns_pollution_check",
  "target": [{"domain": "example.com"}, {"domain": "example.net"}]
}

A GFW Full batch uses the same domain array and adds nodes:

{
  "check_type": "gfw_full_domain_check",
  "target": [{"domain": "example.com"}, {"domain": "example.net"}],
  "nodes": ["NODE_A_FROM_GET_NODES", "NODE_B_FROM_GET_NODES"]
}

Replace the placeholders with current node_id values from /task/get_nodes, called with POST and {}. Parents also return children[]. nodes accepts up to 100 node codes or the string "all"; parents expand into currently available children. Do not substitute display names for codes or choose all nodes without understanding the cost.

Estimate before you create

The v2 Base URL is https://api.weavebit.com/v2. Requests use POST, JSON and Authorization: Bearer. Send the intended body to /task/estimate, review its data, especially target count, node count, planned quantity, estimated cost and available balance, then send the same detection parameters to /task/create. Estimate again if you change targets, nodes or methods.

Amounts are decimal strings. Preserve the original values and use a decimal type for accounting, not binary floating-point. Consult current pricing and the actual estimate rather than treating an example configuration as a fixed price.

Creation returns batch_id, datas[].check_id, normalized targets, execution_deadline_at and expires_at, among other fields. Acceptance is not completion. Valid returned results are settled; timeouts, node errors and results discarded for insufficient balance are not charged. Repeated result queries do not cause duplicate charges.

A Python example for both workflows

This example uses only the Python 3 standard library. Save it as your own batch_check.py and use a separate working directory for each logical batch. Supply WEAVEBIT_API_KEY through your server environment. GFW mode also needs WEAVEBIT_NODE_IDS, a JSON array of current node codes. Set and persist WEAVEBIT_REQUEST_ID for the creation; only set WEAVEBIT_CONFIRM_CREATE to yes after reviewing the estimate. Keep the key out of source code, browser scripts, source control and debug logs.

import json
import os
import sys
from pathlib import Path
from urllib.request import Request, urlopen

BASE = "https://api.weavebit.com/v2/"
KEY = os.environ["WEAVEBIT_API_KEY"]
STATE = Path("weavebit-batch.json")

def api(path, body, request_id=None):
    headers = {"Authorization": "Bearer " + KEY,
               "Content-Type": "application/json"}
    if request_id is not None:
        headers["Idempotency-Key"] = request_id
    request = Request(BASE + path, method="POST", headers=headers,
                      data=json.dumps(body).encode("utf-8"))
    with urlopen(request, timeout=30) as response:
        result = json.load(response)
    if result.get("code") != 0:
        raise RuntimeError("API code: " + str(result.get("code")))
    return result

def payload(mode):
    body = {"target": [{"domain": "example.com"},
                       {"domain": "example.net"}]}
    if mode == "dns":
        body["check_type"] = "dns_pollution_check"
    elif mode == "full":
        body["check_type"] = "gfw_full_domain_check"
        body["nodes"] = json.loads(os.environ["WEAVEBIT_NODE_IDS"])
    else:
        raise ValueError("Choose dns or full")
    return body

action = sys.argv[1]
if action == "estimate":
    print(json.dumps(api("task/estimate", payload(sys.argv[2])), indent=2))
elif action == "create":
    if os.environ.get("WEAVEBIT_CONFIRM_CREATE") != "yes":
        raise RuntimeError("Review the estimate before enabling creation")
    body = payload(sys.argv[2])
    request_id = os.environ["WEAVEBIT_REQUEST_ID"]
    created = api("task/create", body, request_id)
    STATE.write_text(json.dumps({"request_id": request_id,
        "request": body, "created": created}, indent=2), encoding="utf-8")
    print("Saved batch_id:", created["batch_id"])
elif action == "collect":
    saved = json.loads(STATE.read_text(encoding="utf-8"))
    batch_id = saved["created"]["batch_id"]
    first = api("task/result", {"batch_id": batch_id,
                               "page": 1, "page_size": 200})
    pages = int(first["pagination"]["pages"])
    if not 1 <= pages <= 5:
        raise RuntimeError("Unexpected page count")
    for page in range(1, pages + 1):
        result = first if page == 1 else api("task/result",
            {"batch_id": batch_id, "page": page, "page_size": 200})
        Path("weavebit-results-page-" + str(page) + ".json").write_text(
            json.dumps(result, indent=2), encoding="utf-8")
    print("Saved all pages; batch_status:", first["batch_status"])
else:
    raise ValueError("Choose estimate, create or collect")

Run python batch_check.py estimate dns first. After reviewing the estimate, run python batch_check.py create dns, followed by python batch_check.py collect. For GFW checks, replace dns with full after obtaining valid nodes. Use different idempotency IDs and directories for the two independent batches.

The example reads every page once and saves the raw JSON. It does not poll indefinitely or retry creation automatically. A production integration should also persist its business request, parameters and idempotency ID before sending, protect saved files and implement bounded error handling. Keep review output containing real targets or account information out of public logs.

Keep creation retries idempotent

Use Idempotency-Key for each creation. It accepts 1–128 letters, digits and . _ : -. The same account, key and parameters return the original batch. If creation times out and acceptance is uncertain, retry with the original key and body. A genuinely new logical batch needs a new key.

40901 signals a conflicting request using the same key; check your persisted parameters. 40902 means creation is still processing; retry later with the original key. For 42900, respect Retry-After and use bounded backoff. Idempotency records expire with batch data after 24 hours; reusing a key later is a new request, not permanent deduplication.

Read every page and separate progress from outcome

Call /task/result with the batch_id returned by creation. A check_id is not a replacement. page defaults to 1; default and maximum page_size are 200 targets. Follow pagination to read all pages rather than treating the first page as coverage of 1000 targets.

pending, running and completed describe execution progress. Completed does not mean normal. If a batch is unfinished, keep its ID and make a later read pass; do not create another batch to obtain an update. Multiple page reads are not a single instantaneous snapshot. For a final archive, read and save all pages and final billing information again after completion.

For DNS, datas[].result is an object; its assessment code is datas[].result.result_code. Also retain its status. Each GFW Full node result is an object too: the summary is datas[].results[].result.overall_result, alongside the three dimension objects and their statuses. Do not treat the whole result object as an assessment code. Preserve 0, 1 and null separately in assessment fields; never convert null to normal. See the result documentation for interpretation.

Save results before the deadline

A batch can execute for at most six hours. Items still missing at execution_deadline_at enter a final timeout state. Targets and results expire for cleanup 24 hours after creation, not completion. Treat returned deadlines as UTC ISO 8601 and save required evidence before expires_at.

/task/get_batches lists metadata from the past 24 hours; it does not replace reading target results. Your own storage should retain the business request, method, normalized targets, nodes, batch_id, check_id, result statuses and relevant times. Console monitoring history and temporary API batches have different lifecycles.

API v1 at /v1/task/pollute is synchronous single-domain DNS poisoning detection, not the batch interface described here. For platform-scheduled checks and notifications, use Console monitoring tasks. For your own scheduling and data retention, use the v2 workflow above.