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.
The ordinary d= field consumes the entire remainder, including semicolons and
equals signs. e=b64 is standard padded Base64 and produces bytes; decode
those bytes as UTF-8 only for text/JSON. Payload ttl is a hint, distinct from
Answer[].TTL. Dataset envelopes have their own identifiers and fields.
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) {
// ResolveDB JSON escapes quotes/backslashes. For arbitrary DNS presentation
// (including decimal byte escapes), use the preview SDK's parseCharStrings.
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') {
const bytes = Uint8Array.from(atob(payload), (character) => character.charCodeAt(0));
payload = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);
}
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.
Shared Answering Contract (Implemented)
The shared answering contract
aligns product values, effective DNS TTLs, and error outcomes with native DNS.
It adds requested DNSSEC material through do=true, supporting DNSKEY queries,
and query statistics for DoH. Signing does not by itself assert the recursive
validation meaning of the AD flag.
Shared schema/zone and units/moon/gated-BTC answering now provide the common
outcomes and requested do=true material in the repository. Computed ordinary
results retain flat fields after v=rdb1;s=ok;t=data. BTC remains mock-only and
gated off. Hosted private/public-read/demo records now use that shared path too:
private hits and negatives have TTL 0 (including SOA/proofs), public positive
TTLs are capped at record expiry, and corrupt values fail with SERVFAIL. All-family
evaluation statistics include DoH. Weather,
forecast, sun, and GeoIP share the same ordinary fields and requested signatures;
forecast uses daily provider output and TTL at most 1800. Dataset answering is
also shared. Statistics identify the evaluated family, including records for
hosted units and datasets for dataset reads. Verified private attribution
survives miss/expiry; public/schema/dataset activity remains unattributed. JSON
HTTP caching remains no-store. Repository conformance is complete; production
rollout and package publication remain operator-controlled.
Names with a terminal root dot or different DNS letter case must select the
same query. Encoded query parameters use case-stable b32-/hex-, while TXT
e=b64 payloads use standard padded Base64. Ordinary results retain flat fields
or a final d= payload; dataset envelopes remain distinct. JSON responses retain
HTTP no-store. Encoding alignment is implemented across server, Rails accepted
hosted keys, and preview Go/JS clients; MCP rejects reserved encoder prefixes.