curl --request POST \
--url https://dns.basaltic.sh/v1/zones/{zone_id}/import \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"zone_file": "$ORIGIN example.com.\n$TTL 3600\nwww IN A 203.0.113.10\n@ IN MX 10 mail.example.com.\ndkim IN TXT \"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\"\n"
}
'import requests
url = "https://dns.basaltic.sh/v1/zones/{zone_id}/import"
payload = { "zone_file": "$ORIGIN example.com.
$TTL 3600
www IN A 203.0.113.10
@ IN MX 10 mail.example.com.
dkim IN TXT \"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\"
" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
zone_file: '$ORIGIN example.com.\n$TTL 3600\nwww IN A 203.0.113.10\n@ IN MX 10 mail.example.com.\ndkim IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqh"\n'
})
};
fetch('https://dns.basaltic.sh/v1/zones/{zone_id}/import', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://dns.basaltic.sh/v1/zones/{zone_id}/import",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'zone_file' => '$ORIGIN example.com.
$TTL 3600
www IN A 203.0.113.10
@ IN MX 10 mail.example.com.
dkim IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqh"
'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://dns.basaltic.sh/v1/zones/{zone_id}/import"
payload := strings.NewReader("{\n \"zone_file\": \"$ORIGIN example.com.\\n$TTL 3600\\nwww IN A 203.0.113.10\\n@ IN MX 10 mail.example.com.\\ndkim IN TXT \\\"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\\\"\\n\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://dns.basaltic.sh/v1/zones/{zone_id}/import")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"zone_file\": \"$ORIGIN example.com.\\n$TTL 3600\\nwww IN A 203.0.113.10\\n@ IN MX 10 mail.example.com.\\ndkim IN TXT \\\"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\\\"\\n\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://dns.basaltic.sh/v1/zones/{zone_id}/import")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"zone_file\": \"$ORIGIN example.com.\\n$TTL 3600\\nwww IN A 203.0.113.10\\n@ IN MX 10 mail.example.com.\\ndkim IN TXT \\\"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\\\"\\n\"\n}"
response = http.request(request)
puts response.read_body{
"import": {
"records_created": 7,
"records_replaced": 2,
"records_by_type": {
"A": 4,
"MX": 1,
"TXT": 2
},
"skipped": [
{
"name": "example.com",
"type": "NS",
"reason": "the apex NS set is the delegation this platform's nameservers answer for"
}
],
"warnings": [
"<string>"
]
}
}{
"error": {
"code": "INVALID_INPUT",
"message": "Invalid request parameters",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"params": {
"instances": 2,
"pools": 0
}
}
}{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "ACCESS_DENIED",
"message": "You don't have permission to perform this action",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "NOT_FOUND",
"message": "Resource not found",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "INVALID_INPUT",
"message": "Invalid request parameters",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"params": {
"instances": 2,
"pools": 0
}
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal error occurred",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}Import a zone file
Load an RFC 1035 master file — a BIND zone file, the format every DNS provider exports — into an existing zone.
Merges, never replaces. An RRset in the file replaces the RRset at the same name and type wholesale; an RRset the file does not mention is left exactly as it is. So an import cannot delete a record, and importing the same file twice is a no-op rather than an error — which is what makes it safe to retry.
All or nothing. The whole file is validated before any record is
written: every record has to pass the same type, name, TTL and rdata
checks createRecord applies, the same CNAME-coexistence rule, and the
same per-zone and per-org record quota. One bad record refuses the file
and the zone is untouched.
What is skipped rather than imported. The SOA and the apex NS set are
the platform’s: the zone is served by our nameservers, and the apex NS is
what the parent delegates to. The DNSSEC records (RRSIG, DNSKEY,
DS, NSEC, NSEC3, NSEC3PARAM, CDS, CDNSKEY) are generated by
our signer from keys your file cannot know. Records the file places
outside this zone are skipped too. All of them come back in skipped
with a reason — a file exported from a signed zone carries every one, so
refusing the file over them would make export/import unusable.
The file may be sent as JSON (zone_file) or as the raw request body
under any other content type, so curl --data-binary @db.example.com
works directly. Maximum 1 MiB.
On success the zone’s SOA serial advances once for the whole import and the change propagates to the authoritative nameservers.
curl --request POST \
--url https://dns.basaltic.sh/v1/zones/{zone_id}/import \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"zone_file": "$ORIGIN example.com.\n$TTL 3600\nwww IN A 203.0.113.10\n@ IN MX 10 mail.example.com.\ndkim IN TXT \"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\"\n"
}
'import requests
url = "https://dns.basaltic.sh/v1/zones/{zone_id}/import"
payload = { "zone_file": "$ORIGIN example.com.
$TTL 3600
www IN A 203.0.113.10
@ IN MX 10 mail.example.com.
dkim IN TXT \"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\"
" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
zone_file: '$ORIGIN example.com.\n$TTL 3600\nwww IN A 203.0.113.10\n@ IN MX 10 mail.example.com.\ndkim IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqh"\n'
})
};
fetch('https://dns.basaltic.sh/v1/zones/{zone_id}/import', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://dns.basaltic.sh/v1/zones/{zone_id}/import",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'zone_file' => '$ORIGIN example.com.
$TTL 3600
www IN A 203.0.113.10
@ IN MX 10 mail.example.com.
dkim IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqh"
'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://dns.basaltic.sh/v1/zones/{zone_id}/import"
payload := strings.NewReader("{\n \"zone_file\": \"$ORIGIN example.com.\\n$TTL 3600\\nwww IN A 203.0.113.10\\n@ IN MX 10 mail.example.com.\\ndkim IN TXT \\\"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\\\"\\n\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://dns.basaltic.sh/v1/zones/{zone_id}/import")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"zone_file\": \"$ORIGIN example.com.\\n$TTL 3600\\nwww IN A 203.0.113.10\\n@ IN MX 10 mail.example.com.\\ndkim IN TXT \\\"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\\\"\\n\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://dns.basaltic.sh/v1/zones/{zone_id}/import")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"zone_file\": \"$ORIGIN example.com.\\n$TTL 3600\\nwww IN A 203.0.113.10\\n@ IN MX 10 mail.example.com.\\ndkim IN TXT \\\"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\\\"\\n\"\n}"
response = http.request(request)
puts response.read_body{
"import": {
"records_created": 7,
"records_replaced": 2,
"records_by_type": {
"A": 4,
"MX": 1,
"TXT": 2
},
"skipped": [
{
"name": "example.com",
"type": "NS",
"reason": "the apex NS set is the delegation this platform's nameservers answer for"
}
],
"warnings": [
"<string>"
]
}
}{
"error": {
"code": "INVALID_INPUT",
"message": "Invalid request parameters",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"params": {
"instances": 2,
"pools": 0
}
}
}{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication required",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "ACCESS_DENIED",
"message": "You don't have permission to perform this action",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "NOT_FOUND",
"message": "Resource not found",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "INVALID_INPUT",
"message": "Invalid request parameters",
"request_id": "550e8400-e29b-41d4-a716-446655440000",
"params": {
"instances": 2,
"pools": 0
}
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal error occurred",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}dns:ImportZoneFile. See DNS permissions for the full list, what each one covers, and an example policy.Authorizations
An OAuth 2.0 bearer token, sent as Authorization: Bearer <token>.
This is the recommended way to authenticate.
Get one by exchanging a service account's access key pair at
POST /v1/oauth/token with grant_type=client_credentials. It is the
standard client-credentials grant, so any OAuth-aware library will
obtain and refresh it for you.
curl -s -u "$KEY_ID:$SECRET" -d grant_type=client_credentials \
https://iam.basaltic.sh/v1/oauth/token
Tokens last an hour by default. The same access key pair is separately your AWS SigV4 credential for the S3-compatible object endpoint, which speaks nothing else.
Headers
Optional client-generated key that makes a create replay-safe. Retrying a request with the same key returns the original outcome verbatim instead of creating a duplicate resource. Reusing a key with a different request body is rejected (422); a request whose key is still being processed returns 409. Records are honored for 24 hours. Use a UUID or similarly unique token.
255Path Parameters
DNS Zone ID
Body
The zone file, as text. $ORIGIN, $TTL, $GENERATE, relative names
and parenthesised multi-line records are all honoured; $INCLUDE is
refused, because the path it names would be read on our filesystem
rather than yours.
"$ORIGIN example.com.\n$TTL 3600\nwww IN A 203.0.113.10\n@ IN MX 10 mail.example.com.\ndkim IN TXT \"v=DKIM1; k=rsa; p=MIIBIjANBgkqh\"\n"
Response
The file was imported. Read skipped before concluding the zone
matches the file.
What the import did. records_created plus records_replaced is every
RRset taken from the file; anything in skipped is still in the file but
not in the zone.
Show child attributes
Show child attributes

