Introduction

The deSEC DNS API is a REST interface that allows easy management of DNS information. The interface design aims for simplicity so that tasks such as creating domains and manipulating DNS records can be handled with ease and in an intuitive fashion.

Server-side operations, such as creation of domains or DNS records, expect JSON-formatted user input in the body of the POST, PATCH, or PUT request (see below). The request is required to come with a Content-Type: application/json header field.

API functionality is demonstrated using the command line tool curl. To pretty-print JSON output, process the data through jq: curl ... | jq ..

User Registration and Management

Getting Started

Access to the domain management API is granted to registered and logged in users only. User accounts can register free of charge through the API, providing an email address and a password. To register an user account, issue a POST request like this:

curl -X POST https://desec.io/api/v1/auth/users/ \
    --header "Content-Type: application/json" --data @- <<< \
    '{"email": "anemailaddress@example.com", "password": "yourpassword"}'

Your email address is required for account recovery, in case you forgot your password, for contacting support, etc. It is deSEC’s policy to require users to provide a valid email address so that support requests can be verified. If you provide an invalid email address, we will not be able to help you if you need support.

Note that while we do not enforce restrictions on your password, please do not choose a weak one.

Once a user account has been registered, you will be able to log in. Log in is done by asking the API for a token that can be used to authorize subsequent DNS management requests. To obtain such a token, send your email address and password to the /auth/token/login/ endpoint:

curl -X POST https://desec.io/api/v1/auth/token/login/ \
    --header "Content-Type: application/json" --data @- <<< \
    '{"email": "anemailaddress@example.com", "password": "yourpassword"}'

The API will reply with a token like:

{
    "auth_token": "i+T3b1h/OI+H9ab8tRS98stGtURe"
}

Most interactions with the API require authentication of the domain owner using this token. To authenticate, the token is transmitted via the HTTP Authorization header, as shown in the examples in this document.

Additionally, the API provides you with the /auth/tokens/ endpoint which you can use to create and destroy additional tokens (see below). Such token can be used to authenticate devices independently of your current login session, such as routers. They can be revoked individually.

Registration

The API provides an endpoint to register new user accounts. New accounts require an email address and a password.

Your email address is required for account recovery, in case you forgot your password, for contacting support, etc. It is deSEC’s policy to require users to provide a valid email address so that support requests can be verified. If you provide an invalid email address, we will not be able to help you if you need support.

Note that while we do not enforce restrictions on your password, please do not choose a weak one.

Upon successful registration, the server will reply with 201 Created and send you a welcome email. If there is a problem with your email or password, the server will reply with 400 Bad Request and give a human-readable error message that may look like:

HTTP/1.1 400 Bad Request

{
    "password": [
        "This field may not be blank."
    ]
}

Your password information will be stored on our servers using Django’s default method, PBKDF2.

Preventing Abuse

We enforce some limits on user creation requests to make abuse harder. In cases where our heuristic suspects abuse, the server will still reply with 201 Created but will send you an (additional) email asking to solve a Google ReCaptcha. We implemented this as privacy-friendly as possible, but recommend solving the captcha using some additional privacy measures such as an anonymous browser-tab, VPN, etc. Before solving the captcha, the account will be locked, that is, it will be possible to log in; however, most operations on the API will be limited to read-only.

Log In

All interactions with the API that require authentication must be authenticated using a token that identifies the user and authorizes the request. The process of obtaining such a token is what we call log in.

To obtain an authentication token, log in by sending your email address and password to the token create endpoint of the API:

curl -X POST https://desec.io/api/v1/auth/token/login/ \
    --header "Content-Type: application/json" --data @- <<< \
    '{"email": "anemailaddress@example.com", "password": "yourpassword"}'

If email address and password match our records, the server will reply with 201 Created and send you the token as part of the response body:

{
    "auth_token": "i+T3b1h/OI+H9ab8tRS98stGtURe"
}

Note that every time you POST to this endpoint, a new Token will be created, while old tokens remain valid.

To authorize subsequent requests with the new token, set the HTTP Authorization header to the token value, prefixed with Token:

curl -X GET https://desec.io/api/v1/ \
    --header "Authorization: Token i+T3b1h/OI+H9ab8tRS98stGtURe"

Log Out

To invalidate an authentication token (log out), send a POST request to the token destroy endpoint, using the token in question in the Authorization header:

curl -X POST https://desec.io/api/v1/auth/token/logout/ \
    --header "Authorization: Token i+T3b1h/OI+H9ab8tRS98stGtURe"

The server will delete the token and respond with 204 No Content.

Manage Account

Field Reference

A JSON object representing a user has the following structure:

{
    "dyn": false,
    "email": "address@example.com",
    "limit_domains": 5,
    "locked": false
}

Field details:

dyn
Access mode:read-only (deprecated)

Indicates whether the account is restricted to dynDNS domains under dedyn.io.

email
Access mode:read, write

Email address associated with the account. This address must be valid in order to submit support requests to deSEC.

limit_domains
Access mode:read-only

Maximum number of DNS zones the user can create.

locked
Access mode:read-only

Indicates whether the account is locked. If so, domains put in read-only mode. Changes are not propagated in the DNS system.

Retrieve Account Information

To request information about your account, send a GET request to the auth/me/ endpoint:

curl -X GET https://desec.io/api/v1/auth/me/ \
    --header "Authorization: Token i+T3b1h/OI+H9ab8tRS98stGtURe"

Change Email Address

You can change your account email address by sending a PUT request to the auth/me/ endpoint:

curl -X PUT https://desec.io/api/v1/auth/me/ \
    --header "Authorization: Token i+T3b1h/OI+H9ab8tRS98stGtURe" \
    --header "Content-Type: application/json" --data @- <<< \
    '{"email": "new-email@example.com"}'

Please note that our email support only acts upon requests that originate from the email address associated with the deSEC user in question. It is therefore required that you provide a valid email address. However, we do not automatically verify the validity of the address provided.

If you provide an invalid email address and forget your account password and tokens, we will not be able to help you, and access will be lost permanently.

Manage Tokens

To make authentication more flexible, the API can provide you with multiple authentication tokens. To that end, we provide a set of token management endpoints that are separate from the above-mentioned log in and log out endpoints. The most notable difference is that the log in endpoint needs authentication with email address and password, whereas the token management endpoint is authenticated using already issued tokens.

Retrieving All Current Tokens

To retrieve a list of currently valid tokens, issue a GET request:

curl -X GET https://desec.io/api/v1/auth/tokens/ \
    --header "Authorization: Token mu4W4MHuSc0HyrGD1h/dnKuZBond"

The server will respond with a list of token objects, each containing a timestamp when the token was created (note the Z indicating the UTC timezone), an ID to identify that token, as well as the secret token value itself. Each token can carry a name that has no operational relevance to the API and is for user reference only. All tokens created by the log in endpoint will have “login” as name.

[
    {
        "created": "2018-09-06T07:05:54.080564Z",
        "id": 14423,
        "value": "4yScSMFFNdAlk6WZuLIwYBVYnXPF",
        "name": "login"
    },
    {
        "created": "2018-09-06T08:53:26.428396Z",
        "id": 36483,
        "value": "mu4W4MHuSc0HyrGD1h/dnKuZBond",
        "name": ""
    }
]

Create Additional Tokens

To create another token using the token management interface, issue a POST request to the same endpoint:

curl -X POST https://desec.io/api/v1/auth/tokens/ \
    --header "Authorization: Token mu4W4MHuSc0HyrGD1h/dnKuZBond" \
    --header "Content-Type: application/json" --data @- <<< \
    '{"name": "my new token"}'

Note that the name is optional and will be empty if not specified. The server will reply with 201 Created and the created token in the response body:

{
    "created": "2018-09-06T09:08:43.762697Z",
    "id": 73658,
    "value": "4pnk7u+NHvrEkFzrhFDRTjGFyX+S",
    "name": "my new token"
}

Delete Tokens

To delete an existing token via the token management endpoints, issue a DELETE request on the token’s endpoint, replacing :id with the token id value:

curl -X DELETE https://desec.io/api/v1/auth/tokens/:id/ \
    --header "Authorization: Token mu4W4MHuSc0HyrGD1h/dnKuZBond"

The server will reply with 204 No Content, even if the token was not found.

Note that, for now, all tokens have equal power – every token can authorize any action. We may implement specialized tokens in the future.

Token Security Considerations

This section is for information only. Token length and encoding may be subject to change in the future.

Any token is generated from 168 bits of true randomness at the server. Guessing the token correctly is hence practically impossible. The value corresponds to 21 bytes and is represented by 28 characters in Base64-like encoding. That is, any token will only consist of URL-safe characters A-Z, a-z, -, and .. (We do not have any padding at the end because the string length is a multiple of 4.)

As all tokens are stored in plain text on the server, the user may not choose the token value individually to prevent re-using passwords as tokens at deSEC.

Old versions of deSEC encoded 20-byte tokens in 40 characters with hexadecimal representation. Such tokens will not be issued anymore, but remain valid until invalidated by the user.

Domain Management

Domain management is done through the /api/v1/domains/ endpoint. The following sections describe how to create, list, modify, and delete domains using JSON objects. The structure of the JSON objects is detailed in the next section.

Domain Field Reference

A JSON object representing a domain has the following structure:

{
    "created": "2018-09-18T16:36:16.510368Z",
    "keys": [
        {
            "dnskey": "257 3 13 WFRl60...",
            "ds": [
                "6006 13 1 8581e9...",
                "6006 13 2 f34b75...",
                "6006 13 3 dfb325...",
                "6006 13 4 2fdcf8..."
            ],
            "flags": 257,
            "keytype": "csk"
        },
        ...
    ],
    "minimum_ttl": 3600,
    "name": "example.com",
    "published": "2018-09-18T17:21:38.348112Z"
}

Field details:

created
Access mode:read-only

Timestamp of domain creation, in ISO 8601 format (e.g. 2013-01-29T12:34:56.000000Z).

keys
Access mode:read-only

Array with DNSSEC key information. Each entry contains DNSKEY and DS record contents (the latter being computed from the former), and some extra information. For delegation of DNSSEC-secured domains, the parent domain needs to publish these DS records. (This usually involves telling your registrar/registry about those records, and they will publish them for you.)

Notes:

  • Newly created domains are assigned a key after a short while (usually around one minute). Until then, this field is empty.
  • The contents of this field are generated from PowerDNS’ cryptokeys endpoint, see https://doc.powerdns.com/md/httpapi/api_spec/#cryptokeys. We look at each active cryptokey_resource (active is true) and then use the dnskey, ds, flags, and keytype fields.
minimum_ttl
Access mode:read-only

Smallest TTL that can be used in an RRset. The value is set automatically by the server.

If you would like to use lower TTL values, you can apply for an exception by contacting support. We reserve the right to reject applications at our discretion.

name
Access mode:read, write-once (upon domain creation)

Domain name. Restrictions on what is a valid domain name apply on a per-user basis. In general, a domain name consists of lowercase alphanumeric characters as well as hyphens - and underscores _ (except at the beginning of the name). The maximum length is 191.

published
Access mode:read-only

Timestamp of when the domain’s DNS records have last been published, in ISO 8601 format (e.g. 2013-01-29T12:34:56.000000Z).

As we publish record modifications immediately, this indicates the point in time of the last successful write request to a domain’s rrsets/ endpoint.

Creating a Domain

To create a new domain, issue a POST request to the /api/v1/domains/ endpoint, like this:

curl -X POST https://desec.io/api/v1/domains/ \
    --header "Authorization: Token {token}" \
    --header "Content-Type: application/json" --data @- <<< \
    '{"name": "example.com"}'

Only the name field is mandatory.

Upon success, the response status code will be 201 Created, with the domain object contained in the response body. 400 Bad Request is returned if the request contained malformed data. If the object could not be created although the request was wellformed, the API responds with 403 Forbidden if the maximum number of domains for this user has been reached, and with 409 Conflict otherwise. This can happen, for example, if there already is a domain with the same name or if the domain name is considered invalid for policy reasons.

The response body may provide further, human-readable information on the policy violation that occurred.

Restrictions on what is a valid domain name apply. In particular, domains listed on the Public Suffix List cannot be registered. (If you operate a public suffix and would like to host it with deSEC, that’s certainly possible; please contact our support.)

Furthermore, restrictions on a per-user basis may apply. In particular, the number of domains a user can create is limited. If you find yourself affected by this limit although you have a legitimate use case, please contact our support.

Listing Domains

The /api/v1/domains/ endpoint reponds to GET requests with an array of domain objects. For example, you may issue the following command:

curl -X GET https://desec.io/api/v1/domains/ \
    --header "Authorization: Token {token}"

to retrieve an overview of the domains you own.

The response status code is 200 OK. This is true also if you do not own any domains; in this case, the response body will be an empty JSON array.

Retrieving a Specific Domain

To retrieve a domain with a specific name, issue a GET request with the name appended to the domains/ endpoint, like this:

curl -X GET https://desec.io/api/v1/domains/:name/ \
    --header "Authorization: Token {token}"

This will return only one domain (i.e., the response is not a JSON array).

If you own a domain with that name, the API responds with 200 OK and returns the domain object in the reponse body. Otherwise, the return status code is 404 Not Found.

Deleting a Domain

To delete a domain, send a DELETE request to the endpoint representing the domain. Upon success or if the domain did not exist or was not yours in the first place, the response status code is 204 No Content.

Retrieving and Manipulating DNS Information

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 associatd with this name. In the traditional Bind zone file format, the RRset would be written as:

<name>  IN  A 127.0.0.1
<name>  IN  A 127.0.0.2
...

Each of these lines is a Resource Record, and together they form an RRset.

The basic units accessible through the API are RRsets, 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.

RRset Field Reference

A JSON object representing an RRset has the following structure:

{
    "domain": "example.com",
    "subname": "www",
    "name": "www.example.com.",
    "type": "A",
    "records": [
        "127.0.0.1",
        "127.0.0.2"
    ],
    "ttl": 3600
}

Field details:

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 com zone contains an RRset of type NS for the name example.com., in order to set up the delegation to example.com’s DNS operator. The DNS operator’s nameserver again has a similar NS RRset which, this time however, belongs to the example.com zone.

name
Access mode:read-only

The full DNS name of the RRset. If subname is 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, and SRV record types. A typical MX value would thus be be 10 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).

subname
Access mode:read, write-once (upon RRset creation)

Subdomain string which, together with domain, defines the RRset name. Typical examples are www or _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.

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 604800 (one week).

type
Access mode:read, write-once (upon RRset creation)

RRset type (uppercase). We support all RRset types supported by PowerDNS, with the exception of DNSSEC-related types (the backend automagically takes care of setting those records properly). You also cannot access the SOA, see SOA caveat.

Creating an RRset

To create a new RRset, simply issue a POST request to the /api/v1/domains/:name/rrsets/ endpoint, like this:

curl -X POST https://desec.io/api/v1/domains/:name/rrsets/ \
    --header "Authorization: Token {token}" \
    --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 another RRset with the same name and type exists already, the API responds with 409 Conflict. If there is a syntactical error (e.g. not all required fields were provided or the type was not specified in uppercase), the API responds with 400 Bad Request. If field values were semantically invalid (e.g. when you provide an unknown record type, or an A value that is not an IPv4 address), 422 Unprocessable Entity is returned.

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.

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 -X POST https://desec.io/api/v1/domains/:name/rrsets/ \
    --header "Authorization: Token {token}" \
    --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 the TLSA from your certificate, you can use a tool like https://www.huque.com/bin/gen_tlsa. We are planning to provide a tool that is connected directly to our API in the future. 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 -X POST https://desec.io/api/v1/domains/:name/rrsets/ \
    --header "Authorization: Token {token}" \
    --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 reponds to GET requests with an array of RRset objects. For example, you may issue the following command:

curl -X GET https://desec.io/api/v1/domains/:name/rrsets/ \
    --header "Authorization: Token {token}"

to retrieve the contents of a zone that you own.

The response status code 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.

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 {token}"

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 {token}"

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=.

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 {token}"

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 {token}"

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 {token}"

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 {token}" \
    --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 {token}" \
    --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 reponse body. If there is a syntactical error (e.g. not all required fields were provided or the type was not specified in uppercase), the API responds with 400 Bad Request. If field values were semantically invalid (e.g. when you provide an unknown record type, or an A value that is not an IPv4 address), 422 Unprocessable Entity is returned. If the RRset does not exist, 404 Not Found is returned.

To modify an RRset at the zone apex (empty subname), use the special subname value @ (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 {token}" \
    --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 {token}" \
    --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

The rrsets/ endpoint supports bulk 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 {token}" \
    --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

In all cases, the subname field is optional. If missing, the empty subname is assumed.

For the POST and PUT methods, all other 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 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

For efficiency and other reasons, there are three stages of input validation:

  1. Basic syntactical and semantical checks for missing JSON fields, negative TTL and such.
  2. Uniqueness validation. This is both to avoid the creation of multiple RRsets with the same subname and type, and to uncover bulk requests containing multiple parts that refer to the same subname and type.
  3. Lastly, we check whether the given type is a supported DNS record type, and whether the given record contents are consistent with the type.

Errors are collected at each stage; if at least one error occured, the request is aborted at the end of the stage, and the error(s) are returned. Only if no error occurred, will the request be allowed to proceed to the next stage.

In stages 1 and 2, errors are presented as a list of errors, with each list item referring to one part of the bulk request, in the same order. Parts that did not cause errors have an empty error object {}, and parts with errors contain more details describing the error. Unfortunately, in step 3, the API currently does not associate the error message with the RRset that caused it.

The successive treatment of stages 1 and 2 means that one bulk part with a stage-2 error may appear valid ({}) as long as another RRset has a stage-1 error. Only after the stage-1 error is resolved, the request will reach stage 2, at which point an error may occur for the bulk part that previously seemed valid.

Errors in stages 1 and 2 cause status code 400 (regardless of the exact reason(s) which may vary across bulk parts), and errors from stage 3 cause status code 422.

Notes

Consider the following general remarks that apply to our API as a whole:

  • All operations are performed on RRsets, not on the individual Resource Records.
  • The TTL (time-to-live: time for which resolvers may cache DNS information) is a property of an RRset (and not of a record). Thus, all records in an RRset share the record type and also the TTL. (This is actually a requirement of the DNS specification and not an API design choice.)
  • We have not done extensive testing for reverse DNS, but things should work in principle. If you encounter any problems, please let us know.

Generally, the API supports all RRset types supported by PowerDNS, with a few exceptions for such record types that the backend manages automatically. Thus, these restrictions are not limitations from a practical point of view. Furthermore, special care needs to be taken with some types of records, as explained below.

Restricted Types

ALIAS, DNAME
These record types are used very rarely in the wild. Due to conflicts with the security guarantees we would like to give, these record types are disabled in our API. If you attempt to create such RRsets, you will receive a 400 Bad Request response. In case you have a good reason for using these record types, shoot us an email and we can discuss your case.
DNSKEY, NSEC3PARAM, RRSIG
These 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. However, you can neither read nor manipulate these RRsets through the API. When attempting such operations, 403 Forbidden or 400 Bad Request is returned, respectively.
SOA record

The SOA record cannot be read or written through this interface. When attempting to create, modify or otherwise access an SOA record, 400 Bad Request or 403 Forbidden is returned, respectively.

The rationale behind this is that the content of the SOA record 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 the SOA record accordingly.

If you are interested in the value of the SOA record, you can retrieve it using a standard DNS query.

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, SRV and the like).

Instead, those fields are expected to be concatenated in the conventional order used for zone files, with spaces in between them. For MX RRsets, 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.).

CNAME record
  • The record value must be terminated by a dot . (as in example.com.).

  • If you create a CNAME record, its presence will cause other RRsets of the same name to be hidden (“occluded”) from the public (i.e. in responses to DNS queries). This is per RFC 1912.

    However, as far as the API is concerned, you can still retrieve and manipulate those additional RRsets. In other words, CNAME-induced hiding of additional RRsets does not apply when looking at the zone through the API.

  • It is currently possible to create a CNAME RRset with several records. However, this is not legal, and the response to queries for such RRsets is undefined. In short, don’t do it.

  • Similarly, you are discouraged from creating a CNAME RRset for the zone apex (main domain name, empty subname). Doing so will most likely break your domain (for example, any NS records that are present will disappear from DNS responses), and other undefined behavior may occur. In short, don’t do it. If you are interested in aliasing the zone apex, consider using an ALIAS RRset.

MX record
The MX record value consists of the priority value and a mail server name, which must be terminated by a dot .. Example: 10 mail.a4a.de.
NS record
  • The record value must be terminated by a dot . (as in ns1.desec.io.).
  • The use of wildcard RRsets (with one component of subname being equal to *) of type NS is discouraged. This is because the behavior of wildcard NS records in conjunction with DNSSEC is undefined, per RFC 4592, Sec. 4.2.
TXT record

The contents of the TXT record must be enclosed in double quotes. Thus, when POSTing 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 a TXT RRset with HTTPie:

curl -X POST https://desec.io/api/v1/domains/:name/rrsets/ \
    --header "Authorization: Token {token}" \
    --header "Content-Type: application/json" --data @- <<< \
    '{"type": "TXT", "records": ["\"test value1\"","\"value2\""], "ttl": 3600}'

Endpoint Reference

The following table summarizes basic information about the deSEC API endpoints used for User Registration and Management.

Endpoint /api/v1 Methods Use case
/auth/me/ GET Retrieve user account information
PUT Change account email address
/auth/users/ POST Create user account
/auth/token/login/ POST Log in and request authentication token
/auth/token/logout/ POST Log out and destroy authentication token
/auth/tokens/ GET Retrieve all current tokens
POST Create new token
/auth/tokens/:id/ DELETE Delete token

The following table summarizes basic information about the deSEC API endpoints used for Domain Management and Retrieving and Manipulating DNS Information.

Endpoint /api/v1/domains Methods Use case
/ GET Retrieve all domains you own
POST Create a domain
/:name/ GET Retrieve a specific domain
PATCH Modify a domain (deprecated)
DELETE Delete a domain
/:name/rrsets/ GET Retrieve all RRsets from domain, filter by subname or type query parameter
POST Create one or more RRsets
PATCH Create, modify or delete one or more RRsets
PUT Create, modify or delete one or more RRsets
/:name/rrsets/:subname/:type//:name/rrsets/:subname.../:type/ GET Retrieve a specific RRset
PATCH Modify an RRset
PUT Replace an RRset
DELETE Delete an RRset
/:name/rrsets/@/:type/   Access an RRset at the zone apex

API Versions and Lifecycle

To enable users to build reliable tools on top of the deSEC API, we maintain stable versions of the API for extended periods of time.

Each API version will advance through the API version lifecycle, starting from unstable and proceeding to stable, deprecated, and, finally, to historical.

Check out the current status of the API versions to make sure you are using the latest stable API whenever using our service in production.

Unstable API versions are currently under development and may change without prior notice, but we promise to keep an eye on users affected by incompatible changes.

For all stable API versions, we guarantee that

  1. it will be maintained at least until the end of the given support period,
  2. there will be no incompatible changes made to the interface, unless security vulnerabilities make such changes inevitable,
  3. users will be warned before the end of the support period.

Deprecated API versions are going to be disabled in the future. Users will be notified via email and are encouraged to migrate to the next stable version as soon as possible. For this purpose, a migration advisory will be provided. After the support period is over, deprecated API versions may be disabled without further warning and transition to historical state.

Historical API versions are permanently disabled and cannot be used.

Getting Help

If you need help beyond this documentation, please do not hesitate and shoot us an email at support@desec.io.

About this document

To add to our documentation or fix a mistake, please submit a Pull Request at https://github.com/desec-io/desec-stack.