Record Groups API

A DNS record group is a set of DNS records monitored together, such as the records of one zone, one service, or one customer. Its settings, such as which name servers to query and how often, apply to every DNS record in it.

Endpoints

Request Required access What it does
GET /api/v2/record-groups Read Lists the account's record groups, oldest first. Query parameters: status (passing, failing, or unknown, ignoring case), name (part of the name, ignoring case), and limit and cursor for paging (see Lists). For example: /api/v2/record-groups?status=failing&name=mail. For a loop that reads every page, see Finding Failing DNS Records.
POST /api/v2/record-groups Read and write Creates a record group, returning it with 201.
GET /api/v2/record-groups/{id} Read Gets one record group, with its DNS record counts by status.
PATCH /api/v2/record-groups/{id} Read and write Changes the fields sent, and leaves the rest as they are.
DELETE /api/v2/record-groups/{id} Read and write Deletes the record group and every DNS record in it, returning 204. It counts as one write against the write budget, regardless of how many DNS records it contains.

The DNS records in a record group are listed and added through /api/v2/record-groups/{id}/dns-records, described on the DNS Records API page. To add or export many at once, see the Zone Files API.

Reading a Record Group

This reads the example record group. It works without the Authorization header too, but your own record groups need it, with your API key in $DNSCHECK_API_KEY:

curl https://www.dnscheck.co/api/v2/record-groups/ea883d67-d9f6-45e3-b3a1-844dd1857824 \
  -H "Authorization: Bearer $DNSCHECK_API_KEY"

The response is the record group, with a 200 status. records counts its DNS records by status, and Fields describes the rest:

{
  "id": "ea883d67-d9f6-45e3-b3a1-844dd1857824",
  "object": "record_group",
  "name": "Example DNS Check",
  "description": "",
  "name_servers": [],
  "check_frequency": 300,
  "public": true,
  "notifications": true,
  "status": "failing",
  "records": {
    "total": 38,
    "passing": 36,
    "failing": 2,
    "unknown": 0
  },
  "created_at": "2015-04-11T00:38:46Z",
  "updated_at": "2016-03-19T18:29:48Z",
  "web_url": "https://www.dnscheck.co/tests/ea883d67-d9f6-45e3-b3a1-844dd1857824",
  "api_url": "https://www.dnscheck.co/api/v2/record-groups/ea883d67-d9f6-45e3-b3a1-844dd1857824"
}

Creating a Record Group

A create sends only the fields it sets. This one names the record group and makes it private:

curl https://www.dnscheck.co/api/v2/record-groups \
  -H "Authorization: Bearer $DNSCHECK_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Mail", "public": false}'

This uses the requests library (pip install requests):

import os

import requests

response = requests.post(
    "https://www.dnscheck.co/api/v2/record-groups",
    headers={
        "Authorization": f"Bearer {os.environ['DNSCHECK_API_KEY']}",
    },
    json={"name": "Mail", "public": False},
    timeout=30,
)
if response.headers.get("Content-Type", "").startswith("application/problem+json"):
    problem = response.json()
    print(f"{problem['code']}: {problem['detail']}")
    for error in problem.get("errors", []):
        print(error["message"])
    raise SystemExit(1)
response.raise_for_status()
group = response.json()
print(group["id"])

This needs Node.js 18 or later. Save it in a .mjs file, so top-level await works:

const response = await fetch("https://www.dnscheck.co/api/v2/record-groups", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.DNSCHECK_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ name: "Mail", public: false }),
  signal: AbortSignal.timeout(30000),
});
if (response.headers.get("Content-Type")?.startsWith("application/problem+json")) {
  const problem = await response.json();
  console.error(`${problem.code}: ${problem.detail}`);
  for (const error of problem.errors ?? []) console.error(error.message);
  process.exit(1);
}
if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);
const group = await response.json();
console.log(group.id);

Using only the standard library. Save it as create-group.go and run it with go run create-group.go:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	body, err := json.Marshal(map[string]any{"name": "Mail", "public": false})
	if err != nil {
		log.Fatal(err)
	}
	req, err := http.NewRequest("POST", "https://www.dnscheck.co/api/v2/record-groups", bytes.NewReader(body))
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("DNSCHECK_API_KEY"))
	req.Header.Set("Content-Type", "application/json")
	client := &http.Client{Timeout: 30 * time.Second}
	response, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer response.Body.Close()
	if response.StatusCode >= 400 && !strings.HasPrefix(response.Header.Get("Content-Type"), "application/problem+json") {
		log.Fatalf("HTTP %s", response.Status)
	}

	var group struct {
		ID, Code, Detail string
		Errors           []struct{ Message string }
	}
	if err := json.NewDecoder(response.Body).Decode(&group); err != nil {
		log.Fatal(err)
	}
	if response.StatusCode >= 400 {
		fmt.Fprintf(os.Stderr, "%s: %s\n", group.Code, group.Detail)
		for _, e := range group.Errors {
			fmt.Fprintln(os.Stderr, e.Message)
		}
		os.Exit(1)
	}
	fmt.Println(group.ID)
}

The response is the new record group, with a 201 status and its api_url in the Location header. Every field left out takes its default, so this one is checked every 300 seconds and sends notifications. To make a retry safe, send an Idempotency-Key: see Idempotency Keys. Getting Started walks through creating a record group, adding a DNS record to it, and reading its status.

Updating a Record Group

An update sends only the fields it changes, and null clears description or name_servers. To keep it from overwriting someone else's edit, send the ETag header from reading the record group as If-Match: if the record group has been edited since, the update gets a 412 with the code precondition_failed. The example below turns off notifications for the record group in $GROUP_ID, reading it first for its ETag:

The first command reads the record group and saves its ETag in $ETAG, and the second sends it as If-Match:

curl -s -o /dev/null --etag-save etag.txt https://www.dnscheck.co/api/v2/record-groups/$GROUP_ID \
  -H "Authorization: Bearer $DNSCHECK_API_KEY"
ETAG=$(cat etag.txt)
rm etag.txt

curl -X PATCH https://www.dnscheck.co/api/v2/record-groups/$GROUP_ID \
  -H "Authorization: Bearer $DNSCHECK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "If-Match: $ETAG" \
  -d '{"notifications": false}'

This uses the requests library (pip install requests):

import os

import requests

url = f"https://www.dnscheck.co/api/v2/record-groups/{os.environ['GROUP_ID']}"
headers = {"Authorization": f"Bearer {os.environ['DNSCHECK_API_KEY']}"}

current = requests.get(url, headers=headers, timeout=30)
current.raise_for_status()
response = requests.patch(
    url,
    headers={**headers, "If-Match": current.headers["ETag"]},
    json={"notifications": False},
    timeout=30,
)
if response.headers.get("Content-Type", "").startswith("application/problem+json"):
    problem = response.json()
    print(f"{problem['code']}: {problem['detail']}")
    for error in problem.get("errors", []):
        print(error["message"])
    raise SystemExit(1)
response.raise_for_status()
group = response.json()
print(group["notifications"])

This needs Node.js 18 or later. Save it in a .mjs file, so top-level await works:

const url = `https://www.dnscheck.co/api/v2/record-groups/${process.env.GROUP_ID}`;
const auth = { Authorization: `Bearer ${process.env.DNSCHECK_API_KEY}` };

const current = await fetch(url, { headers: auth, signal: AbortSignal.timeout(30000) });
if (!current.ok) throw new Error(`HTTP ${current.status} ${current.statusText}`);
const response = await fetch(url, {
  method: "PATCH",
  headers: { ...auth, "Content-Type": "application/json", "If-Match": current.headers.get("ETag") },
  body: JSON.stringify({ notifications: false }),
  signal: AbortSignal.timeout(30000),
});
if (response.headers.get("Content-Type")?.startsWith("application/problem+json")) {
  const problem = await response.json();
  console.error(`${problem.code}: ${problem.detail}`);
  for (const error of problem.errors ?? []) console.error(error.message);
  process.exit(1);
}
if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);
const group = await response.json();
console.log(group.notifications);

Using only the standard library. Save it as update-group.go and run it with go run update-group.go:

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"time"
)

func main() {
	url := "https://www.dnscheck.co/api/v2/record-groups/" + os.Getenv("GROUP_ID")
	auth := "Bearer " + os.Getenv("DNSCHECK_API_KEY")
	client := &http.Client{Timeout: 30 * time.Second}

	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", auth)
	current, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	current.Body.Close()
	if current.StatusCode != http.StatusOK {
		log.Fatalf("HTTP %s", current.Status)
	}

	body, err := json.Marshal(map[string]any{"notifications": false})
	if err != nil {
		log.Fatal(err)
	}
	req, err = http.NewRequest("PATCH", url, bytes.NewReader(body))
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("Authorization", auth)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("If-Match", current.Header.Get("ETag"))
	response, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer response.Body.Close()
	if response.StatusCode >= 400 && !strings.HasPrefix(response.Header.Get("Content-Type"), "application/problem+json") {
		log.Fatalf("HTTP %s", response.Status)
	}

	var group struct {
		Notifications bool
		Code, Detail  string
		Errors        []struct{ Message string }
	}
	if err := json.NewDecoder(response.Body).Decode(&group); err != nil {
		log.Fatal(err)
	}
	if response.StatusCode >= 400 {
		fmt.Fprintf(os.Stderr, "%s: %s\n", group.Code, group.Detail)
		for _, e := range group.Errors {
			fmt.Fprintln(os.Stderr, e.Message)
		}
		os.Exit(1)
	}
	fmt.Println(group.Notifications)
}

The response is the updated record group, with a 200 status and its new ETag.

Fields

Fields marked as settable can be sent when creating or updating a record group; the rest are set by DNS Check. A create needs only name, and every field left out takes the default shown.

Field Type Description
id string The record group's UUID, the same one its web page's URL and API v1 use
object string Always "record_group"
name string Settable, required on create. Up to 64 letters, numbers, spaces, hyphens, underscores, periods, ampersands, and apostrophes, unique within the account, ignoring case.
description string Settable. Up to 256 characters. null clears it, as "" does. Default: empty.
name_servers array of strings Settable, Professional and Enterprise. Up to 10 name servers to query, one per entry, by name or IP address. Default: empty, which uses DNS Check's own name servers. null clears them, as [] does. See the FAQ for querying private name servers.
check_frequency integer Settable, Professional and Enterprise. Seconds between scheduled checks: 300 or 3600, or 0 to pause them. More choices may be added. Default: 300.
public boolean Settable. Whether anyone with the record group's URL can view it and check its DNS records without logging in. Default: true.
notifications boolean Settable. Whether status updates to its DNS records trigger notifications. Default: true.
status string failing if any DNS record is failing; passing if there's at least one DNS record and all are passing; otherwise unknown
records object How many DNS records it has: total, passing, failing, and unknown
created_at string When it was created, in UTC
updated_at string When its own settings last changed, in UTC. Adding, changing, or checking its DNS records doesn't change it.
web_url string Its web page
api_url string Its URL in API v2

Plan Requirements

On a Basic account, sending name_servers or check_frequency with anything but its current value (or, on a create, its default) gets a 403 with the code feature_not_in_plan.

Creating a record group beyond the plan's limit gets a 403 with the code plan_limit_reached, and an expired account can only delete. See API Errors for details on each error.


DNS monitoring illustration

Protect your DNS infrastructure with automated monitoring

Get notified immediately when DNS records change. Start monitoring your critical DNS infrastructure for free in under 5 minutes.

No credit card required • Cancel anytime