DoH JSON API
Query DNS records using a simple JSON API. Google-compatible format for browsers and debugging.
TL;DR
HTTP GET to /resolve with a name parameter. Returns JSON with Answer records.
curl "https://api.resolvedb.io/resolve?name=get.newyork.weather.public.v1.resolvedb.net&type=TXT"Why Use the JSON API?
- No DNS library required - Works with
fetch(),curl, any HTTP client - Encrypted by default - Queries travel over HTTPS
- Debugging friendly - Human-readable JSON responses
- Google-compatible - Same format as
dns.google/resolve
Endpoint
GET https://api.resolvedb.io/resolveQuery Parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
name | Yes | - | Domain name to query (max 253 chars) |
type | No | A | Query type (string or numeric) |
cd | No | false | Checking Disabled - skip DNSSEC validation |
do | No | false | DNSSEC OK - include DNSSEC records |
edns_client_subnet | No | - | Client subnet hint (e.g., 1.2.3.0/24) |
random_padding | No | - | Random string for cache-busting |
Supported Query Types
| Type | Numeric | Description |
|---|---|---|
A | 1 | IPv4 address |
AAAA | 28 | IPv6 address |
TXT | 16 | Text record (ResolveDB data) |
MX | 15 | Mail exchange |
CNAME | 5 | Canonical name |
NS | 2 | Nameserver |
SOA | 6 | Start of authority |
SRV | 33 | Service record |
CAA | 257 | Certificate authority |
PTR | 12 | Pointer (reverse DNS) |
HTTPS | 65 | HTTPS service binding |
SVCB | 64 | Service binding |
NAPTR | 35 | Naming authority pointer |
DNSKEY | 48 | DNSSEC public key |
DS | 43 | Delegation signer |
RRSIG | 46 | DNSSEC signature |
NSEC | 47 | Next secure |
ANY | 255 | All record types |
Response Format
{
"Status": 0,
"TC": false,
"RD": true,
"RA": true,
"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;ttl=300;ts=...;loc=New York...;tc=-0.3;tf=31.5;cnd=partly_cloudy"
}
]
}Response Fields
| Field | Type | Description |
|---|---|---|
Status | number | DNS RCODE: 0=NOERROR, 2=SERVFAIL, 3=NXDOMAIN |
TC | boolean | Response was truncated |
RD | boolean | Recursion Desired (always true) |
RA | boolean | Recursion Available |
AD | boolean | Authenticated Data (DNSSEC validated) |
CD | boolean | Checking Disabled (per request) |
Question | array | Original question(s) |
Answer | array | Answer records |
Authority | array | Authority records (if any) |
Additional | array | Additional records (if any) |
Comment | string | Error message or diagnostic info |
Answer Record Fields
| Field | Type | Description |
|---|---|---|
name | string | Domain name |
type | number | Record type (numeric) |
TTL | number | Time to live in seconds |
data | string | Record data |
Examples
Query ResolveDB TXT Record
curl "https://api.resolvedb.io/resolve?name=get.newyork.weather.public.v1.resolvedb.net&type=TXT"Response:
{
"Status": 0,
"TC": false,
"RD": true,
"RA": true,
"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;ttl=300;ts=1767186000;loc=New York, New York, United States;tc=-0.3;tf=31.5;cnd=partly_cloudy;wnd=19.0;hum=50"
}
]
}Query with Numeric Type
curl "https://api.resolvedb.io/resolve?name=example.com&type=1"Request DNSSEC Records
curl "https://api.resolvedb.io/resolve?name=cloudflare.com&type=A&do=true"JavaScript/Browser
async function queryResolveDB(resource, namespace = 'hooli', version = 'v1') {
const name = `get.${resource}.${namespace}.${version}.resolvedb.net`;
const url = `https://api.resolvedb.io/resolve?name=${encodeURIComponent(name)}&type=TXT`;
const response = await fetch(url);
const data = await response.json();
if (data.Status !== 0 || !data.Answer?.length) {
throw new Error(data.Comment || 'Query failed');
}
// Parse UQRP response
const txt = data.Answer[0].data;
const match = txt.match(/d=(.+)$/);
return match ? JSON.parse(match[1]) : txt;
}
// Usage
const config = await queryResolveDB('settings.config');
console.log(config); // { theme: "dark", locale: "en-US" }Python
import requests
import json
import re
def query_resolvedb(resource, namespace='hooli', version='v1'):
name = f"get.{resource}.{namespace}.{version}.resolvedb.net"
url = f"https://api.resolvedb.io/resolve?name={name}&type=TXT"
response = requests.get(url)
data = response.json()
if data['Status'] != 0 or not data.get('Answer'):
raise Exception(data.get('Comment', 'Query failed'))
# Parse UQRP response
txt = data['Answer'][0]['data']
match = re.search(r'd=(.+)$', txt)
return json.loads(match.group(1)) if match else txt
# Usage
config = query_resolvedb('settings.config')
print(config) # {'theme': 'dark', 'locale': 'en-US'}Go
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"regexp"
)
type DoHResponse struct {
Status int `json:"Status"`
Answer []Answer `json:"Answer"`
Comment string `json:"Comment,omitempty"`
}
type Answer struct {
Name string `json:"name"`
Type int `json:"type"`
TTL int `json:"TTL"`
Data string `json:"data"`
}
func queryResolveDB(resource, namespace, version string) (map[string]interface{}, error) {
name := fmt.Sprintf("get.%s.%s.%s.resolvedb.net", resource, namespace, version)
reqURL := fmt.Sprintf("https://api.resolvedb.io/resolve?name=%s&type=TXT", url.QueryEscape(name))
resp, err := http.Get(reqURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var data DoHResponse
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return nil, err
}
if data.Status != 0 || len(data.Answer) == 0 {
return nil, fmt.Errorf("query failed: %s", data.Comment)
}
// Parse UQRP response
re := regexp.MustCompile(`d=(.+)$`)
match := re.FindStringSubmatch(data.Answer[0].Data)
if match == nil {
return nil, fmt.Errorf("invalid response format")
}
var result map[string]interface{}
if err := json.Unmarshal([]byte(match[1]), &result); err != nil {
return nil, err
}
return result, nil
}
func main() {
config, err := queryResolveDB("settings.config", "hooli", "v1")
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", config)
}Error Handling
DNS Status Codes
| Status | Name | Description |
|---|---|---|
| 0 | NOERROR | Query successful |
| 1 | FORMERR | Malformed query |
| 2 | SERVFAIL | Server failure |
| 3 | NXDOMAIN | Domain does not exist |
| 4 | NOTIMP | Not implemented |
| 5 | REFUSED | Query refused |
HTTP Status Codes
| Code | Description |
|---|---|
| 200 | Success (check Status field for DNS errors) |
| 400 | Invalid request (missing/malformed parameters) |
| 406 | Accept header doesn't include supported content type |
| 429 | Rate limit exceeded |
| 500 | Server error |
Error Response Example
curl "https://api.resolvedb.io/resolve?name=nonexistent.resolvedb.net&type=TXT"{
"Status": 3,
"Comment": "NXDOMAIN",
"Question": [{ "name": "nonexistent.resolvedb.net", "type": 16 }]
}Caching
Responses include Cache-Control headers matching the minimum TTL:
Cache-Control: max-age=300For error responses:
Cache-Control: no-storeRate Limits
| Plan | Requests/min |
|---|---|
| Free | 60 |
| Pro | 600 |
| Enterprise | Custom |
Rate limit headers are included in responses:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1704067260CORS
The JSON API supports CORS for browser requests from any origin (Access-Control-Allow-Origin: *).
This allows direct use from any web application without a server-side proxy.
Comparison with Wire Format
| Feature | JSON API | Wire Format |
|---|---|---|
| Ease of use | Simple GET | Requires DNS encoding |
| Response format | JSON | Binary |
| Browser support | Native fetch() | Needs library |
| Debugging | Easy | Requires tools |
| Size efficiency | Larger | Compact |
| Standard | Google-compatible | RFC 8484 |
Use JSON for applications. Use wire format for system integration.
Next Steps
- DoH Wire Format - RFC 8484 implementation
- UQRP Protocol - Query and response format
- Quickstart - Get started in 5 minutes