Skip to content

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:

{ "data": { ... } }

On error:

{ "error": { "code": "string", "message": "string" } }

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

curl https://panel.example.com:2087/api/powerdns/config \
  -H "Authorization: Bearer $TOKEN"

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

{ "data": { "success": true } }

Errors

Status Code Meaning
400 bad_request Request body could not be parsed.

Side effects

  • Updates the pdns_config row (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

{ "zone_name": "string (required)", "bind_data": "string (required)" }
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

{ "data": { "success": true } }

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_used on the matching api_keys row.

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, and bind9; may rewrite /etc/resolv.conf.
  • Installs pdns-server/pdns-backend-mysql/pdns-tools (Debian) or pdns/pdns-backend-mysql/pdns-tools (RHEL), adding the PowerDNS apt repo on Debian.
  • Creates a powerdns.localhost website and a dns MySQL database; imports the PowerDNS schema and grants privileges.
  • Writes /etc/powerdns/pdns.conf (and/or /etc/pdns/pdns.conf); removes conflicting pdns.d/*.conf.
  • Updates the pdns_config row and enables/starts the pdns systemd 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

curl https://panel.example.com:2087/api/powerdns/replication \
  -H "Authorization: Bearer $TOKEN"

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

{ "data": { "success": true } }

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_ips in general_settings.
  • Edits pdns.conf directives (allow-axfr-ips, autosecondary, allow-notify-from).
  • For master: switches all zones to kind Master. For slave: deletes and recreates zones as Slave pointing at master_ip. For native: switches all zones to Native.
  • Restarts the pdns systemd 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)

{ "data": { "id": 1 } }

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

{ "data": { "success": true } }

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

{ "data": { "success": true } }

Errors

Status Code Meaning
400 bad_request Body could not be parsed.

Side effects

  • Issues per-field UPDATE statements against pdns_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

{ "data": [ { "name": "uptime", "type": "StatisticItem", "value": "12345" } ] }

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

curl https://panel.example.com:2087/api/powerdns/stats \
  -H "Authorization: Bearer $TOKEN"

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 pdns and pdns_server --version/pdns_control version to determine state (read-only).

Example

curl https://panel.example.com:2087/api/powerdns/status \
  -H "Authorization: Bearer $TOKEN"

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

{ "data": { "token": "bap_<64-hex-chars>" } }

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 (name dns-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

{ "zone_name": "string (required)" }
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-zone to 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

{ "data": [ { "name": "edge1", "ip": "203.0.113.6", "api_key": "bap_..." } ] }

Returns an empty array if none are configured. The object shape is whatever was previously saved (untyped JSON).

Example

curl https://panel.example.com:2087/api/powerdns/sync-servers \
  -H "Authorization: Bearer $TOKEN"

POST /api/powerdns/sync-servers

Saves (replaces) the list of DNS sync servers in general_settings.

Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")

Request body

{ "servers": [ { "name": "edge1", "ip": "203.0.113.6", "api_key": "bap_..." } ] }
Field Type Required Description
servers object[] no List of server objects (untyped JSON). The whole list replaces the stored value.

Response - 200 OK

{ "data": { "success": true } }

Errors

Status Code Meaning
400 bad_request Body could not be parsed.

Side effects

  • Upserts the pdns_sync_servers row in general_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

curl https://panel.example.com:2087/api/powerdns/sync-status \
  -H "Authorization: Bearer $TOKEN"

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

{ "data": { "success": true } }

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_used on the matching api_keys row.
  • Unless skip_sync is 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

curl https://panel.example.com:2087/api/powerdns/templates \
  -H "Authorization: Bearer $TOKEN"

POST /api/powerdns/templates

Creates a DNS record template in pdns_templates.

Guards: RequireSuperuser(), RequirePlugin(db, "powerdns")

Request body

{ "name": "string (required)", "description": "string (optional)", "records": "array (optional)" }
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)

{ "data": { "id": 1, "name": "Basic web" } }

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

{ "data": { "success": true } }

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

curl https://panel.example.com:2087/api/powerdns/templates/1 \
  -H "Authorization: Bearer $TOKEN"

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

{ "name": "string (optional)", "description": "string (optional)", "records": "array (optional)" }
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

{ "data": { "success": true } }

Errors

Status Code Meaning
400 bad_request Body could not be parsed.

Side effects

  • Issues per-field UPDATE statements against pdns_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

{ "data": { "success": true } }

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

curl https://panel.example.com:2087/api/powerdns/zones \
  -H "Authorization: Bearer $TOKEN"

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

{ "name": "string (required)" }
Field Type Required Description
name string yes Zone name. A trailing dot is appended if missing.

Response - 200 OK (returned with status 201)

{ "data": { "id": "example.com.", "name": "example.com.", "kind": "Native" } }

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, nameservers ns1.<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

{ "data": { "success": true } }

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

{ "data": { "id": "example.com.", "name": "example.com.", "kind": "Native", "rrsets": [] } }

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

{ "data": { "success": true } }

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

{ "data": { "enabled": true, "keys": [ { "id": 1, "keytype": "ksk", "active": true } ] } }

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

{ "data": { "id": 1, "keytype": "ksk", "active": true, "algorithm": "ECDSAP256SHA256" } }

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

{ "data": { "zone_file": "example.com. 3600 IN SOA ..." } }

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 LUA rrset 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

{ "zone_file": "string (required)" }
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

{ "data": { "success": true } }

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

{ "data": { "success": true } }

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

{ "name": "string (required)", "type": "string (required)", "content": "string (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 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

{ "data": { "success": true } }

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

{ "data": { "success": true } }

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

curl -X POST 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", "ttl": 3600}'