API & MCP

Let other services and AI agents create zones, manage records and check delegation. The REST API and the MCP server share one set of operations, one permission model and one audit log.

Authentication

An admin creates a token on the API tokens page of the panel. Send it as a bearer token on every request:

curl https://api.dnsaman.com/api/v1/token \
  -H "Authorization: Bearer dnsa_…"

Each token has an owner and a set of permissions:

PermissionAllows
dns.readList zones, read records, check delegation.
dns.writeCreate, update and delete records in reachable zones.
zone.createCreate zones. A non-admin owner is granted the zone it creates.
zone.deleteDelete reachable zones. Irreversible.

Security model

REST API

Base URL https://api.dnsaman.com/api/v1. Requests and responses are JSON. Successful responses look like {"success": true, "result": …}, and failures like {"success": false, "errors": [{"message": "…"}]}.

MethodPathPermissionNotes
GET/api/v1/tokenanyWho this token is and what it may do
GET/api/v1/zonesdns.readZones this token can reach
POST/api/v1/zoneszone.createBody: {"name", "hostmaster"?}
GET/api/v1/zones/{zone}dns.readZone, nameservers and all records
DELETE/api/v1/zones/{zone}zone.deleteZone and every record in it
GET/api/v1/zones/{zone}/recordsdns.readQuery: ?name=&type=
POST/api/v1/zones/{zone}/recordsdns.writeBody: {"name", "type", "content", "ttl"?, "proxied"?}
PATCH/api/v1/zones/{zone}/records/{id}dns.writeAny subset of the record fields
DELETE/api/v1/zones/{zone}/records/{id}dns.write
GET/api/v1/zones/{zone}/delegationdns.readIs the domain pointed at us, and is every node current
# Create a record
curl -X POST https://api.dnsaman.com/api/v1/zones/example.com/records \
  -H "Authorization: Bearer $DNSAMAN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "mail", "type": "A", "content": "203.0.113.25", "ttl": 3600}'

# → 201
{"success": true, "result": {"id": 42, "name": "mail", "type": "A", "ttl": 3600,
                             "content": "203.0.113.25", "proxied": false}}

# Change just the TTL
curl -X PATCH https://api.dnsaman.com/api/v1/zones/example.com/records/42 \
  -H "Authorization: Bearer $DNSAMAN_TOKEN" \
  -H "Content-Type: application/json" -d '{"ttl": 300}'

Every change is published to all nameservers before the response returns. Creating a record that already exists with the same name, type and content returns 400, so a retried request does not create a second copy.

Record format

FieldFormat
name"@" for the apex, a relative label such as "www" or "_dmarc", or a full name inside the zone. "*" and "*.sub" are wildcards.
typeA, AAAA, CNAME, MX, TXT, NS, SRV, CAA, PTR
contentRDATA as in a zone file: "10 mx.example.com" (MX), "0 issue \"letsencrypt.org\"" (CAA), "10 5 443 host.example.com" (SRV). Dotted hostnames are made absolute.
ttl60 to 604800 seconds. Defaults to the zone TTL.
proxiedA/AAAA only. Publishes the edge address instead of the origin.

MCP

The MCP endpoint at https://api.dnsaman.com/api/mcp uses Streamable HTTP and is stateless: send JSON-RPC over POST with the same bearer token. Agents only see the tools their token has permission for.

Claude Code

claude mcp add --transport http dnsaman https://api.dnsaman.com/api/mcp \
  --header "Authorization: Bearer dnsa_…"

Any client with a JSON config (Claude Desktop via mcp-remote, Cursor, …)

{
  "mcpServers": {
    "dnsaman": {
      "type": "http",
      "url": "https://api.dnsaman.com/api/mcp",
      "headers": { "Authorization": "Bearer dnsa_…" }
    }
  }
}
ToolPermissionWhat it does
verify_tokenanyShow which account and permissions this API token carries.
list_zonesdns.readList the zones this token can reach.
get_zonedns.readGet a zone, its authoritative nameservers and all of its records.
create_zonezone.createCreate a zone. Returns the nameservers the domain owner must set at their registrar.
delete_zonezone.deleteDelete a zone and every record in it. Irreversible.
list_recordsdns.readList records in a zone, optionally filtered by name and/or type.
create_recorddns.writeAdd a record to a zone and publish it. Exact duplicates are refused.
update_recorddns.writeChange a record. Fields left out keep their current value.
delete_recorddns.writeDelete a record by id.
check_delegationdns.readCheck whether the domain is delegated to our nameservers and every node serves the current zone.

Tool arguments are the same fields as the REST bodies, plus zone (and id for record operations).

Example: onboarding a mail domain

The usual flow for a service like a mail provider adding a customer domain. It uses a token with dns.read dns.write zone.create owned by a dedicated user:

T="Authorization: Bearer $DNSAMAN_TOKEN"; J="Content-Type: application/json"
API=https://api.dnsaman.com/api/v1

# 1. Create the zone. result.nameservers is what the customer sets at their registrar.
curl -X POST $API/zones -H "$T" -H "$J" -d '{"name": "customer.com"}'

# 2. Add the mail records.
curl -X POST $API/zones/customer.com/records -H "$T" -H "$J" \
  -d '{"name": "@", "type": "MX", "content": "10 mx1.mail.example.net"}'
curl -X POST $API/zones/customer.com/records -H "$T" -H "$J" \
  -d '{"name": "@", "type": "TXT", "content": "v=spf1 include:_spf.mail.example.net -all"}'
curl -X POST $API/zones/customer.com/records -H "$T" -H "$J" \
  -d '{"name": "rm1._domainkey", "type": "TXT", "content": "v=DKIM1; k=rsa; p=MIIB…"}'
curl -X POST $API/zones/customer.com/records -H "$T" -H "$J" \
  -d '{"name": "_dmarc", "type": "TXT", "content": "v=DMARC1; p=quarantine"}'

# 3. Poll until the registrar change is live (results are cached for 30 s).
curl $API/zones/customer.com/delegation -H "$T"
# → result.delegated && result.ok once the internet sees our nameservers

Errors

StatusMeaning
400Validation failed: bad record content, duplicate record, zone already exists
401Missing, wrong, expired or revoked token, disabled owner, or an IP outside the allowlist
403The token lacks the permission this operation needs
404No such zone or record, or one this token may not see (same answer on purpose)
413Body over 64 KiB
429Too many failed authentications from this IP; wait out the lockout

Over MCP, a failed tool call comes back as a normal result with isError: true and the same message, so the agent can read it and correct its request.