Plugin Extension APIs
Plugins register management and observability endpoints below a common tag path. Route availability depends on the active configuration and compiled binary capabilities.
Plugin Extension APIs
Unified Format
/api/plugins/<plugin_tag>/<route>
- A few plugins also expose prefix routes. For example,
query_recorderuses/api/plugins/<tag>/records/<id>.
cache
GET /api/plugins/<cache_tag>/entries
Reads cache entries with pagination.
Query parameters:
limit: Page size. Defaults to100, maximum500.cursor: Pagination cursor.qname: Case-insensitive substring filter for the query domain in the cache key.
GET /api/plugins/<cache_tag>/flush
Clears the cache.
GET /api/plugins/<cache_tag>/dump
Exports a cache dump.
POST /api/plugins/<cache_tag>/load_dump
Imports a cache dump.
matcher
Every matcher declared in plugins: with a stable tag registers these runtime-control endpoints:
GET /api/plugins/<matcher_tag>/status
POST /api/plugins/<matcher_tag>/mode
Both endpoints return the same status shape:
{
"ok": true,
"matcher": "match_cn",
"mode": "normal"
}
Set the mode with a JSON request body:
{ "mode": "always_false" }
Supported values are normal, always_false, and always_true; setting the current mode again is idempotent. Both fixed modes skip the internal matcher and fix its base Boolean value, after which each reference still applies its own negation. With always_false, $match_cn misses and !$match_cn matches; always_true produces the opposite results. A missing or unsupported mode returns 400 invalid_matcher_runtime_mode.
The mode belongs only to the current runtime and is not written to YAML. An application reload or process restart restores normal. Quick-setup matchers inside sequence or any_match use internal qs.match... tags and do not register these endpoints. Extract a quick-setup expression into a standalone matcher under plugins: and reference it with $tag when runtime control is required.
The WebUI requires confirmation for both always_false and always_true; restoring normal does not require confirmation. The old /enable and /disable endpoints and the enabled response field have been removed, so API clients must migrate to /mode.
provider
POST /api/plugins/<provider_tag>/reload
Purpose:
- Reloads that provider's internal snapshot with the same configuration it used at startup.
- Does not rebuild unrelated plugins and does not change provider tags, dependency topology, or config structure.
Responses:
200 OK- The provider reload succeeded.
400 Bad Request- The provider returned an error while reloading.
404 Not Found- The tag is not a loaded provider in the current runtime, so no route was registered for it.
409 Conflict- A reload is already running for the same provider. The new request returns
provider_reload_busyimmediately instead of waiting in a queue.
- A reload is already running for the same provider. The new request returns
Good fit:
- Refreshing only the affected
domain_set,ip_set,geosite,geoip, oradguard_ruleprovider after downloading new rule files. - Avoiding the blast radius of an application-wide
POST /api/reload.
- When the change also updates
config.yaml, provider topology, the plugin list, or other non-provider structures,POST /api/reloadis still required.
The WebUI exposes Reload data on every applied provider card and detail view. It does not show a configuration-change confirmation; while the command is running, duplicate requests for the same provider are disabled and the success, failure, or busy result is surfaced inline.
reverse_lookup
GET /api/plugins/<tag>?ip=<ip_addr>
Looks up the domain cached for an IP address.
Example:
GET /api/plugins/reverse_lookup_main?ip=8.8.8.8
Responses:
- Hit: domain text, usually a fully-qualified domain name
- Miss: empty response body
- Invalid parameter:
400 Bad Request
query_recorder
GET /api/plugins/<tag>/records
Returns recorder rows ordered by created_at_ms descending and does not include steps.
Query parameters:
cursor=<created_at_ms>:<id>- Continue pagination after the last row from the previous page.
limit=<n>- Default
100, maximum500.
- Default
since_ms=<unix_ms>- Only return rows at or after this timestamp.
until_ms=<unix_ms>- Only return rows at or before this timestamp.
qname=<text>- Case-insensitive substring match against request question names.
client_ip=<text>- Case-insensitive substring match against the client IP string; IPv4/IPv6 fragments are accepted.
qtype=<type>- Exact match against request question type.
rcode=<rcode>- Exact match against response code.
status=all|error|has_response|no_response- Filter by recorder row status.
client_ip is the transport peer observed by the DNS server. If record lists or /stats/top_clients show only 127.0.0.1, the queries are usually passing through a local forwarder first, such as systemd-resolved, dnsmasq, AdGuardHome, dae, or clash. Check the deployment chain, point clients directly at OxiDNS, or configure a trusted src_ip_header for HTTP/DoH reverse-proxy deployments.
Responses:
200 OK- JSON shaped like:
{
"ok": true,
"next_cursor": "1713510000123:42",
"records": [
{
"id": 42,
"created_at_ms": 1713510000123,
"elapsed_ms": 12,
"request_id": 1234,
"client_ip": "192.0.2.10",
"questions_json": [
{ "name": "www.example.com.", "qtype": "A", "qclass": "IN" }
],
"req_rd": true,
"req_cd": false,
"req_ad": false,
"req_opcode": "Query",
"req_edns_json": null,
"error": null,
"has_response": true,
"rcode": "NoError",
"resp_aa": false,
"resp_tc": false,
"resp_ra": true,
"resp_ad": false,
"resp_cd": false,
"answer_count": 1,
"authority_count": 0,
"additional_count": 0,
"answers_json": [
{
"name": "www.example.com.",
"class": "IN",
"ttl": 300,
"rr_type": "A",
"payload_kind": "A",
"payload_text": "192.0.2.1",
"payload": { "ip": "192.0.2.1" }
}
],
"authorities_json": [],
"additionals_json": [],
"signature_json": [],
"resp_edns_json": null
}
]
}
GET /api/plugins/<tag>/records/<id>
Returns one full record plus its steps array.
Responses:
200 OK- JSON containing a
recordobject.record.recordholds the fixed main-table fields andrecord.stepsholds path events.
- JSON containing a
404 Not Found- The record does not exist.
DELETE /api/plugins/<tag>/records
Clears all persisted query-history rows and steps path events for the current recorder. The operation first flushes the background writer queue, then deletes all rows from the SQLite records table and clears the in-memory tail.
Responses:
200 OK- JSON shaped like:
{
"ok": true,
"cleared_records": 128
}
GET /api/plugins/<tag>/stats/plugins
Aggregates plugin hit information from recorded path events.
Query parameters:
since_ms=<unix_ms>until_ms=<unix_ms>kind=matcher|executor|builtin|all- Same as
/records, supportsqname,client_ip,qtype,rcode, andstatusfilters.
Response fields:
kindtagcheckedmatchedexecutedquery_totalquery_share
GET /api/plugins/<tag>/stats/top_clients
Aggregates query counts by client IP.
Query parameters:
limit=<n>- Number of buckets to return. Defaults to
20. The backend no longer enforces a200cap; large values increase SQLite sorting and response-size cost.
- Number of buckets to return. Defaults to
- Same as
/records, supportssince_ms,until_ms,qname,client_ip,qtype,rcode,status, andmatcher_tagfilters.
Response fields:
sample_sizerows[].keyrows[].countrows[].share
GET /api/plugins/<tag>/stats/top_qnames
Aggregates query counts by question name. Query parameters and response fields match /stats/top_clients.
GET /api/plugins/<tag>/stats/qtype
Aggregates distribution by QTYPE. Supports the same time range and filter parameters as /records, and returns sample_size plus rows[].key/count/share.
GET /api/plugins/<tag>/stats/rcode
Aggregates distribution by response code or special status bucket. Supports the same time range and filter parameters as /records, and returns sample_size plus rows[].key/count/share.
GET /api/plugins/<tag>/stats/latency
Returns latency summary values, histogram buckets, and slow-query ranking.
Query parameters:
slow_limit=<n>orlimit=<n>- Number of slow-query rows to return. Defaults to
20. The backend no longer enforces a200cap.
- Number of slow-query rows to return. Defaults to
- Same as
/records, supports time range and filter parameters.
GET /api/plugins/<tag>/stats/timeseries
Aggregates query trends into time buckets.
Query parameters:
bucket=minute|hourbuckets=<n>- Number of buckets to return. Defaults to
60, maximum720.
- Number of buckets to return. Defaults to
- Same as
/records, supports time range and filter parameters.
GET /api/plugins/<tag>/stream
Streams newly written records over SSE.
Query parameters:
tail=<n>- Replay the most recent
nrecords from the in-memory tail first, then continue streaming.
- Replay the most recent
event: recorduses the fullRecordDetailJSON asdata.- Heartbeat comment frames are sent periodically to keep the connection alive.
- Clients should send
Accept: text/event-streamand tolerate heartbeat frames, error events, empty payloads, and brief reconnects.