> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dnzwithazee.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Query explain

> API endpoint that returns the decision path of one DNS query through DNZ and the reply it gets, without logging, caching or counting it.

For what the steps mean, see [Query explain](https://docs.dnzwithazee.com/dns/query-explain.md).

## Endpoint

| Method | Path | Permission | Description |
| - | - | - | - |
| `GET` | `/api/query/explain?name=&type=&client=&protocol=&dnssec=&ask_upstreams=` | `logs.read` or `logs.server` | The path one query takes through the server, and its answer |
| `POST` | `/api/capture` | `logs.read` (with `division_id`) or `logs.server` | Live capture: the queries matching keywords as server-sent events (see [Live capture](https://docs.dnzwithazee.com/dns/live-capture.md#api)) |

| Parameter | |
| - | - |
| `name` | The name. With `type=PTR`, an IP address is turned into its reverse name |
| `type` | The record type, `A` when left out |
| `client` | The client's IP address |
| `protocol` | `udp` (default), `tcp`, `dot`, `doh` or `doq` |
| `dnssec` | `true` to ask with the DO bit |
| `ask_upstreams` | `true` to ask the upstreams on a cache miss. Their answer isn't cached, but their latency stats count it |

**Permission:** a superadmin session, or a user whose role has the server-wide `logs.server`, can
explain any client. Other users and [API tokens](https://docs.dnzwithazee.com/api/overview.md#what-a-token-can-reach) need
`logs.read` in the division whose [client network](https://docs.dnzwithazee.com/dns/filtering.md) holds `client`. Explaining is read-only and isn't written to the
audit log.

```bash theme={null}
curl -s "https://dnz.example.com/api/query/explain?name=www.example.com&type=A&client=192.168.1.20" \
  -H "Authorization: Bearer $DNZ_TOKEN"
```

```json theme={null}
{
  "name": "www.example.com.", "type": "A", "client": "192.168.1.20", "protocol": "udp",
  "steps": [
    { "stage": "client", "summary": "Client 192.168.1.20",
      "details": ["No split-horizon view: the default records",
                  "Client network 192.168.1.0/24 of division Office: its blocklists apply, then the server-wide ones"] },
    { "stage": "policy", "summary": "Rate limits, DNS cookies and response rate limiting are skipped for explained queries",
      "details": ["Over UDP, answers larger than 1232 bytes are truncated (TC) so the client retries over TCP"] },
    { "stage": "local", "summary": "Local zone example.com holds it (as www)",
      "details": ["CNAME www.example.com. → example.com (record #2, TTL 300), followed",
                  "example.com. is in local zone example.com",
                  "1 A record at example.com.: 192.0.2.1 (record #1, TTL 300)"] }
  ],
  "source": "local",
  "response": {
    "rcode": "NOERROR", "flags": ["qr", "aa", "rd"],
    "answers": ["www.example.com. 300 IN CNAME example.com.", "example.com. 300 IN A 192.0.2.1"],
    "authorities": [], "additionals": [], "size": 75
  },
  "resolved": true, "asked_upstreams": false, "duration_ms": 0.35
}
```

| Field | |
| - | - |
| `steps[].stage` | `client`, `policy`, `local`, `filter`, `rpz`, `forward`, `cache`, `upstream`, `alias`, `dns64`, `steering` or `dnssec` |
| `source` | Where the answer came from, as the query log names it (`local`, `cache`, `upstream`, `conditional`, `blocked`, `rpz`, `safe_search`…); `null` for replies the query log doesn't record and when nothing is sent |
| `response` | The reply as the client would get it; `null` when none is sent (an RPZ *drop*) |
| `resolved` | `false` when the answer depends on upstreams that weren't asked; `response` is then the SERVFAIL a failed lookup would give, not the real answer |
| `duration_ms` | How long explaining took, upstream questions included |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.