Zone-scoped API keys — a smaller blast radius for DNS automation
Create DNScale API keys that can only touch the zones you pick. Ship safer CI pipelines, grant per-brand access, and keep the blast radius of a leaked key small.
DNScale API keys have always been customer-wide. If you handed a key to a CI pipeline, a teammate, or a partner agency, that key could read and write every zone on the account. Fine for small setups, uncomfortable once you run a few different brands, clients, or environments behind a single DNScale account.
Today we're shipping zone-scoped API keys. You pick the zones a key is allowed to touch at creation time. Everything outside that set is invisible to the key — not just read-only, literally 404 / 403.
Why we built it
A few patterns kept coming up:
- CI for one app shouldn't manage DNS for the whole company. A
staging-deploy pipeline for
api.staging.acme.apphas no business editing records onpayroll.acme.corp, but with a customer-wide key one leaked CI secret exposed both. - Agencies managing DNS for multiple clients want to give each client a key that only sees their own zones — no cross-client visibility, no mistakes during script execution.
- Third-party tooling (monitoring, IaC modules, webhook handlers) rarely needs access to anything beyond the zone it's provisioning for. Least privilege should be the default, not a best-effort convention.
Zone scope is the missing knob.
How it works
When you create an API key in the dashboard, there's a new section:
Zone scope ◉ All zones — key can access every zone in this account. ○ Specific zones — key is restricted to the zones you pick.
Pick Specific zones, filter the list, tick the zones you want. The key you get back only works for those zones. Existing keys are unaffected — they remain customer-wide unless you create new scoped keys alongside them.
Under the hood, the zone set is stored in a join table (api_key_zones) and
enforced by middleware on every request before scope checks run. A zone-scoped
key hitting an out-of-scope zone gets a 403, not a "no such zone" — we tell
you it's a scope issue so you can fix it.
What changes for a zone-scoped key
Zone scope layers on top of the existing scopes enum
(zones:read, records:write, and friends). A zone-scoped key still needs
the right scopes to do anything; it just can't reach across zones.
| Endpoint | Customer-wide key | Zone-scoped key |
|---|---|---|
GET /zones | lists all zones | lists only the scoped zones |
GET /zones/:id | any zone in the account | in-scope zones only — 403 otherwise |
POST /zones/:id/records | any zone | in-scope zones only |
GET /zones/:id/dnssec | any zone | in-scope zones only |
POST /zones (create new zone) | allowed | denied 403 |
/usage/*, /billing/* | allowed | denied 403 |
/users/*, /apikeys/* | allowed | denied 403 |
The rule of thumb: a zone-scoped key can do anything a normal key can do, provided the request names a zone in its set. Anything that would widen the scope (creating a new zone, listing users, managing other API keys, reading customer-wide usage or billing) is off-limits — those paths would effectively let a scoped key escape its sandbox.
One subtlety worth calling out: the zone boundary beats the admin scope. An admin+zone-scoped key is still confined to its zones. Admin scope bypasses scope checks, not zone boundaries. That's the whole point of the feature.
A concrete example
Say you run acme.com, acme.dev, and a dozen client zones in the same
DNScale account. You're wiring up Terraform for
your staging environment and want a key that only acme.dev CI can use.
- Dashboard → API Keys → Generate API Key.
- Name it
ci-staging-acme-dev. Tickzones:read,records:read,records:write. - Zone scope: Specific zones → tick
acme.dev. - Create, copy the key into your CI secret store.
Now:
# This works — acme.dev is in scope.
curl -H "Authorization: Bearer $CI_KEY" \
https://api.dnscale.eu/v1/zones/<acme-dev-id>/records
# This 403s — acme.com is outside the scope.
curl -H "Authorization: Bearer $CI_KEY" \
https://api.dnscale.eu/v1/zones/<acme-com-id>/records
# This 403s — creating new zones would escape scope.
curl -H "Authorization: Bearer $CI_KEY" \
-X POST https://api.dnscale.eu/v1/zones \
-d '{"name":"new-zone.com"}'
# GET /zones returns only acme.dev — acme.com and client zones are hidden.
curl -H "Authorization: Bearer $CI_KEY" https://api.dnscale.eu/v1/zonesIf $CI_KEY leaks, the blast radius is one zone instead of your entire
account. Rotate that one key, done.
Patterns we'd recommend
- One key per pipeline, per environment.
ci-staging-acme-dev,ci-prod-acme-com,terraform-client-x. Small keys with narrow names beat big keys with broad names every time. - Pair zone scope with narrow permissions. A CI key that only touches
TXT records for DNS-01 validation needs
records:read+records:writeon one zone — notzones:write, notdnssec:write. - Keep one admin-ish customer-wide key for humans and ops tooling, scope everything else. Nothing prevents you from using both.
- Audit regularly. The dashboard shows "All zones" vs "Scoped to N zones" in the key list. If a key that shouldn't have customer-wide reach still shows "All zones", that's your signal to replace it.
Rollout and compatibility
Zone scoping is locked at creation time in this first version — you pick the zones when the key is generated, and they stay fixed for the life of the key. If you need to change the scope, create a new key and rotate. We'll look at editable scope once the patterns settle.
Existing API keys are not touched. If you never create a scoped key, nothing about your integration changes.
Zone scoping is live now. If you've been holding off on handing an API key to a pipeline, a client, or an automation script because it felt too broad, this is the version to try.