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/resolve

Query Parameters

ParameterRequiredDefaultDescription
nameYes-Domain name to query (max 253 chars)
typeNoAQuery type (string or numeric)
cdNofalseChecking Disabled - skip DNSSEC validation
doNofalseDNSSEC OK - include DNSSEC records
edns_client_subnetNo-Client subnet hint (e.g., 1.2.3.0/24)
random_paddingNo-Random string for cache-busting

Supported Query Types

TypeNumericDescription
A1IPv4 address
AAAA28IPv6 address
TXT16Text record (ResolveDB data)
MX15Mail exchange
CNAME5Canonical name
NS2Nameserver
SOA6Start of authority
SRV33Service record
CAA257Certificate authority
PTR12Pointer (reverse DNS)
HTTPS65HTTPS service binding
SVCB64Service binding
NAPTR35Naming authority pointer
DNSKEY48DNSSEC public key
DS43Delegation signer
RRSIG46DNSSEC signature
NSEC47Next secure
ANY255All 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

FieldTypeDescription
StatusnumberDNS RCODE: 0=NOERROR, 2=SERVFAIL, 3=NXDOMAIN
TCbooleanResponse was truncated
RDbooleanRecursion Desired (always true)
RAbooleanRecursion Available
ADbooleanAuthenticated Data (DNSSEC validated)
CDbooleanChecking Disabled (per request)
QuestionarrayOriginal question(s)
AnswerarrayAnswer records
AuthorityarrayAuthority records (if any)
AdditionalarrayAdditional records (if any)
CommentstringError message or diagnostic info

Answer Record Fields

FieldTypeDescription
namestringDomain name
typenumberRecord type (numeric)
TTLnumberTime to live in seconds
datastringRecord 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

StatusNameDescription
0NOERRORQuery successful
1FORMERRMalformed query
2SERVFAILServer failure
3NXDOMAINDomain does not exist
4NOTIMPNot implemented
5REFUSEDQuery refused

HTTP Status Codes

CodeDescription
200Success (check Status field for DNS errors)
400Invalid request (missing/malformed parameters)
406Accept header doesn't include supported content type
429Rate limit exceeded
500Server 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=300

For error responses:

Cache-Control: no-store

Rate Limits

PlanRequests/min
Free60
Pro600
EnterpriseCustom

Rate limit headers are included in responses:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1704067260

CORS

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

FeatureJSON APIWire Format
Ease of useSimple GETRequires DNS encoding
Response formatJSONBinary
Browser supportNative fetch()Needs library
DebuggingEasyRequires tools
Size efficiencyLargerCompact
StandardGoogle-compatibleRFC 8484

Use JSON for applications. Use wire format for system integration.

Next Steps