This episode covers the PowerDNS REST API: enabling the webserver with an API key, managing zones, records, TSIG, and crypto keys via /api/v1/servers/localhost/zones, provisioning with curl and Python, plus Terraform provider and ExternalDNS integration in Kubernetes.

Every capability you've built — zones, transfers, DNSSEC, dynamic updates — can be fully automated through the PowerDNS REST API. This is what makes database-driven DNS so valuable: DNS infrastructure is managed as code, not through a panel.
Episode 20 opens up the PowerDNS API: enabling it, basic operations with curl, Python scripting, then integration with Terraform and ExternalDNS in Kubernetes. After this episode, you can create zones automatically, create records in seconds, and manage DNS as part of your CI/CD pipeline.
The API lives in the Authoritative webserver. Enable it in pdns.conf:
api=yes
api-key=sekret-api-key-panjang
webserver=yes
webserver-address=127.0.0.1
webserver-port=8081api=yes enables the REST endpoints; api-key authenticates every call. webserver-address=127.0.0.1 keeps the API accessible only from the machine itself — proxy it through a reverse proxy if needed.
All operations run on the base path /api/v1/servers/localhost:
curl -s -H 'X-API-Key: sekret-api-key-panjang' \
http://127.0.0.1:8081/api/v1/servers
curl -s -H 'X-API-Key: sekret-api-key-panjang' \
http://127.0.0.1:8081/api/v1/servers/localhost/zonescurl -H 'X-API-Key: ...' is the pattern for every API request. The JSON responses contain the server list for the first call and all zones for the second.
Zones are created with a JSON POST:
curl -s -X POST -H 'X-API-Key: sekret-api-key-panjang' \
-H 'Content-Type: application/json' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones \
-d '{"name":"api-demo.example.com","kind":"Primary","masters":[],"nameservers":["ns1.example.com."]}'Note the zone name format ends with a dot. kind determines the zone type: Primary, Secondary, or Native. The response returns the complete representation of the zone just created.
Add records with a PATCH on the zone endpoint:
curl -s -X PATCH -H 'X-API-Key: sekret-api-key-panjang' \
-H 'Content-Type: application/json' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/api-demo.example.com \
-d '{"rrsets":[{"name":"app.api-demo.example.com","type":"A","ttl":300,"records":[{"content":"192.0.2.40","disabled":false}]}]}'The rrsets payload contains the RRsets to add or replace. This pattern is used for all records: A, AAAA, MX, TXT, even DNSSEC records like DNSKEY.
TSIG keys and DNSSEC keys are also managed via the API — the /zones/{zone}/metadata and /zones/{zone}/cryptokeys endpoints:
curl -s -H 'X-API-Key: sekret-api-key-panjang' \
http://127.0.0.1:8081/api/v1/servers/localhost/zones/api-demo.example.com/cryptokeysWith the requests library, provisioning becomes a function callable from any pipeline:
import requests
BASE = "http://127.0.0.1:8081/api/v1/servers/localhost"
HEADERS = {"X-API-Key": "sekret-api-key-panjang"}
def create_a_record(zone, name, ip, ttl=300):
payload = {"rrsets": [{
"name": f"{name}.{zone}",
"type": "A",
"ttl": ttl,
"records": [{"content": ip, "disabled": False}],
}]}
r = requests.patch(f"{BASE}/zones/{zone}", json=payload, headers=HEADERS)
r.raise_for_status()
create_a_record("api-demo.example.com", "web", "192.0.2.41")The create_a_record function above adds an A record with a single API call. From this simple function, you can build complete tooling: automatic sync, auditing, even an internal self-service portal.
Terraform officially uses the powerdns/pdns provider. With it, entire zones and records are declared as code:
provider "pdns" {
api_url = "http://127.0.0.1:8081"
api_key = "sekret-api-key-panjang"
}
resource "powerdns_zone" "app" {
name = "app.example.com."
type = "Primary"
nameservers = ["ns1.example.com.", "ns2.example.com."]
}
resource "powerdns_record" "www" {
zone = powerdns_zone.app.name
name = "www.app.example.com"
type = "A"
ttl = 300
records = ["192.0.2.42"]
}With Terraform, terraform apply creates zones and records, and terraform destroy removes them — DNS becomes part of versioned, reviewed infrastructure-as-code.
ExternalDNS synchronizes Kubernetes Services and Ingresses to DNS automatically:
kind: Deployment
metadata:
name: external-dns
spec:
template:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.15.1
args:
- --source=service
- --source=ingress
- --provider=powerdns
- --pdns-server=http://pdns-api:8081
- --pdns-api-key=sekret-api-key-panjang
- --domain-filter=app.example.comEvery annotated Service in the cluster is registered in PowerDNS DNS without human intervention — a perfect example of the autoprovisioning that is a main reason PowerDNS was designed to be database-driven.
Episode 20 turns you into a fully automated DNS operator: enabling the REST API, doing zone and record operations with curl, provisioning via Python, and declaring all of DNS as code with Terraform and ExternalDNS.
Key takeaways:
api=yes and authenticated with api-key./api/v1/servers/localhost.rrsets.powerdns/pdns Terraform provider makes DNS code.In episode 21, we'll cover production-ready stack and deployment — assembling the complete architecture from client to dnsdist, recursor, and authoritative via a database backend, deploying with Docker and Kubernetes, managing TLS certificates with ACME, and upgrading according to PowerDNS EOL policy.