Keys and limits#
What a key is, what it can reach and read, and the limits every call runs under.
Your key#
- One key per vendor or team. It carries a name, which
account_inforeturns asUsername. - One key works in all six API styles. Some styles also send the key's secret:
| Style | How the key is sent |
|---|---|
| Dynadot | key=KEY parameter |
| Spaceship | X-API-Key: KEY and X-API-Secret: SECRET headers |
| Cloudflare | Authorization: Bearer KEY (or X-Auth-Key: KEY with any X-Auth-Email) |
| GoDaddy | Authorization: sso-key KEY:SECRET |
| Porkbun | "apikey": "KEY", "secretapikey": "SECRET" in the JSON body |
| Namecheap | ApiKey=KEY parameter (ApiUser, UserName and ClientIp can be anything) |
- A key's domains and hosts are attached when it is issued. They can't be added or removed through the API; ask whoever issued your key.
- Prefer
POSTfor Dynadot-style calls: the key goes in the body instead of a URL that might end up in a log. - Give the key to scripts and agents through an environment variable (for example
MAILDNS_KEY), never inside a prompt or a file you commit.
What a key can read#
Only what it was issued for. Every list call (list_domain, GET /zones, GET /v1/domains, /domain/listAll, getList, ...) returns just the key's own domains and hosts, and every read returns just their records. Other domains in your registrar accounts don't show up anywhere: asking for one by name answers as if it didn't exist.
Nameservers: never#
No key can change nameservers, add NS records, or touch registration. In every style the answer is the same text, in that style's error shape:
This API key doesn't work for nameservers.| Style | Refused calls include |
|---|---|
| Dynadot | set_ns, add_ns, register_ns, NS records in set_dns2 |
| Spaceship | Nameserver updates, NS items in PUT |
| Cloudflare | NS records; changing a zone's nameservers (HTTP 403, code 1004) |
| GoDaddy | nameServers in PATCH /v1/domains/{domain}, NS records (HTTP 403, ACCESS_DENIED) |
| Porkbun | /domain/updateNs/{domain}, NS records |
| Namecheap | namecheap.domains.dns.setCustom, setDefault, namecheap.domains.ns.*, NS records in setHosts |
Reading the current nameservers is fine (get_ns, zone name_servers, nameServers, /domain/getNs, namecheap.domains.dns.getList). Don't retry a refused call: it will never succeed.
IP lock#
A key can be locked to the caller IP addresses (single addresses or ranges) you give us, the way Dynadot's API IP list works. A call from any other address is treated as a wrong key:
| Style | What you get |
|---|---|
| Dynadot | HTTP 200, {"Response": {"ResponseCode": "-1", "Error": "invalid key"}} |
| Spaceship | HTTP 401, {"detail": "Unauthorized", "data": []} |
| Cloudflare | HTTP 403, error code 10000 |
| GoDaddy | HTTP 401, UNABLE_TO_AUTHENTICATE |
| Porkbun | HTTP 403, {"status": "ERROR", "message": "invalid key"} |
| Namecheap | HTTP 200, Status="ERROR", error number 1011102 |
To change the list, ask whoever issued your key.
Rate limit#
10 requests per second per key, with bursts of up to 20. Over the limit:
| Style | What you get |
|---|---|
| Dynadot | HTTP 200, {"Response": {"ResponseCode": "-1", "Error": "too many requests; retry in 0.4 seconds"}} and a Retry-After header |
| Spaceship | HTTP 429, {"detail": "Too many requests", "data": []} and a Retry-After header |
| Cloudflare, GoDaddy, Porkbun | HTTP 429 in the style's error shape, with a Retry-After header |
| Namecheap | HTTP 200, Status="ERROR", error number 500000 |
Wait the number of seconds in Retry-After, then send the same call again. Nothing was changed by the refused call.
One change at a time per root domain#
Changes to the same root domain are applied one at a time, in the order they arrive. If an earlier change to that root is still running, your call waits for it, for up to 60 seconds.
- Set your HTTP client's timeout to at least 90 seconds for writes.
- If the wait runs out, the call fails with
another change to this domain is still running; retry in a few seconds(HTTP 429 in the styles that use status codes). Nothing was changed; send it again. - To change many hosts in the same root, one call with all of them (root form, see Hosts) is faster than one call per host.
Record rules#
| Rule | Limit |
|---|---|
| Record types | A, AAAA, CNAME, MX, TXT. Anything else is refused. |
| Records per host | 50, counting the records at the host and under it |
| TXT value | Up to 2,048 characters of printable ASCII (a 2048-bit DKIM key fits) |
| MX priority | 0 to 65535 |
| TTL | Records are served with the domain's TTL (usually 3600 seconds). See below. |
| CNAME | Can't share a name with any other record, and only one CNAME per name |
Records per set_dns2 call |
Up to 100 main records and 100 subdomain records |
TTL. MailDNS never changes a domain's TTL: it is shared with records that aren't yours. The Dynadot-style ttl parameter is accepted, so existing code keeps working, but your records get the domain's TTL. A Spaceship-style item's ttl (300 to 86400) is used where the domain supports a TTL per record; otherwise the domain's TTL applies. get_dns and GET /api/v1/dns/records show the TTL actually served.
TXT values go as one string, without surrounding quotes, even when they are longer than 255 characters. Don't split a DKIM key into quoted pieces yourself.
What a key can't do#
- Reach names outside its domains and hosts. When a key is issued for a host such as
advisor.example.com, the root domain's own records (example.com,www.example.com) are not yours: they are never shown to you and can't be changed. - Change nameservers: see Nameservers: never.
- Change registration, forwarding, contacts, transfers or renewals. Dynadot commands such as
register,renew,delete,push,set_forwarding,set_stealth,set_email_forwardandset_whoisreply{"Response": {"ResponseCode": "-1", "Error": "command not available on this account"}}; the other styles refuse them the same way.