PowerDNS¶
Authoritative DNS zone management via the PowerDNS API: zones, records, DNS templates.
All endpoints below are mounted under /api. Authentication: Authorization: Bearer <token> unless noted as public.
Endpoint count: 37
Endpoints¶
| Method | Path | Handler | Guards |
|---|---|---|---|
GET |
/api/powerdns/config |
getPowerDNSConfig |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/config |
updatePowerDNSConfig |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/import-bind |
importBindZone |
- |
POST |
/api/powerdns/install |
installPowerDNS |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
GET |
/api/powerdns/replication |
getPowerDNSReplication |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/replication |
savePowerDNSReplication |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
GET |
/api/powerdns/smart-rules |
listPowerDNSSmartRules |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/smart-rules |
createPowerDNSSmartRule |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
DELETE |
/api/powerdns/smart-rules/:id |
deletePowerDNSSmartRule |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
PUT |
/api/powerdns/smart-rules/:id |
updatePowerDNSSmartRule |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
GET |
/api/powerdns/stats |
getPowerDNSStats |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
GET |
/api/powerdns/status |
getPowerDNSStatus |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/sync-api-key |
createSyncAPIKey |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/sync-push |
syncPushZone |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
GET |
/api/powerdns/sync-servers |
getSyncServers |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/sync-servers |
saveSyncServers |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
GET |
/api/powerdns/sync-status |
getSyncStatus |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/sync-zone |
syncZoneReceive |
- |
GET |
/api/powerdns/templates |
listPowerDNSTemplates |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/templates |
createPowerDNSTemplate |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
DELETE |
/api/powerdns/templates/:id |
deletePowerDNSTemplate |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
GET |
/api/powerdns/templates/:id |
getPowerDNSTemplate |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
PUT |
/api/powerdns/templates/:id |
updatePowerDNSTemplate |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/templates/:id/apply/:zone |
applyPowerDNSTemplate |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
GET |
/api/powerdns/zones |
listPowerDNSZones |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/zones |
createPowerDNSZone |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
DELETE |
/api/powerdns/zones/:zone |
deletePowerDNSZone |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
GET |
/api/powerdns/zones/:zone |
getPowerDNSZone |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
DELETE |
/api/powerdns/zones/:zone/dnssec |
disablePowerDNSDNSSEC |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
GET |
/api/powerdns/zones/:zone/dnssec |
getPowerDNSDNSSEC |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/zones/:zone/dnssec |
enablePowerDNSDNSSEC |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/zones/:zone/export |
exportPowerDNSZone |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/zones/:zone/geo-record |
addPowerDNSGeoRecord |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/zones/:zone/import |
importPowerDNSZone |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
PATCH |
/api/powerdns/zones/:zone/records |
updatePowerDNSRecords |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
DELETE |
/api/powerdns/zones/:zone/records/simple |
deletePowerDNSRecordSimple |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
POST |
/api/powerdns/zones/:zone/records/simple |
addPowerDNSRecordSimple |
RequireSuperuser(), RequirePlugin(db, "powerdns") |
Source¶
Handlers for this section live in api/routes/handlers_*.go in the StarkOne source. Each handler binds its request body with gin.ShouldBindJSON(&req) and responds with the standard envelope:
On error:
Reference¶
GET /api/powerdns/config¶
Returns the stored PowerDNS API connection settings (API URL, key, server ID, backing DB name) and whether PowerDNS is installed.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Response - 200 OK
{ "data": { "api_url": "http://127.0.0.1:8081", "api_key": "abc123...", "server_id": "localhost", "db_name": "powerdns_dns", "installed": true } }
If no pdns_config row exists, returns { "data": { "installed": false } }.
Example
POST /api/powerdns/config¶
Updates the PowerDNS API connection settings; only non-empty fields are written.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Request body
{ "api_url": "string (optional)", "api_key": "string (optional)", "server_id": "string (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
api_url |
string | no | PowerDNS API base URL. Written only if non-empty. |
api_key |
string | no | PowerDNS API key. Written only if non-empty. |
server_id |
string | no | PowerDNS server ID (e.g. localhost). Written only if non-empty. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Request body could not be parsed. |
Side effects
- Updates the
pdns_configrow (id = 1) in the panel database for each non-empty field.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/config \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"api_url": "http://127.0.0.1:8081", "api_key": "newkey", "server_id": "localhost"}'
POST /api/powerdns/import-bind¶
Parses a raw BIND zone file and imports its records into PowerDNS, creating the zone (kind Native) if it does not already exist. Authenticated by a sync key, not a bearer token.
Authentication: Requires the X-StarkOne-Sync-Key header (validated against the api_keys table), not a bearer token. The stub guard cell is -.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
zone_name |
string | yes | Zone name. A trailing dot is appended if missing. |
bind_data |
string | yes | Full BIND-format zone file text. Supports $ORIGIN and $TTL directives. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
401 |
unauthorized |
Missing or invalid X-StarkOne-Sync-Key header. |
400 |
bad_request |
Body could not be parsed, or no valid records were found in the BIND data. |
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-2xx status on the records PATCH. |
Side effects
- Creates the zone in PowerDNS via its API if absent, then PATCHes the parsed rrsets into it.
- Updates
last_usedon the matchingapi_keysrow.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/import-bind \
-H "X-StarkOne-Sync-Key: $SYNC_KEY" \
-H "Content-Type: application/json" \
-d '{"zone_name": "example.com", "bind_data": "$TTL 3600\n@ IN A 203.0.113.10\n"}'
POST /api/powerdns/install¶
Installs and configures the PowerDNS authoritative server: installs packages, creates a MySQL backend database, imports the schema, writes pdns.conf, and starts the pdns service.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Response - 200 OK
{ "data": { "success": true, "message": "PowerDNS installed successfully", "api_key": "generated-hex-key", "db_name": "powerdns_dns" } }
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
mysql_required |
MySQL/MariaDB is not installed and running. |
400 |
already_installed |
PowerDNS is already installed (pdns_config.installed = 1). |
500 |
install_failed |
Package installation (apt-get/dnf) failed. |
500 |
db_create_failed |
Creating the PowerDNS MySQL database failed. |
500 |
config_failed |
Writing pdns.conf failed. |
Side effects
- Stops/disables
systemd-resolved,named, andbind9; may rewrite/etc/resolv.conf. - Installs
pdns-server/pdns-backend-mysql/pdns-tools(Debian) orpdns/pdns-backend-mysql/pdns-tools(RHEL), adding the PowerDNS apt repo on Debian. - Creates a
powerdns.localhostwebsite and adnsMySQL database; imports the PowerDNS schema and grants privileges. - Writes
/etc/powerdns/pdns.conf(and/or/etc/pdns/pdns.conf); removes conflictingpdns.d/*.conf. - Updates the
pdns_configrow and enables/starts thepdnssystemd service.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/install \
-H "Authorization: Bearer $TOKEN"
GET /api/powerdns/replication¶
Returns the configured replication role (native/master/slave), master IP, allowed AXFR IPs, and, for a slave, per-zone sync status.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Response - 200 OK
{ "data": { "role": "slave", "master_ip": "203.0.113.5", "allow_axfr_ips": ["203.0.113.6"], "zones": [ { "name": "example.com.", "serial": 2026010101, "last_check": 1718600000, "kind": "Slave", "status": "synced" } ] } }
The zones array is populated only when role is slave; status is one of synced, pending, or failed. Defaults to role native if unset.
Example
POST /api/powerdns/replication¶
Sets the replication role and reconfigures all zones and pdns.conf accordingly (master/slave/native), then restarts PowerDNS.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Request body
{ "role": "string (required)", "allow_axfr_ips": ["string (optional)"], "master_ip": "string (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
role |
string | yes | One of native, master, slave. |
allow_axfr_ips |
string[] | no | IPs allowed to perform AXFR (used in master role). |
master_ip |
string | no | Master server IP. Required when role is slave. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body invalid, role not one of native/master/slave, or master_ip missing for slave. |
502 |
pdns_error |
Failed to fetch zones from the PowerDNS API. |
Side effects
- Upserts
pdns_replication_role,pdns_master_ip,pdns_allow_axfr_ipsingeneral_settings. - Edits
pdns.confdirectives (allow-axfr-ips,autosecondary,allow-notify-from). - For master: switches all zones to kind
Master. For slave: deletes and recreates zones asSlavepointing atmaster_ip. For native: switches all zones toNative. - Restarts the
pdnssystemd service.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/replication \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"role": "slave", "master_ip": "203.0.113.5"}'
GET /api/powerdns/smart-rules¶
Lists smart DNS rules from the pdns_smart_rules table, optionally filtered by zone.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Query parameters
| Name | Type | Optional | Description |
|---|---|---|---|
zone |
string | yes | If provided, only rules for this zone are returned. |
Response - 200 OK
{ "data": [ { "id": 1, "zone": "example.com.", "record_name": "www", "record_type": "A", "rule_type": "geo", "config": {}, "enabled": true, "created_at": "2026-06-17 00:00:00" } ] }
Returns an empty array if the table query fails or no rules exist.
Example
curl "https://panel.example.com:2087/api/powerdns/smart-rules?zone=example.com." \
-H "Authorization: Bearer $TOKEN"
POST /api/powerdns/smart-rules¶
Creates a smart DNS rule row in pdns_smart_rules.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Request body
{ "zone": "string (required)", "record_name": "string (required)", "record_type": "string (optional)", "rule_type": "string (required)", "config": "object (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
zone |
string | yes | Zone the rule applies to. |
record_name |
string | yes | Record name within the zone. |
record_type |
string | no | Record type; defaults to A if empty. |
rule_type |
string | yes | Rule type identifier (e.g. geo). |
config |
object | no | Arbitrary rule configuration; stored as JSON, defaults to {}. |
Response - 200 OK (returned with status 201)
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body invalid or required fields missing. |
500 |
db_error |
Insert into pdns_smart_rules failed. |
Side effects
- Inserts a row into
pdns_smart_rules.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/smart-rules \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"zone": "example.com.", "record_name": "www", "rule_type": "geo", "config": {}}'
DELETE /api/powerdns/smart-rules/:id¶
Deletes a smart DNS rule by ID.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | The pdns_smart_rules row ID to delete. |
Response - 200 OK
Side effects
- Deletes the matching row from
pdns_smart_rules(no error if it does not exist).
Example
curl -X DELETE https://panel.example.com:2087/api/powerdns/smart-rules/1 \
-H "Authorization: Bearer $TOKEN"
PUT /api/powerdns/smart-rules/:id¶
Updates fields of a smart DNS rule; only provided non-empty fields are changed.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | The pdns_smart_rules row ID to update. |
Request body
{ "record_name": "string (optional)", "record_type": "string (optional)", "rule_type": "string (optional)", "config": "object (optional)", "enabled": "bool (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
record_name |
string | no | New record name; updated if non-empty. |
record_type |
string | no | New record type; updated if non-empty. |
rule_type |
string | no | New rule type; updated if non-empty. |
config |
object | no | New config JSON; updated if non-null. |
enabled |
bool | no | Enable/disable flag; updated if present (pointer). |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body could not be parsed. |
Side effects
- Issues per-field
UPDATEstatements againstpdns_smart_rules.
Example
curl -X PUT https://panel.example.com:2087/api/powerdns/smart-rules/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
GET /api/powerdns/stats¶
Returns raw PowerDNS server statistics, proxied from the PowerDNS API /statistics endpoint.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Response - 200 OK
The data payload is the verbatim PowerDNS statistics array (shape determined by PowerDNS).
Errors
| Status | Code | Meaning |
|---|---|---|
502 |
pdns_error |
The PowerDNS API request failed. |
Example
GET /api/powerdns/status¶
Reports whether PowerDNS is installed and running, plus version, zone count, and selected statistics.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Response - 200 OK
{ "data": { "installed": true, "running": true, "version": "PowerDNS Authoritative Server 4.9.0", "zone_count": 3, "stats": { "uptime": "12345" } } }
If pdns_config is missing or installed = 0, returns { "data": { "installed": false, "running": false } }. The stats map is populated only when running and an API key is set.
Side effects
- Runs
systemctl is-active pdnsandpdns_server --version/pdns_control versionto determine state (read-only).
Example
POST /api/powerdns/sync-api-key¶
Generates a new API token (named dns-sync) for the authenticated user and returns it once in plaintext.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Response - 200 OK
The token is returned only once; only its SHA-256 hash and a 12-char prefix are stored.
Errors
| Status | Code | Meaning |
|---|---|---|
401 |
unauthorized |
No valid user_id in the request context. |
500 |
db_error |
Insert into api_keys failed. |
Side effects
- Inserts a row into
api_keys(namedns-sync) with the token hash and prefix.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/sync-api-key \
-H "Authorization: Bearer $TOKEN"
POST /api/powerdns/sync-push¶
Pushes a zone's current records from the local PowerDNS to every configured sync server and returns per-server results.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Request body
| Field | Type | Required | Description |
|---|---|---|---|
zone_name |
string | yes | Zone to push. A trailing dot is appended if missing. |
Response - 200 OK
{ "data": { "success": true, "results": [ { "server": "edge1", "ip": "203.0.113.6", "success": true } ] } }
If no sync servers are configured, returns { "data": { "success": true, "message": "no sync servers configured" } }.
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body invalid or zone_name missing. |
502 |
pdns_error |
Failed to fetch the zone from the local PowerDNS API. |
| (passthrough) | pdns_error |
PowerDNS returned a non-200 status for the zone. |
Side effects
- Reads sync servers from
general_settings(pdns_sync_servers). - Issues
POST /api/powerdns/sync-zoneto each remote server (TLS verification skipped). - Writes per-server results to
general_settings(pdns_sync_status).
Example
curl -X POST https://panel.example.com:2087/api/powerdns/sync-push \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"zone_name": "example.com."}'
GET /api/powerdns/sync-servers¶
Returns the list of configured DNS sync servers stored in general_settings.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Response - 200 OK
Returns an empty array if none are configured. The object shape is whatever was previously saved (untyped JSON).
Example
POST /api/powerdns/sync-servers¶
Saves (replaces) the list of DNS sync servers in general_settings.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Request body
| Field | Type | Required | Description |
|---|---|---|---|
servers |
object[] | no | List of server objects (untyped JSON). The whole list replaces the stored value. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body could not be parsed. |
Side effects
- Upserts the
pdns_sync_serversrow ingeneral_settings.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/sync-servers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"servers": [{"name": "edge1", "ip": "203.0.113.6", "api_key": "bap_xxx"}]}'
GET /api/powerdns/sync-status¶
Returns the last recorded per-server sync status from general_settings.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Response - 200 OK
{ "data": [ { "name": "edge1", "ip": "203.0.113.6", "last_sync": "2026-06-17T00:00:00Z", "status": "ok", "error": "" } ] }
Returns an empty array if no status has been recorded.
Example
POST /api/powerdns/sync-zone¶
Receives zone data from a remote StarkOne server and applies it to the local PowerDNS, creating the zone if needed. Authenticated by a sync key, not a bearer token.
Authentication: Requires the X-StarkOne-Sync-Key header (validated against api_keys), not a bearer token. The stub guard cell is -.
Request body
{ "zone_name": "string (required)", "records": [ { "name": "www.example.com.", "type": "A", "content": "203.0.113.10", "ttl": 3600, "disabled": false } ], "skip_sync": "bool (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
zone_name |
string | yes | Zone name. A trailing dot is appended if missing. |
records |
object[] | yes | Flat record list; each has name, type, content, optional ttl (default 3600) and disabled. |
skip_sync |
bool | no | If true, do not re-push to other sync servers (loop prevention in master-master). |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
401 |
unauthorized |
Missing or invalid X-StarkOne-Sync-Key header. |
400 |
bad_request |
Body invalid or required fields missing. |
502 |
pdns_error |
A PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-2xx status creating the zone or PATCHing records. |
Side effects
- Creates the zone (kind
Native) via the PowerDNS API if absent, then PATCHes the supplied records. - Updates
last_usedon the matchingapi_keysrow. - Unless
skip_syncis true, asynchronously pushes the zone to all configured sync servers.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/sync-zone \
-H "X-StarkOne-Sync-Key: $SYNC_KEY" \
-H "Content-Type: application/json" \
-d '{"zone_name": "example.com.", "records": [{"name": "www.example.com.", "type": "A", "content": "203.0.113.10", "ttl": 3600}]}'
GET /api/powerdns/templates¶
Lists all DNS record templates from the pdns_templates table.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Response - 200 OK
{ "data": [ { "id": 1, "name": "Basic web", "description": "A + www", "records": [], "created_at": "2026-06-17 00:00:00" } ] }
Returns an empty array if the query fails or no templates exist.
Example
POST /api/powerdns/templates¶
Creates a DNS record template in pdns_templates.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Template name. |
description |
string | no | Free-text description. |
records |
array | no | Record definitions; stored as JSON, defaults to []. |
Response - 200 OK (returned with status 201)
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body invalid or name missing. |
500 |
db_error |
Insert into pdns_templates failed. |
Side effects
- Inserts a row into
pdns_templates.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/templates \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "Basic web", "description": "A + www", "records": [{"name": "@", "type": "A", "content": "203.0.113.10"}]}'
DELETE /api/powerdns/templates/:id¶
Deletes a DNS record template by ID.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | The pdns_templates row ID to delete. |
Response - 200 OK
Side effects
- Deletes the matching row from
pdns_templates(no error if absent).
Example
curl -X DELETE https://panel.example.com:2087/api/powerdns/templates/1 \
-H "Authorization: Bearer $TOKEN"
GET /api/powerdns/templates/:id¶
Returns a single DNS record template by ID.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | The pdns_templates row ID. |
Response - 200 OK
{ "data": { "id": "1", "name": "Basic web", "description": "A + www", "records": [], "created_at": "2026-06-17 00:00:00" } }
Errors
| Status | Code | Meaning |
|---|---|---|
404 |
not_found |
No template with that ID. |
Example
PUT /api/powerdns/templates/:id¶
Updates a DNS record template; only provided non-empty/non-null fields are changed.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | The pdns_templates row ID to update. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | New name; updated if non-empty. |
description |
string | no | New description; updated if non-empty. |
records |
array | no | New records JSON; updated if non-null. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body could not be parsed. |
Side effects
- Issues per-field
UPDATEstatements againstpdns_templates.
Example
curl -X PUT https://panel.example.com:2087/api/powerdns/templates/1 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"description": "Updated description"}'
POST /api/powerdns/templates/:id/apply/:zone¶
Applies a template's records to a zone, replacing matching rrsets via the PowerDNS API.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
id |
integer | The pdns_templates row ID to apply. |
zone |
string | Target zone name. A trailing dot is appended if missing. |
Response - 200 OK
For each template record, @ is expanded to the zone apex and relative names are qualified with the zone; each is sent as a REPLACE rrset.
Errors
| Status | Code | Meaning |
|---|---|---|
404 |
not_found |
No template with that ID. |
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-2xx status on the PATCH. |
Side effects
- PATCHes the template's rrsets into the zone via the PowerDNS API.
- Asynchronously pushes the zone to all configured sync servers.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/templates/1/apply/example.com. \
-H "Authorization: Bearer $TOKEN"
GET /api/powerdns/zones¶
Lists all zones, proxied verbatim from the PowerDNS API /zones endpoint.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Response - 200 OK
{ "data": [ { "id": "example.com.", "name": "example.com.", "kind": "Native", "serial": 2026010101 } ] }
The array contents are PowerDNS's zone objects (shape determined by PowerDNS).
Errors
| Status | Code | Meaning |
|---|---|---|
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-200 status. |
Example
POST /api/powerdns/zones¶
Creates a new Native zone with auto-generated ns1/ns2 nameservers via the PowerDNS API.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Zone name. A trailing dot is appended if missing. |
Response - 200 OK (returned with status 201)
The data payload is the PowerDNS-created zone object.
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body invalid or name missing. |
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a status other than 200/201. |
Side effects
- Creates the zone in PowerDNS (kind
Native, nameserversns1.<zone>,ns2.<zone>). - Asynchronously pushes the zone to all configured sync servers.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/zones \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "example.com"}'
DELETE /api/powerdns/zones/:zone¶
Deletes a zone via the PowerDNS API.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a status other than 200/204. |
Side effects
- Deletes the zone in PowerDNS.
- Asynchronously clears the zone on configured sync servers (pushes empty records).
Example
curl -X DELETE https://panel.example.com:2087/api/powerdns/zones/example.com. \
-H "Authorization: Bearer $TOKEN"
GET /api/powerdns/zones/:zone¶
Returns a single zone with its rrsets, proxied verbatim from the PowerDNS API.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Response - 200 OK
The data payload is the PowerDNS zone object.
Errors
| Status | Code | Meaning |
|---|---|---|
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-200 status (e.g. 404 for unknown zone). |
Example
curl https://panel.example.com:2087/api/powerdns/zones/example.com. \
-H "Authorization: Bearer $TOKEN"
DELETE /api/powerdns/zones/:zone/dnssec¶
Disables DNSSEC for a zone by deleting all its crypto keys and clearing nsec3param.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
502 |
pdns_error |
Fetching the zone's crypto keys from the PowerDNS API failed. |
Side effects
- Deletes each crypto key for the zone via the PowerDNS API and unsets
nsec3param(PUT). Errors on individual deletes/PUT are not surfaced.
Example
curl -X DELETE https://panel.example.com:2087/api/powerdns/zones/example.com./dnssec \
-H "Authorization: Bearer $TOKEN"
GET /api/powerdns/zones/:zone/dnssec¶
Returns whether DNSSEC is enabled for a zone and lists its crypto keys.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Response - 200 OK
enabled is true when at least one crypto key exists; keys is the verbatim PowerDNS crypto key list.
Errors
| Status | Code | Meaning |
|---|---|---|
502 |
pdns_error |
The PowerDNS API request failed. |
Example
curl https://panel.example.com:2087/api/powerdns/zones/example.com./dnssec \
-H "Authorization: Bearer $TOKEN"
POST /api/powerdns/zones/:zone/dnssec¶
Enables DNSSEC for a zone by creating an active KSK (algorithm ECDSAP256SHA256).
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Response - 200 OK
The data payload is the PowerDNS-created crypto key object. The key parameters are fixed (KSK / active / ECDSAP256SHA256); there is no request body.
Errors
| Status | Code | Meaning |
|---|---|---|
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a status other than 200/201. |
Side effects
- Creates a crypto key for the zone via the PowerDNS API.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/zones/example.com./dnssec \
-H "Authorization: Bearer $TOKEN"
POST /api/powerdns/zones/:zone/export¶
Exports a zone as a BIND-format zone file, fetched from the PowerDNS API export endpoint. Despite the POST method there is no request body.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Response - 200 OK
The zone_file field contains the raw BIND zone text (not a streamed download).
Errors
| Status | Code | Meaning |
|---|---|---|
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-200 status. |
Example
curl -X POST https://panel.example.com:2087/api/powerdns/zones/example.com./export \
-H "Authorization: Bearer $TOKEN"
POST /api/powerdns/zones/:zone/geo-record¶
Creates a GeoIP-aware LUA record that returns different values based on the client's country, using PowerDNS Lua records.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Request body
{ "name": "string (required)", "ttl": "int (optional)", "type": "string (optional)", "rules": [ { "countries": ["US", "*"], "value": "203.0.113.10" } ] }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Record name; @ maps to the zone apex, relative names are qualified with the zone. |
ttl |
int | no | TTL; defaults to 300 if 0/omitted. |
type |
string | no | Record type the Lua resolves to; defaults to A. |
rules |
array | yes | Country-to-value rules. Each rule has countries (list of ISO codes, or ["*"] for default) and value. |
Response - 200 OK
{ "data": { "success": true, "content": "A \"if(country('US')) then return '203.0.113.10' end\"" } }
The record is stored as a PowerDNS LUA rrset; content is the generated Lua expression.
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body invalid or required fields missing. |
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-2xx status on the PATCH. |
Side effects
- PATCHes a
LUArrset into the zone via the PowerDNS API. - Asynchronously pushes the zone to all configured sync servers.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/zones/example.com./geo-record \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "www", "type": "A", "rules": [{"countries": ["US"], "value": "203.0.113.10"}, {"countries": ["*"], "value": "203.0.113.20"}]}'
POST /api/powerdns/zones/:zone/import¶
Parses an RFC-1035 zone-file text and replaces matching rrsets in the zone via the PowerDNS API.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
zone_file |
string | yes | Zone-file text. Records are parsed (name/TTL/class/type/content), grouped into rrsets, and sent as REPLACE. TTL defaults to 3600 for the rrsets. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body invalid or zone_file missing. |
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-2xx status on the PATCH. |
Side effects
- PATCHes parsed rrsets into the zone via the PowerDNS API.
- Asynchronously pushes the zone to all configured sync servers.
Example
curl -X POST https://panel.example.com:2087/api/powerdns/zones/example.com./import \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"zone_file": "www 3600 IN A 203.0.113.10\n"}'
PATCH /api/powerdns/zones/:zone/records¶
Passes a raw rrsets payload through to the PowerDNS API zone PATCH endpoint (low-level record edit).
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Request body
The raw request body is forwarded verbatim to PowerDNS; it must be a PowerDNS rrsets PATCH payload.
{ "rrsets": [ { "name": "www.example.com.", "type": "A", "ttl": 3600, "changetype": "REPLACE", "records": [ { "content": "203.0.113.10", "disabled": false } ] } ] }
| Field | Type | Required | Description |
|---|---|---|---|
rrsets |
array | yes | PowerDNS rrset change list (passed through unmodified). |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Request body could not be read. |
502 |
pdns_error |
The PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-2xx status on the PATCH. |
Side effects
- Forwards the payload as a PATCH to the PowerDNS zone.
- Asynchronously pushes the zone to all configured sync servers.
Example
curl -X PATCH https://panel.example.com:2087/api/powerdns/zones/example.com./records \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"rrsets": [{"name": "www.example.com.", "type": "A", "ttl": 3600, "changetype": "REPLACE", "records": [{"content": "203.0.113.10", "disabled": false}]}]}'
DELETE /api/powerdns/zones/:zone/records/simple¶
Deletes a record by name/type; if content is provided, removes only that value from the rrset, otherwise deletes the whole rrset.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Request body
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Record name; @ maps to the zone apex, relative names are qualified with the zone. |
type |
string | yes | Record type (uppercased). |
content |
string | no | If set, only this value is removed; remaining values are kept (or the rrset deleted if none remain). If empty, the entire rrset is deleted. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body invalid or required fields missing. |
502 |
pdns_error |
A PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-2xx status on the PATCH. |
Side effects
- Issues a PATCH (DELETE or REPLACE rrset) to the PowerDNS zone; when removing a single value it first fetches the zone to filter records.
- Asynchronously pushes the zone to all configured sync servers.
Example
curl -X DELETE https://panel.example.com:2087/api/powerdns/zones/example.com./records/simple \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "www", "type": "A", "content": "203.0.113.10"}'
POST /api/powerdns/zones/:zone/records/simple¶
Adds a single record by name/type/content, merging with existing values of the same name+type via a REPLACE rrset.
Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")
Path parameters
| Name | Type | Description |
|---|---|---|
zone |
string | Zone name. A trailing dot is appended if missing. |
Request body
{ "name": "string (required)", "type": "string (required)", "content": "string (required)", "ttl": "int (optional)" }
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Record name; @ maps to the zone apex, relative names are qualified with the zone. |
type |
string | yes | Record type (uppercased). |
content |
string | yes | Record value. |
ttl |
int | no | TTL; defaults to 3600 if 0/omitted. |
Response - 200 OK
Errors
| Status | Code | Meaning |
|---|---|---|
400 |
bad_request |
Body invalid or required fields missing. |
502 |
pdns_error |
A PowerDNS API request failed. |
| (passthrough) | pdns_error |
PowerDNS returned a non-2xx status on the PATCH. |
Side effects
- Fetches the zone to merge with existing records of the same name+type, then PATCHes a REPLACE rrset.
- Asynchronously pushes the zone to all configured sync servers.
Example