# Errors

Every error says what went wrong in plain words. The texts are the same in all six styles; only the shape around them changes. This page shows each shape, lists the texts, and says what to do.

## How errors look in each style

| Style | HTTP status | Where the text is |
|---|---|---|
| [Dynadot](https://maildns.io/docs/dynadot.md#replies) | Always 200 | `"ResponseCode": "-1"` and `Error` |
| [Spaceship](https://maildns.io/docs/spaceship.md#requests-and-replies) | 4xx / 5xx | `{"detail": "...", "data": []}` |
| [Cloudflare](https://maildns.io/docs/cloudflare.md#requests-and-replies) | 4xx / 5xx | `"success": false`, `errors[0].message` (and `errors[0].code`) |
| [GoDaddy](https://maildns.io/docs/godaddy.md#requests-and-replies) | 4xx / 5xx | `{"code": "...", "message": "..."}` |
| [Porkbun](https://maildns.io/docs/porkbun.md#requests-and-replies) | 4xx / 5xx | `{"status": "ERROR", "message": "..."}` |
| [Namecheap](https://maildns.io/docs/namecheap.md#requests-and-replies) | Always 200 | `Status="ERROR"` and `<Error Number="...">` |

The same refusal in each:

```json
{"SetDnsResponse": {"ResponseCode": "-1", "Status": "error", "Error": "This API key doesn't work for nameservers."}}
{"detail": "This API key doesn't work for nameservers.", "data": []}
{"success": false, "errors": [{"code": 1004, "message": "This API key doesn't work for nameservers."}], "messages": [], "result": null}
{"code": "ACCESS_DENIED", "message": "This API key doesn't work for nameservers."}
{"status": "ERROR", "message": "This API key doesn't work for nameservers."}
```

```xml
<ApiResponse Status="ERROR" xmlns="http://api.namecheap.com/xml.response">
  <Errors>
    <Error Number="2011280">This API key doesn't work for nameservers.</Error>
  </Errors>
</ApiResponse>
```

In Dynadot style, errors before a command runs (key, command name, rate limit) use a plain `Response` object: `{"Response": {"ResponseCode": "-1", "Error": "invalid key"}}`. Test `str(ResponseCode) == "0"` for success.

When you match on texts in code, match on the start: the part after a semicolon or colon (a name, a number, a hint) varies.

The **HTTP** column below is for the four styles that use status codes. Cloudflare's numeric codes and Namecheap's error numbers are on their pages.

## Key, command and limits

| Error | HTTP | Cause and fix |
|---|---|---|
| `invalid key` | 401 (Cloudflare and Porkbun: 403) | Wrong key or secret, a revoked key, or a call from an IP address the key is not locked to. Check the key and where you call from. |
| `incorrect command name` | 404 | Unknown command or path. Check the spelling against the style's reference. |
| `command not available on this account` | 403 | A call outside DNS records (`register`, `renew`, contacts, transfers, ...). Not possible with MailDNS. |
| `XML is not supported: call /api3.json` | | Dynadot style only: you called `/api3.xml`. |
| `too many requests; retry in 0.4 seconds` | 429 | Over 10 requests per second. Wait `Retry-After` seconds and send the same call again. |
| `another change to this domain is still running; retry in a few seconds` | 429 | An earlier change to the same root domain took more than 60 seconds. Nothing was changed. Retry. |

## Nameservers

| Error | HTTP | Cause and fix |
|---|---|---|
| `This API key doesn't work for nameservers.` | 403 | A nameserver change or an NS record, in any style. No key can do this. Don't retry. |

## Domains and names

| Error | HTTP | Cause and fix |
|---|---|---|
| `domain parameter not entered` | 422 | The call names no domain. |
| `could not find domain in your account` | 404 | The domain is neither one of your key's hosts nor the root of one. List your hosts to check. |
| `domain not found` | 404 | A host lookup (`/api/v1/domains/{domain}`, `/v1/domains/{domain}`) with a name that is not one of your hosts. |
| `www.example.com is not one of your hosts` | 403 | A record name outside your hosts. Records go at or under a host. |
| `main records of example.com are not on your account; ...` | 403 | A record at the root domain (a main record, or `@`) when your host is a name inside it. Address the host, or use a subhost. See [Hosts](https://maildns.io/docs/hosts.md). |
| `subdomain0 is empty` | | Dynadot style: a `subdomain{n}` that is empty or `@`. For a record at the domain, use `main_record{n}`. |

## Records

| Error | HTTP | Cause and fix |
|---|---|---|
| `no dns record entered` | 422 | A write with no records in it. |
| `record type SRV is not allowed (allowed: A, AAAA, CNAME, MX, TXT)` | 422 | Only these five types. (NS gets the nameserver answer above.) |
| `empty value for TXT record` | 422 | A record with no value. |
| `TXT value is 2310 characters (max 2048)` | 422 | TXT values are limited to 2,048 characters. |
| `TXT value must be printable ASCII` | 422 | No line breaks, tabs or non-ASCII characters in TXT values. |
| `invalid MX priority: 'ten'`, `MX priority out of range: 70000` | 422 | MX priority is a whole number from 0 to 65535. |
| `invalid ttl: 'one hour'` | 422 | TTL is a whole number of seconds. |
| `invalid IPv4 address: ...`, `invalid IPv6 address: ...` | 422 | `A` takes an IPv4 address, `AAAA` an IPv6 address. |
| `invalid name: ...` | 422 | A name with characters DNS does not allow. Letters, digits, `-` and `_`, separated by dots. |
| `51 records under one host (max 50)` | 422 | A host holds at most 50 records, at and under it. |
| `a CNAME cannot share a name with other records (mail)`, `only one CNAME per name` | 422 | Move the other records first. (Spaceship style: `"force": true` replaces them.) |

Cloudflare style answers record errors with HTTP 400, code 1004, as Cloudflare does.

## When MailDNS can't make the change

| Error | HTTP | Cause and fix |
|---|---|---|
| `example.com has expired; records can't be changed` | 503 | The root domain has expired. Its records can't be changed until it is renewed. Tell whoever issued your key. |
| `example.com is not active at its registry (...); records can't be changed` | 503 | The root domain is on hold or otherwise not active. Tell whoever issued your key. |
| `this domain is temporarily unavailable; ...` | 503 | The domain can't be changed right now. Retry in a few minutes. |
| `changes to this domain are paused; ...` | 503 | Changes to this domain are on hold. Tell whoever issued your key. |
| `the change could not be saved and nothing was changed; ...` | 503 | The change was rolled back as a whole. Retry later. |
| `DNS for this domain is not served by MailDNS, so records can't be changed here` | 503 | This domain's DNS is hosted somewhere MailDNS doesn't write to. Tell whoever issued your key. |
| `internal error` | 500 | Unexpected. Retry once; if it repeats, tell whoever issued your key. |

The texts ending in `...` carry a short note after the semicolon. It never changes what you should do.

## Retrying safely

Every write is safe to send twice:

- **Adds** (Dynadot `set_dns2` with `add_dns_to_current_setting=1`, Spaceship `PUT`, Cloudflare `POST`, GoDaddy `PATCH`, Porkbun `/dns/create`) skip records that are already there.
- **Replaces** (Dynadot `set_dns2` without the add flag, GoDaddy `PUT`, Namecheap `setHosts`) end in the same state however often you send them.
- **Deletes**: Spaceship's `DELETE` ignores records that are already gone. Deletes by id or by name (Cloudflare, GoDaddy, Porkbun) answer "not found" for a record that is already gone: treat that as done.

So after a timeout or a dropped connection, send the same call again. Retry automatically only the rate limit, the busy error and 503s, with a pause. Errors about the key, a name, a record or nameservers will fail the same way every time: fix the call instead.
