DNS Monitors

Create and manage domain monitors. Import a zone file, choose which records are watched, and read check history.

A DNS monitor watches a domain, not a single record. One monitor covers a domain's apex — plus every name declared by a zone file you paste — and sweeps ten record types (A, AAAA, CNAME, MX, TXT, NS, CAA, TLSA, DS, SVCB) at each of them on every check. What it expects is the set of records it discovered, held one row per (name, type, value), so a record appearing beside the ones you already have is reported as an addition rather than hidden inside a changed string.

Breaking change

These endpoints changed shape, and there is no deprecation window or v2. Integrations written against the previous per-record-type monitor will break.

RemovedReplacement
recordType on create/update/readEvery supported type is swept. Per-record control lives in the records sub-resource.
expectedValue on create/update/readThe expectation is imported, not typed. Nothing to transcribe.
lastValue on readPer-record values, on the records sub-resource.
resolvedValue on resultsCounters: recordsChecked, recordsChanged, recordsMissing, recordsNew, queriesFailed.
intervalSeconds as low as 60The minimum is now 900, and the default is 86400 (once a day).

The single-probe DNS check on POST /api/v1/ping is unchanged: it still takes recordType and expectedValue, because a stateless one-shot probe is the one place that shape is still the right question.

Limits

  • We cannot list your DNS records. DNS does not allow it. Without a zone file, a monitor covers your domain's apex and nothing else — a record at staging.example.com is invisible unless that name was in a zone file you pasted.

  • The zone file is a snapshot. It is read once, parsed, and never stored. Names added to your zone afterwards are not tracked until you paste again.

  • One monitor may track at most 85 names. A create carrying more is refused, because a check sweeps every tracked name in one go.

  • Seven record types. SOA, SRV, PTR, DS, DNSKEY, HTTPS and SVCB are not checked.

  • Detection latency is floored by your records' TTL, not by the check interval.

  • One check is one ping, however many names and types it swept.

Every response on this resource carries the standard envelope — data alongside meta, which holds the requestId to quote in a support request and the timestamp the response was written.

The DNS monitor object

json
{
	"id": "dns_abc123",
	"name": "Production domain",
	"domain": "example.com",
	"zoneFileImportedAt": 1786400000000,
	"intervalSeconds": 86400,
	"isEnabled": true,
	"lastCheckedAt": 1786430000000,
	"lastStatus": "ok",
	"registrationStatus": "valid",
	"registrationExpiresAt": 1820000000000,
	"registrar": "Example Registrar, LLC",
	"registrationEppStatuses": ["clientTransferProhibited"],
	"registrationWarningDays": 30,
	"registrationCheckedAt": 1786420000000,
	"registrationError": null,
	"createdAt": 1786300000000,
	"updatedAt": 1786400000000
}

The registration* members and registrar describe the domain's registration, which the monitor looks up at the domain's registry over RDAP once a day:

FieldTypeDescription
registrationStatusstringunknown until the first lookup, then valid, expiring, expired, unavailable (the registry has no record, or no RDAP service) or error (no lookup has succeeded within the warning window)
registrationExpiresAtinteger | nullWhen the registration expires, in milliseconds; null until looked up, or when the registry publishes no date
registrarstring | nullThe registrar's name
registrationEppStatusesstring[]The registry's EPP statuses from the last successful lookup, such as clientTransferProhibited or serverHold
registrationWarningDaysintegerDays before expiry the registration counts as expiring and starts alerting
registrationCheckedAtinteger | nullWhen a lookup last succeeded
registrationErrorstring | nullWhy the last lookup failed, such as not-found, unsupported-tld or rate-limited; null after a success

List All DNS Monitors

GET/api/v1/dns-monitors

Retrieves your team's DNS monitors, newest first.

This endpoint is paginated. See Pagination for how to page through the full list.

Required scope: dns-monitors:read

Query Parameters

  • perPageinteger

    Results per page, 1-200 (default: 50)

  • cursorstring

    Page to fetch, taken from a Link header

Errors (3)
StatusTypeMeaning
400bad-requestThe request is malformed
401unauthorizedThe API key is missing or invalid
403forbiddenThe API key lacks the required scope
Response fields (200)
  • dataobjectRequired
    Show nested fields
    • dnsMonitorsDnsMonitor[]Required
      Show nested fields
      • idstringRequired
      • namestringRequired
      • domainstringRequired
      • zoneFileImportedAtinteger | nullRequired

        When a zone file was last imported; null if only the apex is covered

      • intervalSecondsintegerRequired
      • isEnabledbooleanRequired
      • lastCheckedAtinteger | nullRequired

        Epoch milliseconds

      • lastStatusstring | nullRequired

        One of ok, changed, error.

      • registrationStatusstringRequired

        The domain registration's state from the last RDAP lookup; unavailable when the registry has no record or no RDAP service, error when no lookup has succeeded within the warning window One of unknown, valid, expiring, expired, unavailable, error.

      • registrationExpiresAtinteger | nullRequired

        When the registration expires; null until looked up or when the registry publishes none

      • registrarstring | nullRequired
      • registrationEppStatusesstring[]Required

        EPP status codes from the last successful lookup, such as clientTransferProhibited

      • registrationWarningDaysintegerRequired
      • registrationCheckedAtinteger | nullRequired

        When a registration lookup last succeeded

      • registrationErrorstring | nullRequired

        Why the last lookup failed, such as not-found or rate-limited; null after a success

      • createdAtintegerRequired

        Epoch milliseconds

      • updatedAtintegerRequired

        Epoch milliseconds

  • metaobjectRequired
    Show nested fields
    • requestIdstringRequired

      Format: uuid.

    • timestampstringRequired

      Format: date-time.

    • paginationobjectRequired
      Show nested fields
      • nextstring | nullRequired

        Cursor of the following page

      • prevstring | nullRequired

        Cursor of the preceding page

      • perPageintegerRequired
      • totalinteger
Terminal
curl "https://uptime.sergiodxa.com/api/v1/dns-monitors?perPage=25" \
  -H "Authorization: Bearer uptime_your_api_key"
request.js
let response = await fetch("https://uptime.sergiodxa.com/api/v1/dns-monitors?perPage=25", {
	headers: {
		"Authorization": "Bearer uptime_your_api_key",
	},
});
let result = await response.json();

Response

json
{
	"data": {
		"dnsMonitors": [
			{
				"id": "dns_abc123",
				"name": "Production domain",
				"domain": "example.com",
				"zoneFileImportedAt": 1786400000000,
				"intervalSeconds": 86400,
				"isEnabled": true,
				"lastCheckedAt": 1786430000000,
				"lastStatus": "ok",
				"createdAt": 1786300000000,
				"updatedAt": 1786400000000
			}
		]
	},
	"meta": {
		"requestId": "6b1f9d5e-4a2c-4f7e-9a10-2c8d5f3b7e41",
		"timestamp": "2026-08-11T10:30:00.000Z",
		"pagination": {
			"next": "eyJkIjoiYWZ0ZXIi",
			"prev": null,
			"perPage": 25,
			"total": 31
		}
	}
}

Create a DNS Monitor

POST/api/v1/dns-monitors

Creates a domain monitor and runs discovery immediately: every supported record type is queried at the domain, and at every name a pasted zone file declares.

Everything the resolver answered with is imported and watched. There is no review step on an API call — the dashboard's exists because a human is standing there — so a script that wants something left alone turns it off through the records sub-resource afterwards. The one exception is a record the zone file declares that the resolver does not answer for: it is imported unwatched, for the reason given below.

A team may hold 20 DNS monitors. Past that the create is refused with a 400 limit-exceeded problem, before the monitor row is written and before a single query is sent — one domain monitor sweeps every supported type at every tracked name, so the ceiling is what keeps a team's checks inside the platform's per-invocation budget. Delete a monitor to make room.

The response is 201 Created.

Required scope: dns-monitors:write

Request Body

  • namestringRequired

    Monitor name (1-255 characters)

  • domainstringRequired

    The domain to cover (1-255 characters)

  • zoneFilestring

    A BIND zone file, up to 262144 bytes. Read once, parsed, and never stored.

  • intervalSecondsinteger

    Check interval in seconds (900-86400, default: 86400)

  • isEnabledboolean

    Whether the monitor is checked on its interval (default: true)

  • registrationWarningDaysinteger

    Days before the domain registration expires to start alerting (1-365, default: 30)

The parser reads standard RFC 1035 zone-file syntax: <owner> [<ttl>] [IN] <TYPE> <rdata> records, ; comments, absolute and relative names, @ for the apex, $ORIGIN and $TTL, parenthesised multi-line records, owner-inheriting continuation lines, and quoted TXT character-strings. Anything it cannot use — $INCLUDE, $GENERATE, non-IN classes, untracked types, names outside the domain — is reported, never silently dropped, in discovery.rejectedLines, with the line the entry starts on.

A record the zone file declares but the resolver does not answer is imported unwatched, with status missing. That is a real finding at import — a stale delegation, a change that never published — and a poor standing alert, since the file is a snapshot that only gets older. Enable it through the records sub-resource if you want it watched. On a proxied zone this is the common case rather than the exceptional one: a proxied record is not in public DNS at all.

discovery.queriesFailed counts queries that did not answer. Those names are not covered by this import and no record is inferred from them; the monitor's next scheduled check tries again.

Errors (7)
StatusTypeMeaning
400validation-errorThe request failed validation
400limit-exceededThe team has reached its limit for this resource
400idempotency-key-invalidThe Idempotency-Key header is not valid
401unauthorizedThe API key is missing or invalid
403forbiddenThe API key lacks the required scope
409idempotency-key-in-useA request with this idempotency key is still being processed
422idempotency-key-reusedThis idempotency key was already used for a different request
Response fields (201)
  • dataobjectRequired
    Show nested fields
    • dnsMonitorobjectRequired
      Show nested fields
      • idstringRequired
      • namestringRequired
      • domainstringRequired
      • zoneFileImportedAtinteger | nullRequired

        When a zone file was last imported; null if only the apex is covered

      • intervalSecondsintegerRequired
      • isEnabledbooleanRequired
      • lastCheckedAtinteger | nullRequired

        Epoch milliseconds

      • lastStatusstring | nullRequired

        One of ok, changed, error.

      • registrationStatusstringRequired

        The domain registration's state from the last RDAP lookup; unavailable when the registry has no record or no RDAP service, error when no lookup has succeeded within the warning window One of unknown, valid, expiring, expired, unavailable, error.

      • registrationExpiresAtinteger | nullRequired

        When the registration expires; null until looked up or when the registry publishes none

      • registrarstring | nullRequired
      • registrationEppStatusesstring[]Required

        EPP status codes from the last successful lookup, such as clientTransferProhibited

      • registrationWarningDaysintegerRequired
      • registrationCheckedAtinteger | nullRequired

        When a registration lookup last succeeded

      • registrationErrorstring | nullRequired

        Why the last lookup failed, such as not-found or rate-limited; null after a success

      • createdAtintegerRequired

        Epoch milliseconds

      • updatedAtintegerRequired

        Epoch milliseconds

    • discoveryobjectRequired
      Show nested fields
      • namesintegerRequired

        Names swept: the apex plus every zone-file name

      • recordsImportedintegerRequired
      • queriesFailedintegerRequired

        Queries that did not answer

      • rejectedLinesobject[]Required
        Show nested fields
        • lineintegerRequired
        • reasonstringRequired

          One of includeDirective, generateDirective, unsupportedDirective, nonInternetClass, unsupportedType, outOfZone, malformed.

      • duplicateLinesinteger[]Required

        Lines repeating a record an earlier line declared

  • metaobjectRequired
    Show nested fields
    • requestIdstringRequired

      Format: uuid.

    • timestampstringRequired

      Format: date-time.

Terminal
curl https://uptime.sergiodxa.com/api/v1/dns-monitors \
  -H "Authorization: Bearer uptime_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production domain",
    "domain": "example.com",
    "zoneFile": "www\t1\tIN\tA\t192.0.2.1\n_dmarc\t1\tIN\tTXT\t\"v=DMARC1; p=none\"",
    "intervalSeconds": 86400
  }'
request.js
let response = await fetch("https://uptime.sergiodxa.com/api/v1/dns-monitors", {
	method: "POST",
	headers: {
		"Authorization": "Bearer uptime_your_api_key",
		"Content-Type": "application/json",
	},
	body: JSON.stringify({
		"name": "Production domain",
		"domain": "example.com",
		"zoneFile": "www\t1\tIN\tA\t192.0.2.1\n_dmarc\t1\tIN\tTXT\t\"v=DMARC1; p=none\"",
		"intervalSeconds": 86400
	}),
});
let result = await response.json();

Response

json
{
	"data": {
		"dnsMonitor": {
			"id": "dns_abc123",
			"name": "Production domain",
			"domain": "example.com",
			"zoneFileImportedAt": 1786400000000,
			"intervalSeconds": 86400,
			"isEnabled": true,
			"lastCheckedAt": null,
			"lastStatus": null,
			"createdAt": 1786400000000,
			"updatedAt": 1786400000000
		},
		"discovery": {
			"names": 3,
			"recordsImported": 11,
			"queriesFailed": 0,
			"rejectedLines": [{ "line": 1, "reason": "includeDirective" }],
			"duplicateLines": [14]
		}
	}
}

Get a DNS Monitor

GET/api/v1/dns-monitors/:dnsMonitorId

Retrieves a single DNS monitor by ID.

Required scope: dns-monitors:read

Errors (4)
StatusTypeMeaning
400validation-errorThe request failed validation
401unauthorizedThe API key is missing or invalid
403forbiddenThe API key lacks the required scope
404not-foundThe resource does not exist
Response fields (200)
  • dataobjectRequired
    Show nested fields
    • dnsMonitorobjectRequired
      Show nested fields
      • idstringRequired
      • namestringRequired
      • domainstringRequired
      • zoneFileImportedAtinteger | nullRequired

        When a zone file was last imported; null if only the apex is covered

      • intervalSecondsintegerRequired
      • isEnabledbooleanRequired
      • lastCheckedAtinteger | nullRequired

        Epoch milliseconds

      • lastStatusstring | nullRequired

        One of ok, changed, error.

      • registrationStatusstringRequired

        The domain registration's state from the last RDAP lookup; unavailable when the registry has no record or no RDAP service, error when no lookup has succeeded within the warning window One of unknown, valid, expiring, expired, unavailable, error.

      • registrationExpiresAtinteger | nullRequired

        When the registration expires; null until looked up or when the registry publishes none

      • registrarstring | nullRequired
      • registrationEppStatusesstring[]Required

        EPP status codes from the last successful lookup, such as clientTransferProhibited

      • registrationWarningDaysintegerRequired
      • registrationCheckedAtinteger | nullRequired

        When a registration lookup last succeeded

      • registrationErrorstring | nullRequired

        Why the last lookup failed, such as not-found or rate-limited; null after a success

      • createdAtintegerRequired

        Epoch milliseconds

      • updatedAtintegerRequired

        Epoch milliseconds

  • metaobjectRequired
    Show nested fields
    • requestIdstringRequired

      Format: uuid.

    • timestampstringRequired

      Format: date-time.

Terminal
curl https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123 \
  -H "Authorization: Bearer uptime_your_api_key"
request.js
let response = await fetch("https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123", {
	headers: {
		"Authorization": "Bearer uptime_your_api_key",
	},
});
let result = await response.json();

Response

json
{
	"data": {
		"dnsMonitor": {
			"id": "dns_abc123",
			"name": "Production domain",
			"domain": "example.com",
			"zoneFileImportedAt": 1786400000000,
			"intervalSeconds": 86400,
			"isEnabled": true,
			"lastCheckedAt": 1786430000000,
			"lastStatus": "ok",
			"createdAt": 1786300000000,
			"updatedAt": 1786400000000
		}
	}
}

Update a DNS Monitor

PATCH/api/v1/dns-monitors/:dnsMonitorId

Updates a DNS monitor with a JSON merge patch: send only the fields you want to change.

There is no zoneFile here, and one sent is ignored rather than refused. The pasted text is never stored, so re-importing is a deliberate act rather than something carried along by a rename — import again from the dashboard, or create a new monitor.

Required scope: dns-monitors:write

Request Body

  • namestring

    Monitor name (1-255 characters)

  • domainstring

    The domain this monitor covers (1-255 characters)

  • intervalSecondsinteger

    Check interval in seconds (900-86400)

  • isEnabledboolean

    Whether the monitor is checked on its interval

  • registrationWarningDaysinteger

    Days before the domain registration expires to start alerting (1-365)

The limits are the ones Create a DNS Monitor applies. null resets intervalSeconds to 86400, isEnabled to true and registrationWarningDays to 30; name and domain cannot be removed, so null on either is a 400 validation-error.

A new registrationWarningDays makes the registration due for a lookup on the next hourly sweep, so registrationStatus reflects the new window within the hour.

Changing domain does not re-run discovery: the records already tracked stay as they are, so point a monitor at a different domain only if you also mean to review its records.

Update With PUT

PUT /api/v1/dns-monitors/:dnsMonitorId takes the same fields, for integrations written before PATCH existed. A field you leave out keeps its value, null is refused, and sending isEnabled schedules the next check again on every request, even when the value is unchanged.

Required scope: dns-monitors:write

Errors (4)
StatusTypeMeaning
400validation-errorThe request failed validation
401unauthorizedThe API key is missing or invalid
403forbiddenThe API key lacks the required scope
404not-foundThe resource does not exist
Response fields (200)
  • dataobjectRequired
    Show nested fields
    • dnsMonitorobjectRequired
      Show nested fields
      • idstringRequired
      • namestringRequired
      • domainstringRequired
      • zoneFileImportedAtinteger | nullRequired

        When a zone file was last imported; null if only the apex is covered

      • intervalSecondsintegerRequired
      • isEnabledbooleanRequired
      • lastCheckedAtinteger | nullRequired

        Epoch milliseconds

      • lastStatusstring | nullRequired

        One of ok, changed, error.

      • registrationStatusstringRequired

        The domain registration's state from the last RDAP lookup; unavailable when the registry has no record or no RDAP service, error when no lookup has succeeded within the warning window One of unknown, valid, expiring, expired, unavailable, error.

      • registrationExpiresAtinteger | nullRequired

        When the registration expires; null until looked up or when the registry publishes none

      • registrarstring | nullRequired
      • registrationEppStatusesstring[]Required

        EPP status codes from the last successful lookup, such as clientTransferProhibited

      • registrationWarningDaysintegerRequired
      • registrationCheckedAtinteger | nullRequired

        When a registration lookup last succeeded

      • registrationErrorstring | nullRequired

        Why the last lookup failed, such as not-found or rate-limited; null after a success

      • createdAtintegerRequired

        Epoch milliseconds

      • updatedAtintegerRequired

        Epoch milliseconds

  • metaobjectRequired
    Show nested fields
    • requestIdstringRequired

      Format: uuid.

    • timestampstringRequired

      Format: date-time.

Terminal
curl https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123 \
  -X PATCH \
  -H "Authorization: Bearer uptime_your_api_key" \
  -H "Content-Type: application/merge-patch+json" \
  -d '{ "intervalSeconds": 3600 }'
request.js
let response = await fetch("https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123", {
	method: "PATCH",
	headers: {
		"Authorization": "Bearer uptime_your_api_key",
		"Content-Type": "application/merge-patch+json",
	},
	body: JSON.stringify({
		"intervalSeconds": 3600
	}),
});
let result = await response.json();

Response

json
{
	"data": {
		"dnsMonitor": {
			"id": "dns_abc123",
			"name": "Production domain",
			"domain": "example.com",
			"zoneFileImportedAt": 1786400000000,
			"intervalSeconds": 3600,
			"isEnabled": true,
			"lastCheckedAt": 1786430000000,
			"lastStatus": "ok",
			"createdAt": 1786300000000,
			"updatedAt": 1786440000000
		}
	}
}

Delete a DNS Monitor

DELETE/api/v1/dns-monitors/:dnsMonitorId

Permanently deletes a DNS monitor, every record it tracks, and its check history.

Required scope: dns-monitors:delete

Errors (4)
StatusTypeMeaning
400validation-errorThe request failed validation
401unauthorizedThe API key is missing or invalid
403forbiddenThe API key lacks the required scope
404not-foundThe resource does not exist
Response fields (200)
  • dataobjectRequired
    Show nested fields
    • deletedtrueRequired
  • metaobjectRequired
    Show nested fields
    • requestIdstringRequired

      Format: uuid.

    • timestampstringRequired

      Format: date-time.

Terminal
curl https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123 \
  -X DELETE \
  -H "Authorization: Bearer uptime_your_api_key"
request.js
let response = await fetch("https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123", {
	method: "DELETE",
	headers: {
		"Authorization": "Bearer uptime_your_api_key",
	},
});
let result = await response.json();

Response

json
{ "data": { "deleted": true } }

Get DNS Monitor Results

GET/api/v1/dns-monitors/:dnsMonitorId/results

Retrieves the check history for a DNS monitor: one row per check of the monitor, whatever the sweep cost in queries.

Results arrive newest first, a page at a time. See Pagination for how to walk the whole history.

Required scope: dns-monitors:read

Query Parameters

  • perPageinteger

    Results per page, 1-200 (default: 50)

  • cursorstring

    Page to fetch, taken from a Link header

responseTimeMs is the slowest single query in the sweep, not the sum: it answers "how long did DNS take to answer", which is a latency figure rather than a cost one.

A value edited inside a record set holding several values reads as one missing record plus one new one. That is truthful rather than a bug: DNS gives an individual record no identity of its own, so editing one value is indistinguishable, on the wire, from removing one and adding another. recordsChanged counts only the case a diff can attribute without guessing — a name and type holding exactly one watched record and answering with exactly one differing value.

Errors (5)
StatusTypeMeaning
400bad-requestThe request is malformed
400validation-errorThe request failed validation
401unauthorizedThe API key is missing or invalid
403forbiddenThe API key lacks the required scope
404not-foundThe resource does not exist
Response fields (200)
  • dataobjectRequired
    Show nested fields
    • resultsDnsMonitorResult[]Required
      Show nested fields
      • idstringRequired
      • statusstringRequired

        One of ok, changed, error.

      • recordsCheckedintegerRequired
      • recordsChangedintegerRequired
      • recordsMissingintegerRequired
      • recordsNewintegerRequired
      • queriesFailedintegerRequired
      • responseTimeMsinteger | nullRequired

        The slowest single query

      • errorMessagestring | nullRequired
      • checkedAtintegerRequired

        Epoch milliseconds

  • metaobjectRequired
    Show nested fields
    • requestIdstringRequired

      Format: uuid.

    • timestampstringRequired

      Format: date-time.

    • paginationobjectRequired
      Show nested fields
      • nextstring | nullRequired

        Cursor of the following page

      • prevstring | nullRequired

        Cursor of the preceding page

      • perPageintegerRequired
      • totalinteger
Terminal
curl -i "https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123/results?perPage=10" \
  -H "Authorization: Bearer uptime_your_api_key"

Response

json
{
	"data": {
		"results": [
			{
				"id": "res_xyz789",
				"status": "changed",
				"recordsChecked": 18,
				"recordsChanged": 0,
				"recordsMissing": 1,
				"recordsNew": 1,
				"queriesFailed": 0,
				"responseTimeMs": 42,
				"errorMessage": null,
				"checkedAt": 1786430000000
			}
		]
	},
	"meta": {
		"requestId": "6b1f9d5e-4a2c-4f7e-9a10-2c8d5f3b7e41",
		"timestamp": "2026-08-11T10:30:00.000Z",
		"pagination": {
			"next": "eyJkIjoiYWZ0ZXIi",
			"prev": null,
			"perPage": 10
		}
	}
}

Records

Which records a monitor watches is edited through its records sub-resource, on the same dns-monitors:read/dns-monitors:write scopes — a key that may reconfigure a domain monitor may decide which of its records are watched, since the two authorities are the same authority.

GET   /api/v1/dns-monitors/:dnsMonitorId/records
PATCH /api/v1/dns-monitors/:dnsMonitorId/records/:recordId

A record carries its name, recordType, value, how it was discovered (source), whether a deviation from it alerts (isEnabled), and its status (ok, changed, missing, new, error). PATCH takes { "isEnabled": boolean }.

Two things about that table are worth knowing before you script against it:

  • It holds everything we have ever seen for the domain, including records you turned off. Turning one off says only that deviations from it do not alert; deleting the row instead would make the next check rediscover it as new, forever.

  • A record discovered by a check is stored unwatched, with status new. Accepting something that appeared without you putting it there should be an act, not the thing that happens by ignoring an email.

List DNS Monitor Records

GET/api/v1/dns-monitors/:dnsMonitorId/records

Retrieves every record the monitor tracks, including the ones you have declined to watch. The table is the complete set of everything ever seen for the domain: isEnabled says only whether a deviation from that record alerts, so a declined record stays listed rather than being rediscovered as new on the next check.

Required scope: dns-monitors:read

Records read alphabetically by name, then by recordType, then by value — the order you review a zone in — and arrive a page at a time. See Pagination for how to walk the whole table.

Query Parameters

  • perPageinteger

    Results per page, 1-200 (default: 50)

  • cursorstring

    Page to fetch, taken from a Link header

Field Meanings

  • namestring

    Absolute owner name, lowercased, no trailing dot. The apex is the monitor's domain.

  • recordTypeA` `AAAA` `CNAME` `MX` `TXT` `NS` `CAA` `TLSA` `DS` `SVCB

    The ten types checked.

  • valuestring

    Normalized RDATA. Together with name and recordType it is the record's identity, and it is never client-writable.

  • sourceresolver` `zone_file

    How the record first entered the table.

  • isEnabledboolean

    Whether a deviation from this record alerts. The only writable field.

  • statusok` `changed` `missing` `new` `error

    What the last check found. new and missing are states of the record, not of a check.

  • lastSeenAtinteger | null

    Last check at which this exact record resolved. null for a zone-file record that has never resolved.

  • lastCheckedAtinteger | null

    Last check that had an answer about this record. null until the first check.

Timestamps are epoch milliseconds.

Ownership

A monitor belonging to another team returns 404, not 403: a 403 would confirm the id names a real monitor somebody else owns.

Errors (5)
StatusTypeMeaning
400bad-requestThe request is malformed
400validation-errorThe request failed validation
401unauthorizedThe API key is missing or invalid
403forbiddenThe API key lacks the required scope
404not-foundThe resource does not exist
Response fields (200)
  • dataobjectRequired
    Show nested fields
    • recordsDnsMonitorRecord[]Required
      Show nested fields
      • idstringRequired
      • dnsMonitorIdstringRequired
      • namestringRequired
      • recordTypestringRequired

        One of A, AAAA, CNAME, MX, TXT, NS, CAA, TLSA, DS, SVCB.

      • valuestringRequired
      • sourcestringRequired

        One of resolver, zone_file.

      • isEnabledbooleanRequired

        Whether a deviation from this record alerts

      • statusstringRequired

        One of ok, changed, missing, new, error.

      • firstSeenAtintegerRequired

        Epoch milliseconds

      • lastSeenAtinteger | nullRequired

        Last check that resolved this record; null for zone-file-only records

      • lastCheckedAtinteger | nullRequired

        Epoch milliseconds

      • createdAtintegerRequired

        Epoch milliseconds

      • updatedAtintegerRequired

        Epoch milliseconds

  • metaobjectRequired
    Show nested fields
    • requestIdstringRequired

      Format: uuid.

    • timestampstringRequired

      Format: date-time.

    • paginationobjectRequired
      Show nested fields
      • nextstring | nullRequired

        Cursor of the following page

      • prevstring | nullRequired

        Cursor of the preceding page

      • perPageintegerRequired
      • totalinteger
Terminal
curl -i "https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123/records?perPage=50" \
  -H "Authorization: Bearer uptime_your_api_key"

Response

json
{
	"data": {
		"records": [
			{
				"id": "dnsrec_abc123",
				"dnsMonitorId": "dns_abc123",
				"name": "example.com",
				"recordType": "A",
				"value": "192.0.2.1",
				"source": "resolver",
				"isEnabled": true,
				"status": "ok",
				"firstSeenAt": 1770000000000,
				"lastSeenAt": 1770086400000,
				"lastCheckedAt": 1770086400000,
				"createdAt": 1770000000000,
				"updatedAt": 1770086400000
			},
			{
				"id": "dnsrec_abc124",
				"dnsMonitorId": "dns_abc123",
				"name": "mail.example.com",
				"recordType": "MX",
				"value": "10 mx.example.com",
				"source": "zone_file",
				"isEnabled": false,
				"status": "missing",
				"firstSeenAt": 1770086400000,
				"lastSeenAt": null,
				"lastCheckedAt": 1770086400000,
				"createdAt": 1770086400000,
				"updatedAt": 1770086400000
			}
		]
	},
	"meta": {
		"requestId": "6b1f9d5e-4a2c-4f7e-9a10-2c8d5f3b7e41",
		"timestamp": "2026-08-11T10:30:00.000Z",
		"pagination": {
			"next": "eyJkIjoiYWZ0ZXIi",
			"prev": null,
			"perPage": 50,
			"total": 86
		}
	}
}

Update a DNS Monitor Record

PATCH/api/v1/dns-monitors/:dnsMonitorId/records/:recordId

Enables or declines one record. This is how a script decides what a monitor watches: an API-created monitor imports and enables everything discovery found, because there is no reviewer standing at the other end of an API call.

Required scope: dns-monitors:write

Request Body

Send the body as application/merge-patch+json or application/json; both read the same.

json
{
	"isEnabled": false
}

Accepted Fields

  • isEnabledbooleanRequired

    Whether a deviation from this record should alert

isEnabled is the only accepted field, and the request is rejected rather than filtered. Sending name, recordType, value, status, or any other key returns a 400 validation-error problem with a message naming the key — for example value: Unknown key.

A record's identity is (name, recordType, value), and it comes from DNS or from the zone file you imported, never from a client. It is also the key the check diffs on, so accepting an edit to it would silently retarget the expectation instead of changing it — and a request that was quietly ignored is worse than one that was refused, because the caller goes on believing the edit landed. Change your DNS, or re-import your zone file.

Omitting isEnabled, or sending it as null, is also a 400: a request with nothing writable in it expresses no decision, and answering 200 would claim one was made.

Enabling a record whose status is new also settles it to ok: it resolved when we found it, and you have now said you want it watched, so leaving it new would keep it on the "needs your attention" list forever. Every other status survives — enabling a zone-file record that has never resolved leaves it missing, which is true and is presumably why you enabled it. Declining a record never changes its status.

Ownership

A record id belonging to a different monitor — including one on your own team — returns 404. Records are addressable only through the monitor that tracks them.

Errors (4)
StatusTypeMeaning
400validation-errorThe request failed validation
401unauthorizedThe API key is missing or invalid
403forbiddenThe API key lacks the required scope
404not-foundThe resource does not exist
Response fields (200)
  • dataobjectRequired
    Show nested fields
    • recordobjectRequired
      Show nested fields
      • idstringRequired
      • dnsMonitorIdstringRequired
      • namestringRequired
      • recordTypestringRequired

        One of A, AAAA, CNAME, MX, TXT, NS, CAA, TLSA, DS, SVCB.

      • valuestringRequired
      • sourcestringRequired

        One of resolver, zone_file.

      • isEnabledbooleanRequired

        Whether a deviation from this record alerts

      • statusstringRequired

        One of ok, changed, missing, new, error.

      • firstSeenAtintegerRequired

        Epoch milliseconds

      • lastSeenAtinteger | nullRequired

        Last check that resolved this record; null for zone-file-only records

      • lastCheckedAtinteger | nullRequired

        Epoch milliseconds

      • createdAtintegerRequired

        Epoch milliseconds

      • updatedAtintegerRequired

        Epoch milliseconds

  • metaobjectRequired
    Show nested fields
    • requestIdstringRequired

      Format: uuid.

    • timestampstringRequired

      Format: date-time.

Terminal
curl -X PATCH "https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123/records/dnsrec_abc124" \
  -H "Authorization: Bearer uptime_your_api_key" \
  -H "Content-Type: application/merge-patch+json" \
  -d '{"isEnabled": true}'
request.js
let response = await fetch("https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123/records/dnsrec_abc124", {
	method: "PATCH",
	headers: {
		"Authorization": "Bearer uptime_your_api_key",
		"Content-Type": "application/merge-patch+json",
	},
	body: JSON.stringify({
		"isEnabled": true
	}),
});
let result = await response.json();

Response

json
{
	"data": {
		"record": {
			"id": "dnsrec_abc124",
			"dnsMonitorId": "dns_abc123",
			"name": "mail.example.com",
			"recordType": "MX",
			"value": "10 mx.example.com",
			"source": "zone_file",
			"isEnabled": true,
			"status": "ok",
			"firstSeenAt": 1770086400000,
			"lastSeenAt": 1770086400000,
			"lastCheckedAt": 1770086400000,
			"createdAt": 1770086400000,
			"updatedAt": 1770090000000
		}
	},
	"meta": {
		"requestId": "6b1f9d5e-4a2c-4f7e-9a10-2c8d5f3b7e41",
		"timestamp": "2026-08-11T10:30:00.000Z"
	}
}
Documentation