Retrieving and Creating DNS Records
All DNS information is composed of so-called Resource Record Sets
(RRsets). An RRset is the set of all Resource Records of a given record
type for a given name. For example, the name example.com may have an
RRset of type A, denoting the set of IPv4 addresses associated with this
name. In the traditional BIND zone file format, the RRset would be written
as:
<name> 3600 IN A 127.0.0.1
<name> 3600 IN A 127.0.0.2
...
where 3600 is the number of seconds for which DNS resolvers may cache the information before asking again (time-to-live, short: TTL).
Each of these lines is a Resource Record, and together they form an RRset.
The basic units accessible through the API are RRsets (not individual Resource Records). The TTL is a property of an RRset; and all records of an RRset therefore share the record type and also the TTL.
RRsets are each represented by a JSON object. The object structure is detailed in the next section.
The relevant endpoints all reside under /api/v1/domains/{name}/rrsets/,
where {name} is the name of a domain you own. When operating on domains
that don’t exist or you don’t own, the API responds with a 404 Not Found
status code. For a quick overview of the available endpoints, methods, and
operations, see Endpoint Reference.
All operations are subject to rate limiting. For details, see Rate Limits.
RRset Field Reference
A JSON object representing an RRset has the following structure:
{
"created": "2019-09-18T16:32:16.510368Z",
"domain": "example.com",
"subname": "www",
"name": "www.example.com.",
"type": "A",
"records": [
"127.0.0.1",
"127.0.0.2"
],
"ttl": 3600,
"touched": "2020-04-06T09:24:09.987436Z"
}
Field details:
created- Access mode:
read-only
Timestamp of RRset creation, in ISO 8601 format (e.g.
2019-09-18T16:32:16.510368Z).domain- Access mode:
read-only
Name of the zone to which the RRset belongs.
Note that the zone name does not follow immediately from the RRset name. For example, the
comzone contains an RRset of typeNSfor the nameexample.com., in order to set up the delegation toexample.com’s DNS operator. The DNS operator’s nameserver again has a similarNSRRset which, this time however, belongs to theexample.comzone.name- Access mode:
read-only
The full DNS name of the RRset. If
subnameis empty, this is equal to{name}., otherwise it is equal to{subname}.{name}..records- Access mode:
read, write
Array of record content strings. Please note that when a record value contains a domain name, it is in almost all cases required to add a final dot after the domain name. This applies, for example, to the
CNAME,MX, andSRVrecord types. A typicalMXvalue would thus be be10 mx.example.com.(note the trailing dot).Please also consider the caveat on the priority field.
The maximum number of array elements is 4091, and the maximum length of the array is 64,000 (after JSON encoding).
Records must be given in presentation format (a.k.a. “BIND” or zone file format). Record values that are not given in canonical form, such as
0:0000::1for an IPv6 address, will be converted by the API into canonical form (here:::1). Exact validation and canonicalization depend on the record type.subname- Access mode:
read, write-once (upon RRset creation)
Subdomain string which, together with
domain, defines the RRset name. Typical examples arewwwor_443._tcp. In general, a subname consists of lowercase alphanumeric characters as well as hyphens-, underscores_, and dots.. Wildcard name components are denoted by*; this is allowed only once at the beginning of the name (see RFC 4592 for details). The maximum length is 178. Further restrictions may apply on a per-user basis.Note that for subnames to be created, they must be explicitly stated. In particular, the
wwwname is not automatically created when assigning an IP address to your domain name (by creating anAorAAAArecord). The same applies for the catch-all mechanism: If you would like a record to apply to all otherwise undefined subdomains, the wildcard subdomain*must be explicitly given.ttl- Access mode:
read, write
TTL (time-to-live) value, which dictates for how long resolvers may cache this RRset, measured in seconds. The smallest acceptable value is given by the domain’s minimum TTL setting. The maximum value is 86400 (one day).
type- Access mode:
read, write-once (upon RRset creation)
RRset type (uppercase). A broad range of record types is supported, with most DNSSEC-related types (and the
SOAtype) managed automagically by the backend. For details, check the section on Record Types.touched- Access mode:
read-only
Timestamp of when the RRset was last touched (same format as
created). This field reflects the most recent write request to the RRset. It is also updated when the write request does not actually change anything (e.g. overwriting a DNS record with identical values).
Creating an RRset
To create a new RRset, simply issue a POST request to the
/api/v1/domains/{name}/rrsets/ endpoint, like this:
curl https://desec.io/api/v1/domains/{name}/rrsets/ \
--header "Authorization: Token {secret}" \
--header "Content-Type: application/json" --data @- <<< \
'{"subname": "www", "type": "A", "ttl": 3600, "records": ["127.0.0.1", "127.0.0.2"]}'
type, records, and ttl are mandatory, whereas the subname
field is optional.
Upon success, the response status code will be 201 Created, with the RRset
contained in the response body. If the operation cannot be performed with the
given parameters, the API returns 400 Bad Request. This can happen, for
instance, when there is a conflicting RRset with the same name and type, when
not all required fields were provided correctly (such as, when the type
value was not provided in uppercase), or when the record content is
semantically invalid (e.g. when you provide an unknown record type, or an A
value that is not an IPv4 address).
Note that the values of type and subname as well as the records
items are strings, and as such the JSON specification requires them to be
enclosed in double quotes (with the quotes being part of the field value);
your shell or programming language may require another layer of quotes! By
contrast, ttl is an integer field, so the JSON value does not contain
quotes.
RRset write operations are subject to rate limiting (see Rate Limits). When creating (or updating) a large number of RRsets, we strongly encourage you to use Bulk Operations instead of multiple sequential requests. (Single requests are much more expensive on the server side, as each request triggers a DNSSEC signing operation. With bulk requests, only one signing operation is performed, covering all changes together.)
Creating a TLSA RRset
A common use case is the creation of a TLSA RRset which carries information
about the TLS certificate used by the server that the domain points to. For
example, to create a TLSA RRset for www.example.com, you can run:
curl https://desec.io/api/v1/domains/{name}/rrsets/ \
--header "Authorization: Token {secret}" \
--header "Content-Type: application/json" --data @- <<EOF
{
"subname": "_443._tcp.www",
"type": "TLSA",
"ttl": 3600,
"records": ["3 1 1 11501875615d4.....dd122bbf9190"]
}
EOF
Note: The subname is prefixed with _{port}._{transport_protocol}.
For a HTTPS server, this will usually be _443._tcp (for an otherwise empty
subname), or _443._tcp.www for the common www domain prefix. For
other use cases, the values have to be adapted accordingly (e.g. _993._tcp
for an IMAPS server).
To generate TLSA value from your certificate, you can use the following
command:
openssl x509 -in /path/to/fullchain.pem -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl sha256
Pro tip: To put only the hash into an environment variable, use:
TLSA_HASH=$(openssl x509 -in /path/to/fullchain.pem -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl sha256 2>&1 \
| cut -f2 -d ' ')
Alternatively, you can use a tool like https://www.huque.com/bin/gen_tlsa.
For full detail on how TLSA records work, please refer to RFC 6698.
Bulk Creation of RRsets
It is often desirable to create several RRsets at once. This is achieved by
sending an array of RRset objects to the rrsets/ endpoint (instead of just
one), like this:
curl https://desec.io/api/v1/domains/{name}/rrsets/ \
--header "Authorization: Token {secret}" \
--header "Content-Type: application/json" --data @- <<EOF
[
{"subname": "www", "type": "A", "ttl": 3600, "records": ["1.2.3.4"]},
{"subname": "www", "type": "AAAA", "ttl": 3600, "records": ["c0::fefe"]},
...
]
EOF
This is especially useful for bootstrapping a new domain.
For details about input validation and return status codes, please refer to Bulk Operations.
Retrieving all RRsets in a Zone
The /api/v1/domains/{name}/rrsets/ endpoint responds to GET requests
with an array of RRset objects. For example, you may issue the following
command:
curl https://desec.io/api/v1/domains/{name}/rrsets/ \
--header "Authorization: Token {secret}"
to retrieve the contents of a zone that you own. RRsets are returned in reverse chronological order of their creation.
The response status code in case of success is 200 OK. This is true also
if there are no RRsets in the zone; in this case, the response body will be an
empty JSON array.
Pagination
Up to 500 items are returned at a time. If more than 500 items would match the
query, the use of the cursor query parameter is required. The first page
can be retrieved by sending an empty pagination parameter, cursor=.
Once in pagination mode, the URLs to retrieve the next (or previous) page are
given in the Link: response header. For example:
Link: <https://desec.io/api/v1/domains/{domain}/rrsets/?cursor=>; rel="first",
<https://desec.io/api/v1/domains/{domain}/rrsets/?cursor=:prev_cursor>; rel="prev",
<https://desec.io/api/v1/domains/{domain}/rrsets/?cursor=:next_cursor>; rel="next"
where :prev_cursor and :next_cursor are page identifiers that are to
be treated as opaque by clients. On the first/last page, the Link: header
will not contain a prev/next link, respectively.
If no pagination parameter is given although pagination is required, the server
will return 400 Bad Request, along with a Link: header containing the
first link, and human-readable instructions on pagination in the body.
Filtering by Record Type
To retrieve an array of all RRsets from your zone that have a specific type
(e.g. all A records, regardless of subname), augment the previous
GET request with a type query parameter carrying the desired RRset type
like:
curl https://desec.io/api/v1/domains/{name}/rrsets/?type={type} \
--header "Authorization: Token {secret}"
Query parameters used for filtering are fully compatible with pagination.
Filtering by Subname
To filter the RRsets array by subname (e.g. to retrieve all records in the
www subdomain, regardless of their type), use the subname query
parameter, like this:
curl https://desec.io/api/v1/domains/{name}/rrsets/?subname={subname} \
--header "Authorization: Token {secret}"
This approach also allows to retrieve all records associated with the zone
apex (i.e. example.com where subname is empty), by querying
rrsets/?subname=.
Query parameters used for filtering are fully compatible with pagination.
Retrieving a Specific RRset
To retrieve an RRset with a specific name and type from your zone (e.g. the
A record for the www subdomain), issue a GET request with the
subname information and the type appended to the rrsets/ endpoint,
like this:
curl https://desec.io/api/v1/domains/{name}/rrsets/{subname}/{type}/ \
--header "Authorization: Token {secret}"
This will return only one RRset (i.e., the response is not a JSON array). The
response status code is 200 OK if the requested RRset exists, and 404
Not Found otherwise.
Accessing the Zone Apex
Note: The RRset at the zone apex (the domain root with an empty subname)
cannot be queried via /api/v1/domains/{name}/rrsets//{type}/. This is due
to normalization rules of the HTTP specification which cause the double-slash
// to be replaced with a single slash /, breaking the URL structure.
To access an RRset at the root of your domain, we reserved the special subname
value @. This is a common placeholder for this use case (see RFC 1035).
As an example, you can retrieve the IPv4 address(es) of your domain root by
running:
curl https://desec.io/api/v1/domains/{name}/rrsets/@/A/ \
--header "Authorization: Token {secret}"
Pro tip: If you like to have the convenience of simple string expansion
in the URL, you can add three dots after {subname}, like so:
curl https://desec.io/api/v1/domains/{name}/rrsets/{subname}.../{type}/ \
--header "Authorization: Token {secret}"
With this syntax, the above-mentioned normalization problem does not occur, and no special treatment is needed for accessing the zone apex. You can think of the three dots as abbreviating the rest of the DNS name.
Modifying an RRset
To modify an RRset, use the endpoint that you would also use to retrieve that
specific RRset. The API allows changing the values of records and
ttl. When using the PATCH method, only fields you would like to modify
need to be provided. In contrast, if you use PUT, the full resource must
be specified (that is, all fields, including subname and type).
Examples:
curl -X PUT https://desec.io/api/v1/domains/{name}/rrsets/{subname}/{type}/ \
--header "Authorization: Token {secret}" \
--header "Content-Type: application/json" --data @- <<EOF
{
"subname": "{subname}",
"type": "{type}",
"ttl": 3600,
"records": ["..."]
}
EOF
curl -X PATCH https://desec.io/api/v1/domains/{name}/rrsets/{subname}/{type}/ \
--header "Authorization: Token {secret}" \
--header "Content-Type: application/json" --data @- <<< \
'{"ttl": 86400}'
If the RRset was updated successfully, the API returns 200 OK with the
updated RRset in the response body. An exception to this rule is when an
empty array is provided as the records field, in which case the RRset is
deleted and the return code is 204 No Content (cf. Deleting an RRset).
In case the operation cannot be performed with
the given parameters, the API returns 400 Bad Request. This can happen, for
instance, when there is a conflicting RRset with the same name and type, when
not all required fields were provided correctly (such as, when the type
value was not provided in uppercase), or when the record content is
semantically invalid (e.g. when you provide an unknown record type, or an A
value that is not an IPv4 address).
To modify an RRset at the zone apex (empty subname), use the special subname
value @ in the endpoint URL (read more about Accessing the Zone Apex).
Bulk Modification of RRsets
It is sometimes desirable to modify several RRsets at once. This is achieved
by sending an array of RRset objects to the rrsets/ endpoint (instead of
just one), like this:
curl -X PUT https://desec.io/api/v1/domains/{name}/rrsets/ \
--header "Authorization: Token {secret}" \
--header "Content-Type: application/json" --data @- <<EOF
[
{"subname": "www", "type": "A", "ttl": 3600, "records": ["1.2.3.4"]},
{"subname": "www", "type": "AAAA", "ttl": 3600, "records": ["c0::fefe"]},
...
]
EOF
Each given RRset is uniquely identified by its subname and type (with
subname defaulting to the empty string if omitted). For ttl and
records, the usual validation rules apply.
For details about input validation and return status codes, please refer to Bulk Operations.
Deleting an RRset
To delete an RRset, you can send a DELETE request to the endpoint
representing the RRset. Alternatively, you can modify it and provide an empty
array for the records field ([]).
Upon success or if the RRset did not exist in the first place, the response
status code is 204 No Content.
Bulk Deletion of RRsets
It is sometimes desirable to delete an RRset while creating or modifying
another one. This is achieved by sending a bulk request with an RRset that
has an empty records list [], using the PATCH or PUT method:
curl -X PATCH https://desec.io/api/v1/domains/{name}/rrsets/ \
--header "Authorization: Token {secret}" \
--header "Content-Type: application/json" --data @- <<EOF
[
{"subname": "www", "type": "A", "ttl": 3600, "records": ["1.2.3.4"]},
{"subname": "www", "type": "AAAA", "records": []}
]
EOF
For details about input validation and return status codes, please refer to Bulk Operations.
Bulk Operations
To download your domain’s DNS records in zonefile format, please see Exporting a Domain as Zonefile.
The rrsets/ endpoint supports bulk write operations via the POST,
PATCH, and PUT request methods. You can simply send an array of RRset
objects (instead of just one), like this:
curl -X PATCH https://desec.io/api/v1/domains/{name}/rrsets/ \
--header "Authorization: Token {secret}" \
--header "Content-Type: application/json" --data @- <<EOF
[
{"subname": "www", "type": "A", "ttl": 3600, "records": ["1.2.3.4"]},
{"subname": "www", "type": "AAAA", "ttl": 3600, "records": ["c0::fefe"]},
{"subname": "backup", "type": "MX", "records": []},
...
]
EOF
Note that the zone apex is referred to by an empty subname string,
"subname": "". (The special character @ is not accepted as an alias.)
For context, see Accessing the Zone Apex.
Atomicity
Bulk operations are performed atomically, i.e. either all given RRsets are accepted and published in (or deleted from) the DNS, or none of them are.
This allows you to smoothly apply large DNS changes to your domain without running into the undesirable situation of an error showing up half-way through the process when some changes already have been applied.
Field requirements
For the POST and PUT methods, all fields are required for each given
RRset. With POST, only new RRsets are acceptable (i.e. the domain must
not yet have an RRset with the same subname and type), while PUT allows
both creating new RRsets and modifying existing ones.
For the PATCH method, only subname and type is required; if you
want to modify only ttl or records, you can skip the other field. To
create a new RRset using PATCH, all fields but subname must be
specified.
To delete an RRset during a bulk operation, use PATCH or PUT and set
records to [].
Input validation
The API performs various types of validation checks:
Sanity checks, such as syntax, basic semantics (e.g. negative TTL).
RRset uniqueness (with respect to subname and type) and
CNAMEexclusivity. We both check with respect to pre-existing RRsets as well as with respect to other RRsets sent in the same request.DNS record checks, such as whether the given type is a supported record type, and whether the given record contents are consistent with the type.
Error responses have status 400 Bad Request and contain a list of errors
in the response body, with each list item corresponding to one part of the
bulk request, in the same order. Parts that passed without errors have an
empty error object {}, and parts with errors contain a data structure
explaining the error(s) in a human-readable fashion.
In case of several errors for the same RRset, we sometimes only return one of them. For example, if you’re creating an RRset that conflicts with an existing RRset, the API does not perform further validation of the record contents, and instead only points out the uniqueness conflict.
Record Types
Supported Types
Generally, the API supports almost all RRset types supported by PowerDNS, with a few exceptions for such record types that the backend manages automatically.
At least the following record types are supported: A, AAAA, AFSDB,
APL, CAA, CDNSKEY, CDS, CERT, CNAME, DHCID,
DNAME, DNSKEY, DLV, DS, EUI48, EUI64, HINFO,
HTTPS, KX, L32, L64, LOC, LP, MX,
NAPTR, NID, NS, OPENPGPKEY, PTR, RP, SMIMEA,
SPF, SRV, SSHFP, SVCB, TLSA, TXT, URI.
(The SOA record is managed automatically.)
Special care needs to be taken with some types of records, as explained below.
Automatically Managed Types
DNSKEY,DS,CDNSKEY,CDS,NSEC3PARAM,RRSIGThese record types are meant to provide DNSSEC-related information in order to secure the data stored in your zones. RRsets of this type are generated and served automatically by our nameservers. It is currently not possible to read or manipulate any automatically generated values using the API.
Note, however, that it is possible to add additional values for some key-related records types (
DNSKEY,DS,CDNSKEY) in order to publish extra public keys. For details, see DNSKEY caveat.When attempting an unsupported operation,
403 Forbiddenor400 Bad Requestis returned.
SOArecordThe
SOArecord cannot be read or written through this interface. When attempting to create, modify or otherwise access anSOArecord,400 Bad Requestor403 Forbiddenis returned, respectively.The rationale behind this is that the content of the
SOArecord is entirely determined by the DNS operator, and users should not have to bother with this kind of metadata. Upon zone changes, the backend automatically takes care of updating theSOArecord accordingly.If you are interested in the value of the
SOArecord, you can retrieve it using a standard DNS query.
Unsupported Types
ALIAS/ANAMEDue to conflicts with the security guarantees we would like to give, we do not support these record types (detailed explanation). Attempts to create such records will result in a
400 Bad Requestresponse.If you need redirect functionality at the zone apex, consider using the
HTTPSrecord type which serves exactly this purpose. (Note that as of 06/2021, this record type is not yet supported in all browsers.)
Caveats
- Record types with priority field
The deSEC DNS API does not explicitly support structured records fields (such as the priority field used for
MX,SRVand the like).Instead, those fields are expected to be concatenated in the conventional order used for zone files, with spaces in between them. For
MXRRsets, that means that the priority is located at the beginning of the record content, separated from the rest of it by a space (e.g.10 mx.example.com.).
CDNSKEY,CDS,DNSKEYrecordThese records are managed automatically by deSEC. However, our API allows adding additional values for specialized purposes. Regular, automatic DNSSEC operation does not require deSEC users to touch these records.
Using these record types inappropriately may break proper functioning of your domain. If you know what you’re doing, you can use these record types for announcing extra DNSSEC public keys, for example in order to orchestrate keys when your zone is signed by several DNSSEC operators independently (“multi-signer setup”, see also RFC 8901).
Note: Manually provided records are published in addition to the ones managed automatically by deSEC. As a consequence, the TTL values of extra records configured at the zone apex are ignored by the API, and manually provided records are published with the same TTL as automatic ones.
CNAMErecordThe record value (target) must be terminated by a dot
.(as inexample.com.). Only one value is allowed.A
CNAMErecord is not allowed when other records exist at the same subname. This is a limitation of the DNS specification.Due to the previous limitation, a CNAME is not allowed at the zone apex (empty subname), as it would always collide with the NS record (and the internally managed SOA record).
If you need redirect functionality at the zone apex, consider using the
HTTPSrecord type which serves exactly this purpose. (Note that as of 06/2021, this record type is not yet supported in all browsers.)
DNSKEYrecordSee notes on the
CDNSKEY,CDS, andDNSKEYrecord types.MXrecordThe
MXrecord value consists of the priority value and a mail server name, which must be terminated by a dot.. Example:10 mail.a4a.de.NSrecordThe record value must be terminated by a dot
.(as inns1.desec.io.).The use of wildcard RRsets (with one component of
subnamebeing equal to*) of typeNSis discouraged. This is because the behavior of wildcardNSrecords in conjunction with DNSSEC is undefined, per RFC 4592, Sec. 4.2.
TXTrecordThe contents of the
TXTrecord must be enclosed in double quotes. Thus, whenPOSTing to the API, make sure to do proper escaping etc. as required by the client you are using. Here’s an example of how to create aTXTRRset:curl https://desec.io/api/v1/domains/{name}/rrsets/ \ --header "Authorization: Token {secret}" \ --header "Content-Type: application/json" --data @- <<< \ '{"type": "TXT", "records": ["\"test value1\"","\"value2\""], "ttl": 3600}'
Record values consisting of several token strings separated by whitespace are permitted, for example:
["\"token 1 of record 1\" \"token 2 of record 1\"", "\"record 2\""]Token strings longer than 255 characters are automatically split into several token strings. (This maximum length is given by the DNS specification.)
Binary record contents are supported, but subject to various escaping rules (both JSON and
TXTrecord syntax; in addition, certain non-printable characters are not accepted even when unicode-escaped, like\u0000). Still, you can store any binary data by using DNS-style\DDDencoding for your binary data (see RFC 1035 Sec. 3.3.14 and 5.1). For example, a carriage return (\r) can be stored as\013. (Note that JSON encoding needs to be applied on top of that, so a validrecordsfield would be["\"\\013\""].)