This API is currently in beta and is subject to breaking changes without notice (rare, if any).
We are actively working on it and we expect it to be production-ready in the following weeks.
We welcome bug reports and feedback at https://help.nextdns.io.
Enjoy!
https://api.nextdns.io/...
Pass your API key via the X-Api-Key
header for every calls:
X-Api-Key: a8f4e42e896ff37f181e3e8a42a9737e1423d8e7
💡 Find your API key at the bottom of your account page (https://my.nextdns.io/account).
All API responses with 200 or 400 status follow one of the following formats:
{
"data": {...}, // or [...], depending on the endpoint
"meta": {...} // optional
}
{
"errors": [...]
}
The API will respond with a 400 Bad Request
and { "errors": [...] }
on client error:
query
arguments.The API will respond with a 200 OK
and { "errors": [...] }
on user error:
The errors
array will contain at least one Error
object with the following format:
{
"code": "invalid",
"detail": "\"HeLlO\" is not an integer.",
"source": {
"parameter": "limit" // for query string parameters, or "pointer" when the error is in the body.
}
}
Most endpoints that return an array for the data
key are paginated.
Cursors are strings returned in the meta
section of the response and should be passed as query string argument cursor
to get the next page:
{
"data": [...],
"meta": {
"pagination": {
"cursor": "j2k3zl3b4v"
}
}
}
// get the next page with /endpoint?...&cursor=j2k3zl3b4v
Use the limit
query string argument to adjust the number of results per page (e.g. /endpoint?...&limit=50
).
cursor
will be null
when there is no more page.
A entire profile can be represented with the following JSON:
{
"name": "My profile",
"security": {
"threatIntelligenceFeeds": true,
"aiThreatDetection": true,
"googleSafeBrowsing": true,
"cryptojacking": true,
"dnsRebinding": true,
"idnHomographs": true,
"typosquatting": true,
"dga": true,
"nrd": true,
"ddns": true,
"parking": true,
"csam": true,
"tlds": [
{
"id": "ru"
},
{
"id": "cn"
},
{
"id": "cf"
},
{
"id": "accountants"
}
]
},
"privacy": {
"blocklists": [
{
"id": "nextdns-recommended"
},
{
"id": "oisd"
}
],
"natives": [
{
"id": "huawei"
},
{
"id": "samsung"
}
],
"disguisedTrackers": true,
"allowAffiliate": true
},
"parentalControl": {
"services": [
{
"id": "tiktok",
"active": true
},
{
"id": "facebook",
"active": false
}
],
"categories": [
{
"id": "porn",
"active": true
},
{
"id": "social-networks",
"active": false
}
],
"safeSearch": true,
"youtubeRestrictedMode": true,
"blockBypass": false
},
"denylist": [
{
"id": "badwebsite.com",
"active": true
},
{
"id": "pornhub.com",
"active": false
}
],
"allowlist": [
{
"id": "goodwebsite.com",
"active": true
},
{
"id": "nytimes.com",
"active": false
}
],
"settings": {
"logs": {
"enabled": true,
"drop": {
"ip": false,
"domain": false
},
"retention": 7776000,
"location": "eu"
},
"blockPage": {
"enabled": true
},
"performance": {
"ecs": true,
"cacheBoost": false,
"cnameFlattening": true
},
"web3": true
}
}
To create a new profile, POST
at https://api.nextdns.io/profiles
the above JSON. The API will return:
{
"data": {
"id": "abc123" // the id of the newly created profile
}
}
You can then GET
, PATCH
and DELETE
a profile at:
https://api.nextdns.io/profiles/:profile
(:profile
in our example should be abc123
).
All nested objects and arrays also have their own API child endpoints.
For example, you can GET
or PATCH
performance settings at:
https://api.nextdns.io/profiles/:profile/settings/performance
…or add a domain to the Denylist by POST
‘ing at:
https://api.nextdns.io/profiles/:profile/denylist
Objects endpoints (such as .../privacy
) support the GET
and PATCH
methods.
Arrays endpoints (such as .../privacy/blocklists
) support the GET
, PUT
and POST
methods. Each child supports the PATCH
and DELETE
method (you should use the first key as id).
Analytics are available at https://api.nextdns.io/profiles/:profile/analytics/*
(e.g. .../analytics/protocols
).
Type: Date
Filter out entities with older date (inclusive).
Type: Date
Filter out entities with newer or equal date (exclusive).
Type: Integer
Default: 10
Limit the number of results returned (see Pagination).
limit
should be >= 1 and <= 500.
Type: String
Use cursor
to get the next page (see Pagination).
Type: String
Only get entities related to a specific device.
Use
__UNIDENTIFIED__
to filter against all unidentified devices.
{
"data": [
{
"status": "default",
"queries": 819491
},
{
"status": "blocked",
"queries": 132513
},
{
"status": "allowed",
"queries": 6923
},
...
],
...
}
Specific query parameters:
status
(optional, default | blocked | allowed
)root
(optional, boolean
, default false
){
"data": [
{
"domain": "app-measurement.com",
"queries": 29801
},
{
"domain": "gateway.icloud.com",
"root": "icloud.com",
"queries": 18468
},
{
"domain": "app.smartmailcloud.com",
"root": "smartmailcloud.com",
"queries": 16414
},
...
],
...
}
{
"data": [
{
"id": "blocklist:nextdns-recommended",
"name": "NextDNS Ads & Trackers Blocklist",
"queries": 131833
},
{
"id": "native:apple",
"name": "Native Tracking (Apple)",
"queries": 402
},
{
"id": "disguised-trackers",
"name": "Disguised Third-Party Trackers",
"queries": 269
},
...
],
...
}
{
"data": [
{
"ip": "91.171.12.34",
"network": {
"cellular": false,
"vpn": false,
"isp": "Free",
"asn": 12322
},
"geo": {
"latitude": 48.8998,
"longitude": 2.703,
"countryCode": "FR",
"country": "France",
"city": "Gagny"
},
"queries": 136935
},
{
"ip": "2a01:e0a:2cd:1234:312a:4c24:215d:185",
"network": {
"cellular": false,
"vpn": false,
"isp": "Free",
"asn": 12322
},
"geo": {
"latitude": 48.5136,
"longitude": -1.9042,
"countryCode": "FR",
"country": "France",
"city": "Miniac-Morvan"
},
"queries": 40410
},
...
],
...
}
{
"data": [
{
"id": "8TD1G",
"name": "Romain’s iPhone",
"model": "iPhone 12 Pro Max",
"queries": 489885
},
{
"id": "E24AR",
"name": "MBP",
"model": "Macbook Pro",
"localIp": "192.168.0.11",
"queries": 215663
},
{
"id": "__UNIDENTIFIED__",
"queries": 74242
},
...
],
...
}
{
"data": [
{
"protocol": "DNS-over-HTTPS",
"queries": 958757
},
{
"protocol": "DNS-over-TLS",
"queries": 39582
},
{
"protocol": "UDP",
"queries": 2334
},
...
],
...
}
{
"data": [
{
"type": 28,
"name": "AAAA",
"queries": 356230
},
{
"type": 1,
"name": "A",
"queries": 341812
},
{
"type": 65,
"name": "HTTPS",
"queries": 260478
},
...
],
...
}
{
"data": [
{
"version": 6,
"queries": 784154
},
{
"version": 4,
"queries": 174308
},
...
],
...
}
{
"data": [
{
"validated": false,
"queries": 817664
},
{
"validated": true,
"queries": 8199
},
...
],
...
}
{
"data": [
{
"encrypted": true,
"queries": 958331
},
{
"encrypted": false,
"queries": 1
},
...
],
...
}
{
"data": [
{
"code": "US",
"domains": [
"app.smartmailcloud.com",
"imap.gmail.com",
"api.coinbase.com",
"events-service.coinbase.com",
"ws.coinbase.com"
],
"queries": 209851
},
{
"code": "FR",
"domains": [
"inappcheck.itunes.apple.com",
"iphone-ld.apple.com",
"bag.itunes.apple.com",
"itunes.apple.com",
"www.apple.com"
],
"queries": 105497
},
...
],
...
}
{
"data": [
{
"company": "others",
"queries": 478732
},
{
"company": "apple",
"queries": 284832
},
{
"company": "google",
"queries": 159488
},
...
],
...
}
All the endpoints above can return time series data to easily create charts.
Time series endpoints are built by appending ;series
to any of the endpoints above (e.g. .../analytics/status;series?...
).
Time series responses return an array of values for queries
instead of a single value.
All query string parameters mentioned here are supported and should be used, with the addition of the following parameters:
Type: Seconds | Duration
By default, the API uses an appropriate duration for each tumbling window based on the full window size (from
-> to
).
Values: start | end | clock
Default: end
Useful when the full window doesn’t fit an exact number of tumbling windows, or when you want to align windows on the clock.
Type: TimeZone
Default: GMT
Use this in correlation with alignment=clock
.
Values: none | start | end | all
Default: none
Decide if you want partial tumbling windows to be returned.
// https://api.nextdns.io/profiles/abc123/analytics/queryTypes;series?from=-7d&interval=1d&limit=2
{
"data": [
{
"type": 28,
"name": "AAAA",
"queries": [
4019,
5801,
2667,
2817,
3314,
3128,
3810
]
},
{
"type": 1,
"name": "A",
"queries": [
3873,
5421,
2691,
2865,
3387,
3192,
3864
]
}
],
"meta": {
"series": {
"times": [ // the starting date of each tumbling window
"2021-03-08T16:51:36.623Z",
"2021-03-09T16:51:36.623Z",
"2021-03-10T16:51:36.623Z",
"2021-03-11T16:51:36.623Z",
"2021-03-12T16:51:36.623Z",
"2021-03-13T16:51:36.623Z",
"2021-03-14T16:51:36.623Z"
],
"interval": 86400 // the duration (in seconds) of each tumbling window
},
"pagination": {
"cursor": "jS8sl16m"
}
}
}
Analytics are available at https://api.nextdns.io/profiles/:profile/logs
.
Type: Date
Filter out logs with older date (inclusive).
Type: Date
Filter out logs with newer or equal date (exclusive).
Values: asc | desc
Default: desc
Using asc
will return logs from oldest to newest, desc
from newest to oldest.
Type: Integer
Default: 100
Limit the number of results returned (see Pagination).
limit
should be >= 10 and <= 1000.
Type: String
Use cursor
to get the next page (see Pagination).
Type: String
Only get logs made for a specific device.
Use
__UNIDENTIFIED__
to filter against all unidentified devices.
Values: default | error | blocked | allowed
Optional
Filter by status.
Type: String
Only returns logs matching the search query.
e.g.
Type: Boolean
Default: false
By default, only navigational query types (A
, AAAA
and HTTPS
) are returned, and are automatically deduplicated. Irrelevant domains considered noise (like Google Chrome random DNS lookups) are also not returned. This gives a clearer overview of network access. Use raw=1
to show all DNS queries.
{
"data": [
{
"timestamp": "2021-03-18T03:00:10.338Z",
"domain": "21-courier.push.apple.com",
"root": "apple.com",
"tracker": "apple",
"encrypted": true,
"protocol": "DNS-over-HTTPS",
"clientIp": "2a01:e0a:2cd:87a0:1b23:2832:57cd:aa1d",
"client": "apple-profile",
"device": {
"id": "8TD1G",
"name": "Romain’s iPhone",
"model": "iPhone 12 Pro Max"
},
"status": "default",
"reasons": []
},
{
"timestamp": "2021-03-18T02:56:14.182Z",
"domain": "sb.scorecardresearch.com",
"root": "scorecardresearch.com",
"tracker": "scorecard_research_beacon",
"encrypted": false,
"protocol": "UDP",
"clientIp": "91.172.51.28",
"status": "blocked",
"reasons": [
{
"id": "blocklist:nextdns-recommended",
"name": "NextDNS Ads & Trackers Blocklist"
},
{
"id": "blocklist:oisd",
"name": "oisd"
}
]
},
...
],
...
}
Stream new logs in real-time with Server-sent events (or SSE) by using the endpoint GET /profiles/:profile/logs/stream
.
The /logs/stream
endpoint supports all query string parameters supported by the /logs
endpoint with the exception of from
, to
, sort
, limit
and cursor
.
Events have the following format:
id: 64v32d9r6rwkcctg6cu38e9g60
data: {"timestamp":"2021-03-16T04:40:30.344Z","domain":"g.whatsapp.net","root":"whatsapp.net","encrypted":true,"protocol":"DNS-over-HTTPS","clientIp":"2a01:e0a:2cd:87a0:5540:d573:57cd:aa1d","client":"apple-profile","device":{"id":"8TD1G","name":"Romain’s iPhone","model":"iPhone 12 Pro Max"},"status":"default","reasons":[]}
Pass the last id
received as query string parameter to resume where you left off (e.g. .../logs/stream?id=64v32d9r6rwkcctg6cu38e9g60
).
:bulb: The standard
/logs
endpoint will return anid
when getting the most recent logs:{ "data": [...], "meta": { ... "stream": { "id": "64v32d9r6rwkcctg6cu38e9g60" } } }
Pass this
id
as query string parameter to the/logs/stream
endpoint when you need to stitch recent logs and new logs received from the streaming endpoint without any duplicate or missing logs.
GET https://api.nextdns.io/profiles/:profile/logs/download
:bulb: By default, this endpoint will automatically redirect to the public URL of the file. Use
?redirect=0
to get a JSON containing that URL (useful when showing a loader while the file is being generated).
DELETE https://api.nextdns.io/profiles/:profile/logs
:bulb: Logs are automatically cleared when a profile is deleted.
Date
format in query parametersThe API accepts different formats for dates in query string parameters:
2021-03-15T16:34:05.203Z
)1615826071
)1615826071284
)-6h
, -1d
, -3M
or now
)2021-03-15
)TimeZone
formatThe API only accepts time zone names from the Time Zone Database.
In JavaScript, you can get this with:
Intl.DateTimeFormat().resolvedOptions().timeZone
> "Europe/Paris"