DoH JSON API
Query ResolveDB names over HTTPS using a Google-style JSON DNS response.
Endpoint
GET https://doh.resolvedb.io/resolveThe equivalent parameter-selected route is
https://doh.resolvedb.io/dns-query?name=....
ResolveDB is authoritative-only. These endpoints resolve ResolveDB qnames, not arbitrary Internet domains.
Request
curl --get https://doh.resolvedb.io/resolve \
--data-urlencode "name=get.newyork.weather.public.v1.resolvedb.net" \
--data-urlencode "type=TXT"| Parameter | Required | Default | Behavior |
|---|---|---|---|
name | Yes | - | ResolveDB qname, maximum 253 characters |
type | No | A | DNS type name or number; use TXT for UQRP data |
cd | No | false | Copied to the DNS checking-disabled flag |
edns_client_subnet | No | - | Echoed in the JSON response; not used for routing |
do | No | - | Accepted for compatibility but currently ignored |
random_padding | No | - | Accepted and ignored |
Response
{
"Status": 0,
"TC": false,
"RD": true,
"RA": false,
"AD": false,
"CD": false,
"Question": [
{ "name": "get.newyork.weather.public.v1.resolvedb.net", "type": 16 }
],
"Answer": [
{
"name": "get.newyork.weather.public.v1.resolvedb.net",
"type": 16,
"TTL": 300,
"data": "\"v=rdb1;s=ok;t=data;...\""
}
]
}Status is the DNS RCODE. ResolveDB normally represents an unknown name as
NOERROR with no answers (NODATA), not NXDOMAIN. RA is false because the
service is authoritative, not recursive.
All JSON responses use Cache-Control: no-store. Use DNS wire transport when
you need HTTP caching based on the DNS TTL.
Parsing TXT Data
Answer[].data uses DNS presentation format. A TXT record can contain multiple
quoted character strings. Unquote and concatenate them before parsing the UQRP
envelope. Then decode d according to e.
This example reads the operator-managed, public-read Hooli demo record. Hooli namespaces are read-only examples and cannot be claimed by customers.
function decodeTxtPresentation(value) {
const strings = value.match(/"(?:\\.|[^"\\])*"/g) ?? [];
return strings.map((part) => JSON.parse(part)).join('');
}
function parseUqrpJson(txt) {
const dataMarker = ';d=';
const markerIndex = txt.indexOf(dataMarker);
if (markerIndex === -1) throw new Error('Missing UQRP data field');
const metadata = Object.fromEntries(
txt.slice(0, markerIndex).split(';').map((field) => field.split('=', 2)),
);
let payload = txt.slice(markerIndex + dataMarker.length);
if (metadata.e === 'b64') {
payload = atob(payload);
}
return JSON.parse(payload);
}
const name = 'get.dark-mode.flags.hooli.v1.resolvedb.net';
const response = await fetch(
`https://doh.resolvedb.io/resolve?name=${encodeURIComponent(name)}&type=TXT`,
);
const dns = await response.json();
if (dns.Status !== 0 || !dns.Answer?.length) {
throw new Error(dns.Comment || 'No answer');
}
const txt = decodeTxtPresentation(dns.Answer[0].data);
console.log(parseUqrpJson(txt));
// { enabled: true, variant: "default" }Private hosted records use the same response format but require an
auth-rdbq... label. Treat the qname as a bearer credential and use HTTPS.
Status Codes
| DNS status | Meaning |
|---|---|
0 | NOERROR: answer or NODATA |
1 | FORMERR: malformed query |
2 | SERVFAIL: service or storage failure |
5 | REFUSED: authorization or namespace denial |
Validation errors return HTTP 400 with a static JSON error. Valid DNS queries,
including DNS-level errors, return HTTP 200. The JSON endpoint does not emit
plan-based X-RateLimit-* headers.
CORS
The endpoint permits browser requests from any origin.