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.
| Removed | Replacement |
|---|---|
recordType on create/update/read | Every supported type is swept. Per-record control lives in the records sub-resource. |
expectedValue on create/update/read | The expectation is imported, not typed. Nothing to transcribe. |
lastValue on read | Per-record values, on the records sub-resource. |
resolvedValue on results | Counters: recordsChecked, recordsChanged, recordsMissing, recordsNew, queriesFailed. |
intervalSeconds as low as 60 | The 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.comis 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,HTTPSandSVCBare 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
{
"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:
| Field | Type | Description |
|---|---|---|
registrationStatus | string | unknown 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) |
registrationExpiresAt | integer | null | When the registration expires, in milliseconds; null until looked up, or when the registry publishes no date |
registrar | string | null | The registrar's name |
registrationEppStatuses | string[] | The registry's EPP statuses from the last successful lookup, such as clientTransferProhibited or serverHold |
registrationWarningDays | integer | Days before expiry the registration counts as expiring and starts alerting |
registrationCheckedAt | integer | null | When a lookup last succeeded |
registrationError | string | null | Why the last lookup failed, such as not-found, unsupported-tld or rate-limited; null after a success |
List All DNS Monitors
/api/v1/dns-monitorsRetrieves 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
perPageintegerResults per page, 1-200 (default: 50)
cursorstringPage to fetch, taken from a
Linkheader
Errors (3)
| Status | Type | Meaning |
|---|---|---|
| 400 | bad-request | The request is malformed |
| 401 | unauthorized | The API key is missing or invalid |
| 403 | forbidden | The API key lacks the required scope |
Response fields (200)
dataobjectRequiredShow nested fields
dnsMonitorsDnsMonitor[]RequiredShow nested fields
idstringRequirednamestringRequireddomainstringRequiredzoneFileImportedAtinteger | nullRequiredWhen a zone file was last imported; null if only the apex is covered
intervalSecondsintegerRequiredisEnabledbooleanRequiredlastCheckedAtinteger | nullRequiredEpoch milliseconds
lastStatusstring | nullRequiredOne of
ok,changed,error.registrationStatusstringRequiredThe domain registration's state from the last RDAP lookup;
unavailablewhen the registry has no record or no RDAP service,errorwhen no lookup has succeeded within the warning window One ofunknown,valid,expiring,expired,unavailable,error.registrationExpiresAtinteger | nullRequiredWhen the registration expires; null until looked up or when the registry publishes none
registrarstring | nullRequiredregistrationEppStatusesstring[]RequiredEPP status codes from the last successful lookup, such as
clientTransferProhibitedregistrationWarningDaysintegerRequiredregistrationCheckedAtinteger | nullRequiredWhen a registration lookup last succeeded
registrationErrorstring | nullRequiredWhy the last lookup failed, such as
not-foundorrate-limited; null after a successcreatedAtintegerRequiredEpoch milliseconds
updatedAtintegerRequiredEpoch milliseconds
metaobjectRequiredShow nested fields
requestIdstringRequiredFormat: uuid.
timestampstringRequiredFormat: date-time.
paginationobjectRequiredShow nested fields
nextstring | nullRequiredCursor of the following page
prevstring | nullRequiredCursor of the preceding page
perPageintegerRequiredtotalinteger
Response
{
"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
/api/v1/dns-monitorsCreates 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
namestringRequiredMonitor name (1-255 characters)
domainstringRequiredThe domain to cover (1-255 characters)
zoneFilestringA BIND zone file, up to 262144 bytes. Read once, parsed, and never stored.
intervalSecondsintegerCheck interval in seconds (900-86400, default: 86400)
isEnabledbooleanWhether the monitor is checked on its interval (default: true)
registrationWarningDaysintegerDays 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)
| Status | Type | Meaning |
|---|---|---|
| 400 | validation-error | The request failed validation |
| 400 | limit-exceeded | The team has reached its limit for this resource |
| 400 | idempotency-key-invalid | The Idempotency-Key header is not valid |
| 401 | unauthorized | The API key is missing or invalid |
| 403 | forbidden | The API key lacks the required scope |
| 409 | idempotency-key-in-use | A request with this idempotency key is still being processed |
| 422 | idempotency-key-reused | This idempotency key was already used for a different request |
Response fields (201)
dataobjectRequiredShow nested fields
dnsMonitorobjectRequiredShow nested fields
idstringRequirednamestringRequireddomainstringRequiredzoneFileImportedAtinteger | nullRequiredWhen a zone file was last imported; null if only the apex is covered
intervalSecondsintegerRequiredisEnabledbooleanRequiredlastCheckedAtinteger | nullRequiredEpoch milliseconds
lastStatusstring | nullRequiredOne of
ok,changed,error.registrationStatusstringRequiredThe domain registration's state from the last RDAP lookup;
unavailablewhen the registry has no record or no RDAP service,errorwhen no lookup has succeeded within the warning window One ofunknown,valid,expiring,expired,unavailable,error.registrationExpiresAtinteger | nullRequiredWhen the registration expires; null until looked up or when the registry publishes none
registrarstring | nullRequiredregistrationEppStatusesstring[]RequiredEPP status codes from the last successful lookup, such as
clientTransferProhibitedregistrationWarningDaysintegerRequiredregistrationCheckedAtinteger | nullRequiredWhen a registration lookup last succeeded
registrationErrorstring | nullRequiredWhy the last lookup failed, such as
not-foundorrate-limited; null after a successcreatedAtintegerRequiredEpoch milliseconds
updatedAtintegerRequiredEpoch milliseconds
discoveryobjectRequiredShow nested fields
namesintegerRequiredNames swept: the apex plus every zone-file name
recordsImportedintegerRequiredqueriesFailedintegerRequiredQueries that did not answer
rejectedLinesobject[]RequiredShow nested fields
lineintegerRequiredreasonstringRequiredOne of
includeDirective,generateDirective,unsupportedDirective,nonInternetClass,unsupportedType,outOfZone,malformed.
duplicateLinesinteger[]RequiredLines repeating a record an earlier line declared
metaobjectRequiredShow nested fields
requestIdstringRequiredFormat: uuid.
timestampstringRequiredFormat: date-time.
Response
{
"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
/api/v1/dns-monitors/:dnsMonitorIdRetrieves a single DNS monitor by ID.
Required scope: dns-monitors:read
Errors (4)
| Status | Type | Meaning |
|---|---|---|
| 400 | validation-error | The request failed validation |
| 401 | unauthorized | The API key is missing or invalid |
| 403 | forbidden | The API key lacks the required scope |
| 404 | not-found | The resource does not exist |
Response fields (200)
dataobjectRequiredShow nested fields
dnsMonitorobjectRequiredShow nested fields
idstringRequirednamestringRequireddomainstringRequiredzoneFileImportedAtinteger | nullRequiredWhen a zone file was last imported; null if only the apex is covered
intervalSecondsintegerRequiredisEnabledbooleanRequiredlastCheckedAtinteger | nullRequiredEpoch milliseconds
lastStatusstring | nullRequiredOne of
ok,changed,error.registrationStatusstringRequiredThe domain registration's state from the last RDAP lookup;
unavailablewhen the registry has no record or no RDAP service,errorwhen no lookup has succeeded within the warning window One ofunknown,valid,expiring,expired,unavailable,error.registrationExpiresAtinteger | nullRequiredWhen the registration expires; null until looked up or when the registry publishes none
registrarstring | nullRequiredregistrationEppStatusesstring[]RequiredEPP status codes from the last successful lookup, such as
clientTransferProhibitedregistrationWarningDaysintegerRequiredregistrationCheckedAtinteger | nullRequiredWhen a registration lookup last succeeded
registrationErrorstring | nullRequiredWhy the last lookup failed, such as
not-foundorrate-limited; null after a successcreatedAtintegerRequiredEpoch milliseconds
updatedAtintegerRequiredEpoch milliseconds
metaobjectRequiredShow nested fields
requestIdstringRequiredFormat: uuid.
timestampstringRequiredFormat: date-time.
Response
{
"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
/api/v1/dns-monitors/:dnsMonitorIdUpdates 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
namestringMonitor name (1-255 characters)
domainstringThe domain this monitor covers (1-255 characters)
intervalSecondsintegerCheck interval in seconds (900-86400)
isEnabledbooleanWhether the monitor is checked on its interval
registrationWarningDaysintegerDays 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)
| Status | Type | Meaning |
|---|---|---|
| 400 | validation-error | The request failed validation |
| 401 | unauthorized | The API key is missing or invalid |
| 403 | forbidden | The API key lacks the required scope |
| 404 | not-found | The resource does not exist |
Response fields (200)
dataobjectRequiredShow nested fields
dnsMonitorobjectRequiredShow nested fields
idstringRequirednamestringRequireddomainstringRequiredzoneFileImportedAtinteger | nullRequiredWhen a zone file was last imported; null if only the apex is covered
intervalSecondsintegerRequiredisEnabledbooleanRequiredlastCheckedAtinteger | nullRequiredEpoch milliseconds
lastStatusstring | nullRequiredOne of
ok,changed,error.registrationStatusstringRequiredThe domain registration's state from the last RDAP lookup;
unavailablewhen the registry has no record or no RDAP service,errorwhen no lookup has succeeded within the warning window One ofunknown,valid,expiring,expired,unavailable,error.registrationExpiresAtinteger | nullRequiredWhen the registration expires; null until looked up or when the registry publishes none
registrarstring | nullRequiredregistrationEppStatusesstring[]RequiredEPP status codes from the last successful lookup, such as
clientTransferProhibitedregistrationWarningDaysintegerRequiredregistrationCheckedAtinteger | nullRequiredWhen a registration lookup last succeeded
registrationErrorstring | nullRequiredWhy the last lookup failed, such as
not-foundorrate-limited; null after a successcreatedAtintegerRequiredEpoch milliseconds
updatedAtintegerRequiredEpoch milliseconds
metaobjectRequiredShow nested fields
requestIdstringRequiredFormat: uuid.
timestampstringRequiredFormat: date-time.
Response
{
"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
/api/v1/dns-monitors/:dnsMonitorIdPermanently deletes a DNS monitor, every record it tracks, and its check history.
Required scope: dns-monitors:delete
Errors (4)
| Status | Type | Meaning |
|---|---|---|
| 400 | validation-error | The request failed validation |
| 401 | unauthorized | The API key is missing or invalid |
| 403 | forbidden | The API key lacks the required scope |
| 404 | not-found | The resource does not exist |
Response fields (200)
dataobjectRequiredShow nested fields
deletedtrueRequired
metaobjectRequiredShow nested fields
requestIdstringRequiredFormat: uuid.
timestampstringRequiredFormat: date-time.
Response
{ "data": { "deleted": true } }Get DNS Monitor Results
/api/v1/dns-monitors/:dnsMonitorId/resultsRetrieves 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
perPageintegerResults per page, 1-200 (default: 50)
cursorstringPage to fetch, taken from a
Linkheader
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)
| Status | Type | Meaning |
|---|---|---|
| 400 | bad-request | The request is malformed |
| 400 | validation-error | The request failed validation |
| 401 | unauthorized | The API key is missing or invalid |
| 403 | forbidden | The API key lacks the required scope |
| 404 | not-found | The resource does not exist |
Response fields (200)
dataobjectRequiredShow nested fields
resultsDnsMonitorResult[]RequiredShow nested fields
idstringRequiredstatusstringRequiredOne of
ok,changed,error.recordsCheckedintegerRequiredrecordsChangedintegerRequiredrecordsMissingintegerRequiredrecordsNewintegerRequiredqueriesFailedintegerRequiredresponseTimeMsinteger | nullRequiredThe slowest single query
errorMessagestring | nullRequiredcheckedAtintegerRequiredEpoch milliseconds
metaobjectRequiredShow nested fields
requestIdstringRequiredFormat: uuid.
timestampstringRequiredFormat: date-time.
paginationobjectRequiredShow nested fields
nextstring | nullRequiredCursor of the following page
prevstring | nullRequiredCursor of the preceding page
perPageintegerRequiredtotalinteger
curl -i "https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123/results?perPage=10" \
-H "Authorization: Bearer uptime_your_api_key"Response
{
"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/:recordIdA 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
/api/v1/dns-monitors/:dnsMonitorId/recordsRetrieves 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
perPageintegerResults per page, 1-200 (default: 50)
cursorstringPage to fetch, taken from a
Linkheader
Field Meanings
namestringAbsolute owner name, lowercased, no trailing dot. The apex is the monitor's
domain.recordTypeA` `AAAA` `CNAME` `MX` `TXT` `NS` `CAA` `TLSA` `DS` `SVCBThe ten types checked.
valuestringNormalized RDATA. Together with
nameandrecordTypeit is the record's identity, and it is never client-writable.sourceresolver` `zone_fileHow the record first entered the table.
isEnabledbooleanWhether a deviation from this record alerts. The only writable field.
statusok` `changed` `missing` `new` `errorWhat the last check found.
newandmissingare states of the record, not of a check.lastSeenAtinteger | nullLast check at which this exact record resolved.
nullfor a zone-file record that has never resolved.lastCheckedAtinteger | nullLast check that had an answer about this record.
nulluntil 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)
| Status | Type | Meaning |
|---|---|---|
| 400 | bad-request | The request is malformed |
| 400 | validation-error | The request failed validation |
| 401 | unauthorized | The API key is missing or invalid |
| 403 | forbidden | The API key lacks the required scope |
| 404 | not-found | The resource does not exist |
Response fields (200)
dataobjectRequiredShow nested fields
recordsDnsMonitorRecord[]RequiredShow nested fields
idstringRequireddnsMonitorIdstringRequirednamestringRequiredrecordTypestringRequiredOne of
A,AAAA,CNAME,MX,TXT,NS,CAA,TLSA,DS,SVCB.valuestringRequiredsourcestringRequiredOne of
resolver,zone_file.isEnabledbooleanRequiredWhether a deviation from this record alerts
statusstringRequiredOne of
ok,changed,missing,new,error.firstSeenAtintegerRequiredEpoch milliseconds
lastSeenAtinteger | nullRequiredLast check that resolved this record; null for zone-file-only records
lastCheckedAtinteger | nullRequiredEpoch milliseconds
createdAtintegerRequiredEpoch milliseconds
updatedAtintegerRequiredEpoch milliseconds
metaobjectRequiredShow nested fields
requestIdstringRequiredFormat: uuid.
timestampstringRequiredFormat: date-time.
paginationobjectRequiredShow nested fields
nextstring | nullRequiredCursor of the following page
prevstring | nullRequiredCursor of the preceding page
perPageintegerRequiredtotalinteger
curl -i "https://uptime.sergiodxa.com/api/v1/dns-monitors/dns_abc123/records?perPage=50" \
-H "Authorization: Bearer uptime_your_api_key"Response
{
"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
/api/v1/dns-monitors/:dnsMonitorId/records/:recordIdEnables 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.
{
"isEnabled": false
}Accepted Fields
isEnabledbooleanRequiredWhether 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)
| Status | Type | Meaning |
|---|---|---|
| 400 | validation-error | The request failed validation |
| 401 | unauthorized | The API key is missing or invalid |
| 403 | forbidden | The API key lacks the required scope |
| 404 | not-found | The resource does not exist |
Response fields (200)
dataobjectRequiredShow nested fields
recordobjectRequiredShow nested fields
idstringRequireddnsMonitorIdstringRequirednamestringRequiredrecordTypestringRequiredOne of
A,AAAA,CNAME,MX,TXT,NS,CAA,TLSA,DS,SVCB.valuestringRequiredsourcestringRequiredOne of
resolver,zone_file.isEnabledbooleanRequiredWhether a deviation from this record alerts
statusstringRequiredOne of
ok,changed,missing,new,error.firstSeenAtintegerRequiredEpoch milliseconds
lastSeenAtinteger | nullRequiredLast check that resolved this record; null for zone-file-only records
lastCheckedAtinteger | nullRequiredEpoch milliseconds
createdAtintegerRequiredEpoch milliseconds
updatedAtintegerRequiredEpoch milliseconds
metaobjectRequiredShow nested fields
requestIdstringRequiredFormat: uuid.
timestampstringRequiredFormat: date-time.
Response
{
"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"
}
}