API Reference

The MARnet Tools REST API lets you run DNS zone checks programmatically. Submit a domain and its nameserver list; receive structured validation results including per-NS SOA queries, glue IP checks, serial consistency, and NS delegation status. No authentication required — requests are rate-limited per IP.

Rate limits

The API enforces a per-IP rate limit to ensure fair access.

Current limit: 10 per minute per remote IP address.
This is configured in config.pyAPI_RATE_LIMIT. The server returns HTTP 429 with a Retry-After header when exceeded.

When the limit is exceeded, the response includes a retry_after field with the number of seconds to wait before retrying.

Endpoint

POST /api/v1/dnscheck DNS zone check

Accepts a JSON body with a domain name and 2–8 nameserver entries. Performs server-side DNS queries directly to each nameserver and returns structured results.

Request body

Content-Type: application/json

Top-level fields

FieldTypeRequiredDescription
domainstringrequiredFully qualified domain name, e.g. example.mk
ns_entriesarrayrequiredArray of nameserver objects. Minimum 2, maximum 8.

ns_entries object

FieldTypeDescription
nsstringrequiredNameserver hostname, e.g. ns1.example.mk
ipv4stringoptional*Glue IPv4 address. Required when NS hostname falls within the delegated domain (in-zone).
ipv6stringoptionalGlue IPv6 address for in-zone nameservers.
Glue records: if the NS hostname is within the zone being delegated (e.g. ns1.example.mk for example.mk), at least one glue IP (ipv4 or ipv6) must be provided.
Example request body
{
  "domain": "example.mk",
  "ns_entries": [
    {
      "ns":   "ns1.example.mk",
      "ipv4": "194.149.137.1",
      "ipv6": "2001:db8::1"
    },
    {
      "ns":   "ns2.example.mk",
      "ipv4": "194.149.137.2"
    }
  ]
}

Response

Successful responses return HTTP 200 with a JSON envelope.

Response structure
{
  "api_version": "1.0",
  "data": {
    "domain": "example.mk",
    "summary": {
      "overall_ok":        true,
      "all_soa_ok":        true,
      "all_resolve":       true,
      "glue_issues":       false,
      "serial_consistent": true,
      "all_authoritative": true,
      "serials":           [2024040801],
      "ns_match":          true
    },
    "registered_ns": ["ns1.example.mk", "ns2.example.mk"],
    "ns_checks": [
      {
        "ns":           "ns1.example.mk",
        "in_zone":     true,
        "query_ip":    "194.149.137.1",
        "resolved_a":  ["194.149.137.1"],
        "resolved_aaaa":["2001:db8::1"],
        "glue_ipv4":   "194.149.137.1",
        "glue_ipv6":   null,
        "glue_check": {
          "ipv4_entered":  "194.149.137.1",
          "ipv4_resolved": ["194.149.137.1"],
          "ipv4_match":    true
        },
        "soa": {
          "status":        "ok",
          "authoritative": true,
          "rcode":         "NOERROR",
          "soa": {
            "mname":   "ns1.example.mk",
            "rname":   "hostmaster.example.mk",
            "serial":  2024040801,
            "refresh": 3600,
            "retry":   900,
            "expire":  604800,
            "minimum": 86400,
            "ttl":     3600
          }
        }
      }
    ]
  }
}

soa.status values

ok
SOA record returned; soa object is populated.
timeout
UDP/TCP query timed out. NS may be unreachable.
refused
Nameserver refused the query (REFUSED rcode or TCP reset).
nxdomain
NS returned NXDOMAIN — domain is not loaded on this server.
servfail
NS returned SERVFAIL.
no_soa
NOERROR response but no SOA in answer or authority section.
no_ip
NS hostname could not be resolved to any IP — query not attempted.
error
Unexpected error; message field contains details.

Error responses

422
Validation failed. Body: {"error":"validation_failed","details":[...]}
429
Rate limit exceeded. Body: {"error":"rate_limit_exceeded","retry_after":N}
500
Server error. Usually indicates dnspython is not installed.

curl

curl -s -X POST https://tools.marnet.mk/api/v1/dnscheck \
  -H 'Content-Type: application/json' \
  -d '{
    "domain": "example.mk",
    "ns_entries": [
      {"ns": "ns1.example.mk", "ipv4": "194.149.137.1"},
      {"ns": "ns2.example.mk", "ipv4": "194.149.137.2"}
    ]
  }' | jq '.data.summary'

Python

import requests

resp = requests.post(
    "https://tools.marnet.mk/api/v1/dnscheck",
    json={
        "domain": "example.mk",
        "ns_entries": [
            {"ns": "ns1.example.mk", "ipv4": "194.149.137.1"},
            {"ns": "ns2.example.mk", "ipv4": "194.149.137.2"},
        ],
    },
    timeout=30,
)
resp.raise_for_status()
data = resp.json()["data"]
print("Overall OK:", data["summary"]["overall_ok"])
for ns in data["ns_checks"]:
    print(ns["ns"], "→", ns["soa"]["status"])

JavaScript (fetch)

const res = await fetch("https://tools.marnet.mk/api/v1/dnscheck", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    domain: "example.mk",
    ns_entries: [
      { ns: "ns1.example.mk", ipv4: "194.149.137.1" },
      { ns: "ns2.example.mk", ipv4: "194.149.137.2" },
    ],
  }),
});

if (res.status === 429) {
  const err = await res.json();
  console.error(`Rate limited. Retry after ${err.retry_after}s`);
} else {
  const { data } = await res.json();
  console.log("Overall OK:", data.summary.overall_ok);
}