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:
{"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). Checkstatus; themessagetexts are listed in Errors.
{"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#
curl https://api.maildns.io/api/json/v3/ping \
-H 'Content-Type: application/json' \
-d '{"apikey": "KEY", "secretapikey": "SECRET"}'{"status": "SUCCESS", "yourIp": "203.0.113.7"}List hosts#
curl https://api.maildns.io/api/json/v3/domain/listAll \
-H 'Content-Type: application/json' \
-d '{"apikey": "KEY", "secretapikey": "SECRET"}'{
"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#
curl https://api.maildns.io/api/json/v3/dns/retrieve/advisor.example.com \
-H 'Content-Type: application/json' \
-d '{"apikey": "KEY", "secretapikey": "SECRET"}'{
"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#
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"}'{"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:
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:
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#
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:
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=3J9tq0iV8pTt1yW5zKc2mE7aR4bN6hD0sF1gL8xQWait for "Live": "yes" before asking Google or Microsoft to verify.