Docs pages

Porkbun-style API#

https://api.maildns.io/api/json/v3 takes the same paths and JSON bodies as Porkbun's API v3 for DNS records. Point a Porkbun client at that base URL and give it your MailDNS key and secret.

Requests and replies#

Every call is a POST with a JSON body that carries the key and secret, plus the call's own fields:

JSON
{"apikey": "KEY", "secretapikey": "SECRET"}
  • {domain} in a path is one of your hosts (advisor.example.com) or the root domain they live in (example.com). See Hosts.
  • In requests, name (and {subdomain} in paths) is the part in front of {domain}: _dmarc, google._domainkey. Leave it empty for {domain} itself. Replies give full names, as Porkbun does.
  • Record ids are numeric strings.
  • Success: {"status": "SUCCESS", ...}. Errors: {"status": "ERROR", "message": "..."} with a 4xx HTTP status (429 when you should wait and retry, 503 when the change can't be made right now). Check status; the message texts are listed in Errors.
JSON
{"status": "ERROR", "message": "This API key doesn't work for nameservers."}

Endpoints#

Path (all POST) Does
/ping Check the key; returns the IP address you called from
/domain/listAll Your hosts
/domain/getNs/{domain} The nameservers (read-only)
/domain/updateNs/{domain} Refused: nameservers
/dns/retrieve/{domain} All records
/dns/retrieve/{domain}/{id} One record
/dns/retrieveByNameType/{domain}/{type}[/{subdomain}] Records of one type at one name
/dns/create/{domain} Create a record; returns its id
/dns/edit/{domain}/{id} Change one record
/dns/editByNameType/{domain}/{type}[/{subdomain}] Change every record of that type at that name
/dns/delete/{domain}/{id} Delete one record
/dns/deleteByNameType/{domain}/{type}[/{subdomain}] Delete every record of that type at that name

Check the key#

Shell
curl https://api.maildns.io/api/json/v3/ping \
  -H 'Content-Type: application/json' \
  -d '{"apikey": "KEY", "secretapikey": "SECRET"}'
JSON
{"status": "SUCCESS", "yourIp": "203.0.113.7"}

List hosts#

Shell
curl https://api.maildns.io/api/json/v3/domain/listAll \
  -H 'Content-Type: application/json' \
  -d '{"apikey": "KEY", "secretapikey": "SECRET"}'
JSON
{
  "status": "SUCCESS",
  "domains": [
    {
      "domain": "advisor.example.com",
      "status": "ACTIVE",
      "tld": "com",
      "createDate": "2025-01-01 00:00:00",
      "expireDate": "2027-01-01 00:00:00",
      "securityLock": "1",
      "whoisPrivacy": "1",
      "autoRenew": 1,
      "notLocal": 0
    }
  ]
}

The dates describe the root domain. /domain/getNs/advisor.example.com replies {"status": "SUCCESS", "ns": ["ns1.example.net", "ns2.example.net"]}.

Read records#

Shell
curl https://api.maildns.io/api/json/v3/dns/retrieve/advisor.example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey": "KEY", "secretapikey": "SECRET"}'
JSON
{
  "status": "SUCCESS",
  "records": [
    {"id": "106926652", "name": "advisor.example.com", "type": "MX", "content": "smtp.google.com", "ttl": "3600", "prio": "1", "notes": ""},
    {"id": "106926653", "name": "advisor.example.com", "type": "TXT", "content": "v=spf1 include:_spf.google.com ~all", "ttl": "3600", "prio": "0", "notes": ""},
    {"id": "106926654", "name": "_dmarc.advisor.example.com", "type": "TXT", "content": "v=DMARC1; p=quarantine", "ttl": "3600", "prio": "0", "notes": ""}
  ]
}

One type at one name: /dns/retrieveByNameType/advisor.example.com/TXT/_dmarc. For {domain} itself, leave the subdomain off: /dns/retrieveByNameType/advisor.example.com/TXT.

Create a record#

Shell
curl https://api.maildns.io/api/json/v3/dns/create/advisor.example.com \
  -H 'Content-Type: application/json' \
  -d '{"apikey": "KEY", "secretapikey": "SECRET",
       "name": "", "type": "TXT", "ttl": "3600",
       "content": "google-site-verification=3J9tq0iV8pTt1yW5zKc2mE7aR4bN6hD0sF1gL8xQ"}'
JSON
{"status": "SUCCESS", "id": "106926659"}
Field Value
name The part in front of {domain} (_dmarc, google._domainkey), or empty for {domain} itself
type A, AAAA, CNAME, MX or TXT
content The value. TXT without surrounding quotes, in one piece.
ttl Optional; see the TTL rule
prio MX priority, 0 to 65535

If the same record (type, name, content) is already there, you get its id back instead of a second copy, so retrying a create is harmless.

Change records#

By id, sending the whole record:

Shell
curl https://api.maildns.io/api/json/v3/dns/edit/advisor.example.com/106926654 \
  -H 'Content-Type: application/json' \
  -d '{"apikey": "KEY", "secretapikey": "SECRET",
       "name": "_dmarc", "type": "TXT", "content": "v=DMARC1; p=reject"}'

Or every record of a type at a name, sending only the new values:

Shell
curl https://api.maildns.io/api/json/v3/dns/editByNameType/advisor.example.com/TXT/_dmarc \
  -H 'Content-Type: application/json' \
  -d '{"apikey": "KEY", "secretapikey": "SECRET", "content": "v=DMARC1; p=reject"}'

Record ids come from the record itself, so an edited record gets a new id. Retrieve the records again before using an id after an edit.

Delete records#

Shell
curl https://api.maildns.io/api/json/v3/dns/delete/advisor.example.com/106926654 \
  -H 'Content-Type: application/json' \
  -d '{"apikey": "KEY", "secretapikey": "SECRET"}'

/dns/deleteByNameType/advisor.example.com/TXT/_dmarc deletes every TXT record at _dmarc.advisor.example.com.

Nameservers#

/domain/updateNs/{domain}, and creating an NS record, reply {"status": "ERROR", "message": "This API key doesn't work for nameservers."}. Reading them with /domain/getNs/{domain} is fine.

Check that a record is live#

The live check is a Dynadot-style call, and it takes the same key:

Shell
curl https://api.maildns.io/api3.json \
  --data-urlencode key=KEY --data-urlencode command=dns_status \
  --data-urlencode domain=advisor.example.com --data-urlencode subdomain=@ \
  --data-urlencode record_type=txt \
  --data-urlencode value=google-site-verification=3J9tq0iV8pTt1yW5zKc2mE7aR4bN6hD0sF1gL8xQ

Wait for "Live": "yes" before asking Google or Microsoft to verify.