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.
URL forwarding
Redirects the domain - or a name under it - to a destination you already have, with no server of your own. It is a permanent (301) redirect: the registrar's own zone API offers no temporary kind, so there is nothing to choose.
curl -X PUT https://resellerhq.com/api/v1/domains/DOMAIN_ID/forwarding \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"records":[{"target":"https://example.com"},{"subdomain":"shop","target":"https://shop.example.com"}]}'subdomain is empty or left out for the root. The answer is the full new set, as URL records
carrying "managedBy":"url_forwarding". This call replaces every URL-forwarding record at
once - anything hand-made, or made by email forwarding, is untouched - so send every entry you
want kept, and an empty records list removes URL forwarding entirely.
A destination that is not an absolute http:// or https:// URL is refused with 422 invalid_forward_url. A name that already holds a record of any kind - including another URL
forward - is refused the same way a second CNAME there would be.
Email forwarding
Forwards mail addressed to this domain to another inbox you already have - your own Gmail, say
- with no mail server of your own.
curl -X PUT https://resellerhq.com/api/v1/domains/DOMAIN_ID/email-forwarding \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"records":[{"address":"sales","target":"[email protected]"},{"target":"[email protected]"}]}'address is the local part: sales forwards mail sent to [email protected]. Leave it out,
or send "*", for the catch-all - everything else sent to the domain. The answer is the full new
set, as MBOXFW records carrying "managedBy":"email_forwarding". Like URL forwarding, this
call replaces every email-forwarding record at once, and an empty records list removes it
entirely.
A destination that is not an email address is refused with 422 invalid_forward_address.
Managed records
A record either of these made carries managedBy - "url_forwarding" or "email_forwarding" -
when you read the zone back (GET …/dns/records). Change or remove it from its own
route above, never the generic record routes, which refuse both:
{
"error": "dns_record_managed",
"message": "This record is managed by forwarding; change it from the forwarding settings instead",
"requestId": "req-4f"
}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. A URL-forwarding entry is exclusive the same way. |
422 invalid_forward_url | A URL-forwarding destination is not an absolute http:// or https:// URL. |
422 invalid_forward_address | An email-forwarding destination is not an email address. |
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_record_managed | This record was made by URL or email forwarding. Change it from PUT …/forwarding or PUT …/email-forwarding instead. |
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.
A domain connected for mailboxes has its DNS
hosted with them: a nameserver change away from the mail provider's own is refused with 409 nameservers_used_by_mailboxes unless the same call also sends acknowledgeMailboxes: true, and
turning DNSSEC on or off (or managing its keys) is refused outright with 409 dnssec_not_available - there is nothing to sign while the zone is theirs.
One change to a domain's DNS at a time. A record change, switching hosting on or off, and
the nameserver change that gives a hosted zone up all share one lock per domain: whichever of
them is accepted first holds it from that moment until the registrar has taken the change, and
anything else that would touch the zone or the delegation while it stands - another record
change, a second attempt to switch hosting, a release racing a record change - 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.
DNSSEC
Signs a domain so a resolver can tell its records are genuine rather than forged along the way. Every domain carries whether its own ending can be signed at all, whether it is signed now, and what the registry holds for it:
{ "dns": { "dnssecSupported": true, "signed": false, "dsRecords": [], "keys": [] } }dnssecSupported is a fact about the ending, not about the registrar - it can be true
even where supported above is false, and the other way round, since a registrar that hosts
zones fine may still refuse DNSSEC on a given TLD. An ending that refuses it answers 422 dnssec_unavailable to both routes below. dsRecords is the DS data the registry holds right
now; empty means not signed. keys only means anything once DNS is hosted elsewhere - it is
the last list sent with PUT …/dnssec.
A zone we host
One flag. We manage the keys and any rollover ourselves:
curl -X PUT https://resellerhq.com/api/v1/domains/DOMAIN_ID/dns \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"dnssec":true}'dnssec can be sent together with hosted or on its own, as long as the domain is already
hosted with us. Turning hosted DNS off turns DNSSEC off with it - there is no zone left to sign
- and the DS records go with it the same way the records do.
DNS hosted elsewhere
Send the keys your own DNS provider generated, so the registry can add the matching DS record:
curl -X PUT https://resellerhq.com/api/v1/domains/DOMAIN_ID/dnssec \
-H "Authorization: Bearer $RHQ_API_KEY" \
-H "Content-Type: application/json" \
-d '{"keys":[{"flags":257,"algorithm":13,"publicKey":"BASE64_PUBLIC_KEY"}]}'flags is 256 for a zone-signing key or 257 for a key-signing key; algorithm is one of
the IANA DNSSEC algorithm numbers (3, 5, 6, 7, 8, 10, 12, 13, 14, 15, 16, 17, 23); publicKey
is base64, whitespace tolerated and stripped before it is checked or sent - a key copied out of
a zone file wraps across lines. An empty keys list removes every key and, with it, every DS
record the registry holds - the only way one is ever removed. This route is refused with
409 dns_hosted while DNS is hosted with us: sign or unsign that zone with PUT …/dns instead.
What can go wrong
- A wrong key makes the domain unreachable until it is fixed or removed: a resolver that validates DNSSEC refuses every answer it cannot verify against the DS record at the registry, rather than falling back to an unsigned one. Copy each value exactly from your DNS provider, and check a domain with a DNSSEC checker before relying on it.
- A malformed key - the wrong
flags, an algorithm not in the list above, or an empty or non-base64publicKey- is refused with422 invalid_dnssec_key, naming which key, before anything reaches the registry. - Signing or unsigning a hosted zone is a change at the registry, so - like a nameserver change
- it can take up to a day to reach every resolver. A DS
record that has not arrived yet reads back as an empty
dsRecordslist even thoughsignedis alreadytrue.
- it can take up to a day to reach every resolver. A DS
record that has not arrived yet reads back as an empty
- DS data is read-only in the direction that adds it: you can only ever remove what the registry holds (by clearing the keys, or by unsigning a hosted zone), never add a DS record directly. Sending a matching key is the only way one appears.
| Error | Meaning |
|---|---|
422 dnssec_unavailable | This ending does not support DNSSEC. |
422 invalid_dnssec_key | One of the keys sent is malformed. The message names which key and why. |
422 too_many_dnssec_keys | At most 4 keys are allowed in one list. |
409 dns_hosted | DNS for this domain is hosted with us; sign or unsign it with PUT …/dns instead of sending keys. |
Branded nameservers
Useful when you want a domain's nameservers to read as your own name rather than ours or the
registrar's - a customer who looks a domain up sees ns1.yourcompany.com, not
ns1.sandbox-registrar.test.
It takes a domain you already hold with us, to carry 2 to 4 host names pointed at fixed, registrar-documented addresses - the same kind of hostnames child nameservers already are, just carrying every other domain's DNS rather than their own.
Set it up once, in Settings → Ordering defaults: pick the domain, name its hostnames, and wait for them to be ready. From then on it is a plain nameserver change on whichever domains you choose to point at it - one at a time, or several together from the domains list's bulk actions - or automatically on every new domain, once the set is ready.
Unlike a change to nameservers elsewhere, moving a domain onto the branded set never gives up
hosted DNS: a domain that is already hosted keeps its zone and records, and one that is not
starts being hosted with us. releaseHostedDns is never needed for this change.
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. Below the table, URL forwarding and Email forwarding are two short sections of their own, each with its own Add forwarding dialog; a record either one made shows in the records table above with a "Forwarding" badge and a link to its own section, rather than a row menu.
Below that, on an ending that supports DNSSEC, a DNSSEC panel: a Sign this zone switch and the DS record the registry holds when we host the zone, or a Manage keys button opening a dialog to paste the keys your own DNS provider generated when we do not. An ending that cannot be signed shows no panel at all.
In the API reference
Turn hosted DNS on or off
Host the zone with us, or hand DNS back.
List a zone's records
Everything the zone holds, with ids.
Add a record
One record, checked before it is written.
Change a record
Only the fields you send.
Delete a record
One record, by its id.
Replace every record
The whole set in one call.
Set URL forwarding
Redirect the root, or a name under it, to a destination URL.
Set email forwarding
Forward an address, or the catch-all, to another inbox.
Set the DNSSEC keys for a domain hosted elsewhere
The keys your own DNS provider generated. Empty removes them all.