DNS
Host a domain's DNS with us: turn the zone on, keep its records, and know how long a change takes to be seen.
Sandbox only for now
Hosted DNS can be used in the sandbox today. The live side needs a registrar key, and until
this deployment is given one these routes answer 422 not_available there. A sandbox zone is
stored and read back exactly as a live one is, and is served by no real nameserver.
What hosted DNS is here
A domain you register through us can have its DNS hosted by us too, so a customer's name points at a server and receives mail without a second supplier. It is included with the domain: turning it on charges nothing, holds nothing and moves nothing in the wallet.
It is switched on one domain at a time. Every domain carries what you need to decide:
{ "dns": { "supported": true, "hosted": false } }supported is whether the registrar behind this domain hosts zones at all — a registrar that
does not answers 409 dns_not_supported to every route below. hosted is whether we host this
one. Both fields are always there.
Turn it on:
curl -X PUT https://resellerhq.com/api/v1/domains/DOMAIN_ID/dns \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"hosted":true}'The answer is the domain, now on the registrar's own DNS nameservers — in the sandbox, these two:
{
"id": "dom_01K6XJ4Q8ZT3M9R5V2B7WDHNCA",
"domain": "acme.com",
"nameservers": ["ns1.dns.sandbox-registrar.test", "ns2.dns.sandbox-registrar.test"],
"dns": { "supported": true, "hosted": true }
}Three things happen, in this order: the zone is created at the registrar, the domain is pointed at the registrar's own DNS nameservers, and the nameservers it had are kept so turning hosting off can put them back. The zone starts empty — the records you add are the whole of what it answers with.
Read the domain back for the nameservers it is now on, as the sample above does. Those hostnames are the registrar's to publish, so we report what it gave us and never a guessed name; a registrar that publishes none leaves the domain reading back on its default nameservers.
Asking for the state it is already in changes nothing and asks the registrar nothing, so a retry after a timeout is safe.
Reading records needs product.read, and every change below needs domain.manage; see
Team.
Records
A record has a name, a type, content, a TTL, and a priority on the two types that take one. Nine types are on offer:
| Type | What it is for | Content |
|---|---|---|
A | Point a name at a server | An IPv4 address, like 203.0.113.10 |
AAAA | The same, over IPv6 | An IPv6 address, like 2001:db8::1 |
CNAME | Point one name at another name | A hostname. Never at the root, and never beside another record with the same name |
MX | Where mail for the domain goes | A mail host, with a priority; lower is tried first |
TXT | Free text: domain verification, SPF | Up to 4096 characters |
SRV | A service's host and port | weight port target, like 10 5060 sip.example.com |
CAA | Which certificate authorities may issue | <flag> <tag> "<value>", the tag being issue, issuewild or iodef |
NS | Delegate a subdomain elsewhere | A hostname |
ALIAS | Point the root at another name | A hostname. This is what a CNAME cannot do |
The root is @. @ and the domain itself mean the same thing, and a bare label
like www is taken as being under the domain. A name is never empty: "" is refused with
400 validation_error, so write the root as @. Names are stored and returned absolute and lower
case, with any trailing dot stripped, so www, www.acme.com and www.acme.com. are one
name. A wildcard (*) and the underscore labels SRV and the mail conventions use
(_sip._tcp) are both fine. A name that reads as another domain of its own —
www.other-company.com, pasted in by mistake — is refused rather than quietly filed under this
domain.
TTL is in seconds, from 60 to 604800, and defaults to 3600 when you send none. It is how long resolvers may cache the record, and it is what decides how long a change takes to be seen.
List what the zone holds:
curl https://resellerhq.com/api/v1/domains/DOMAIN_ID/dns/records \
-H "Authorization: Bearer $RHQ_API_KEY"{
"records": [
{
"id": "dns_01K6XJ4Q8ZT3M9R5V2B7WDHNCA",
"name": "www.acme.com",
"type": "A",
"content": "203.0.113.10",
"ttl": 3600,
"priority": null
}
]
}The id is ours, and stable: it is what you change or delete that one record by. priority is
always there, and null on a type that has none. A zone holds at most 200 records.
Add a record
curl -X POST https://resellerhq.com/api/v1/domains/DOMAIN_ID/dns/records \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"www","type":"A","content":"203.0.113.10"}'The answer is the record as it was stored, with its id, at 201. Mail takes a priority:
curl -X POST https://resellerhq.com/api/v1/domains/DOMAIN_ID/dns/records \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"@","type":"MX","content":"mail.example.com","priority":10,"ttl":300}'Change a record
Send only the fields you want to change:
curl -X PATCH https://resellerhq.com/api/v1/domains/DOMAIN_ID/dns/records/dns_01K6XJ4Q8ZT3M9R5V2B7WDHNCA \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content":"203.0.113.20","ttl":60}'The id does not change, so the record you edited is the record you read back. Changing the
type to one that has no priority drops the priority with it, rather than sending the
registrar a priority it would refuse.
Delete a record
curl -X DELETE https://resellerhq.com/api/v1/domains/DOMAIN_ID/dns/records/dns_01K6XJ4Q8ZT3M9R5V2B7WDHNCA \
-H "Authorization: Bearer $RHQ_API_KEY"204, with no body. A deleted record's id is gone for good: a later call with it answers
404 not_found.
Replace the whole set
If DNS for a domain lives in a system of your own, swap the zone in one call rather than reconciling it record by record:
curl -X PUT https://resellerhq.com/api/v1/domains/DOMAIN_ID/dns/records \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"records":[{"name":"@","type":"TXT","content":"v=spf1 include:example.com -all"},{"name":"shop","type":"CNAME","content":"shops.example.com"}]}'The answer is the full new set, in the order you sent it. Four things to know about it:
- Every record is checked before any of it is written, so one bad record in a hundred leaves the zone exactly as it was.
- The ids are new. These are new records, not edits of the ones they replace, and the ids of the old set stop working.
- An empty
recordslist empties the zone. - At most 200 records, as everywhere else.
What is refused, and when
Every record is checked here, by us, before the registrar sees any of it — and when a record is refused nothing is sent upstream at all, so a rejected change cannot half-land. The message always names the record it is about:
curl -X POST https://resellerhq.com/api/v1/domains/DOMAIN_ID/dns/records \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"mail","type":"MX","content":"mail.example.com"}'{
"error": "invalid_dns_record",
"message": "Check the MX record for mail.acme.com: a MX record needs a priority between 0 and 65535",
"requestId": "req-4f"
}The rules each refusal comes from:
Acontent must be an IPv4 address andAAAAcontent an IPv6 one;CNAME,ALIAS,NSandMXcontent must be a hostname.MXandSRVneed apriorityfrom 0 to 65535, and every other type refuses one.- A
CNAMEat the root is refused — use anALIASrecord there instead. - A
CNAMEbeside any other record with the same name is refused, in either direction: adding theCNAMEsecond is refused, and so is adding anything else at a name aCNAMEalready holds. TwoCNAMErecords at one name likewise. - A record identical to one already in the zone — same name, type and content — is refused.
- An
SRVname must read_service._proto, and its contentweight port target. ACAAcontent must read<flag> <tag> "<value>".
The plain bounds are the request's own. A TTL outside 60 to 604800, a priority outside 0 to
65535, a content over 4096 characters or a name over 253 come back as
400 validation_error, naming the field, before any of the rules above is reached.
| Error | Meaning |
|---|---|
422 invalid_dns_record | The record's name, type, content or priority breaks one of the rules above. The message says which record and why. |
422 dns_record_conflict | A CNAME cannot sit beside another record with the same name. The message names both records. |
422 too_many_dns_records | A zone holds at most 200 records. The message says how many this change would have left. |
409 dns_not_hosted | Hosted DNS is off for this domain. Turn it on first. |
409 dns_not_supported | The registrar behind this domain does not host DNS. |
409 dns_busy | Another change to the same zone is still being written. Nothing of yours was sent or stored: read the records back and send it again. |
409 domain_expired | The domain is in a state the registrar will not change — expired, still registering, or being checked. |
404 not_found | No such record, or no such domain on your account. |
502 dns_failed | The registrar could not complete the change. Nothing is stored here, so the zone still reads as whatever the registrar still serves. Try again shortly. |
That last one is why a 200 means what it says: a change goes to the registrar first and is
stored here only once the registrar has taken it.
One change to a zone at a time. A zone is one list, read and written whole, so a change
holds it from the moment it is accepted until the registrar has taken it, and a second change
arriving in that window gets 409 dns_busy without anything of it being sent upstream. If you
are loading many records, send them in one PUT …/dns/records rather than in parallel calls.
Switching hosting off
curl -X PUT https://resellerhq.com/api/v1/domains/DOMAIN_ID/dns \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"hosted":false,"nameservers":["ns1.example-dns.com","ns2.example-dns.com"]}'The domain goes onto the nameservers you send — 2 to 6 hostnames, as in any
nameserver change. Leave nameservers
out and it goes back to the ones it was on before hosting was turned on, or to the registrar's
own if it was on those.
The records go with the zone
The zone is given up with the hosting. The records stored here go the moment hosting does —
the record routes answer 409 dns_not_hosted from then on — and the registrar deletes the
unused zone 7 days later. Turning hosting on again gives you an empty zone, not the one you
had, so take a copy with GET …/dns/records first if you may want it back.
Changing the nameservers by hand does the same thing. While we host a domain's DNS its nameservers are ours, so pointing it elsewhere means giving the zone up. A plain update is refused rather than doing that quietly:
curl -X PATCH https://resellerhq.com/api/v1/domains/DOMAIN_ID \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"nameservers":["ns1.theirs.example","ns2.theirs.example"]}'{
"error": "dns_hosted",
"message": "Hosted DNS is on for this domain, so its nameservers are ours. Send releaseHostedDns: true to switch hosting off with this change: the registrar deletes the unused zone after 7 days and the records stored here go with it",
"requestId": "req-4f"
}Send releaseHostedDns: true alongside and the one call does both — hosting off, new
nameservers on:
curl -X PATCH https://resellerhq.com/api/v1/domains/DOMAIN_ID \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"nameservers":["ns1.theirs.example","ns2.theirs.example"],"releaseHostedDns":true}'How long a change takes to be seen
The API answers as soon as the registrar has the change. Being seen is a separate clock, and it is not one we can wind forward.
- A record you change or delete is cached by every resolver that already asked for it, for as long as that record's own TTL. The TTL that governs is the one on the record you are replacing, not the new one: drop a record from 3600 to 60 and the hour still has to pass before anyone notices the 60. So when a change is planned, lower the TTL first, wait out the old value, then make the change on the short TTL and put the TTL back afterwards.
- A record you add at a name nothing has looked up yet shows up as soon as our nameservers answer for it — though a resolver that asked for that name and got nothing may hold the nothing for a short while too.
- Turning hosting on or off, and any nameserver change, is a change at the registry rather than in the zone. Registries and resolvers cache a delegation for far longer than a record: allow up to a day for one to reach everyone, and expect some resolvers to be answering from the old nameservers in the meantime.
- Neither clock can be hurried. There is no flush, here or at the registrar: a cache belongs to whoever is holding it. Checking from a machine that never asked before — or against our nameservers directly — tells you the zone is right; it does not tell you the internet has caught up yet.
In the dashboard
A domain's own page carries a DNS panel saying whether hosting is on and how many records there are, and its Manage DNS link opens the zone as a page of its own: one table of records, Add record, a row menu to edit or delete one, and a Manage menu holding the Google Workspace, Microsoft 365 and point-at-an-IP presets and Turn hosted DNS off.