curl --request POST \
--url https://dns.basaltic.sh/v1/zones \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "example.com",
"description": "Production apex",
"visibility": "public",
"dnssec": true,
"import_existing_records": true,
"vpcs": [
"c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9"
],
"tags": {
"environment": "production",
"team": "backend"
}
}
'import requests
url = "https://dns.basaltic.sh/v1/zones"
payload = {
"name": "example.com",
"description": "Production apex",
"visibility": "public",
"dnssec": True,
"import_existing_records": True,
"vpcs": ["c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9"],
"tags": {
"environment": "production",
"team": "backend"
}
}
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({
name: 'example.com',
description: 'Production apex',
visibility: 'public',
dnssec: true,
import_existing_records: true,
vpcs: ['c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9'],
tags: {environment: 'production', team: 'backend'}
})
};
fetch('https://dns.basaltic.sh/v1/zones', 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",
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([
'name' => 'example.com',
'description' => 'Production apex',
'visibility' => 'public',
'dnssec' => true,
'import_existing_records' => true,
'vpcs' => [
'c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9'
],
'tags' => [
'environment' => 'production',
'team' => 'backend'
]
]),
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"
payload := strings.NewReader("{\n \"name\": \"example.com\",\n \"description\": \"Production apex\",\n \"visibility\": \"public\",\n \"dnssec\": true,\n \"import_existing_records\": true,\n \"vpcs\": [\n \"c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9\"\n ],\n \"tags\": {\n \"environment\": \"production\",\n \"team\": \"backend\"\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")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"example.com\",\n \"description\": \"Production apex\",\n \"visibility\": \"public\",\n \"dnssec\": true,\n \"import_existing_records\": true,\n \"vpcs\": [\n \"c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9\"\n ],\n \"tags\": {\n \"environment\": \"production\",\n \"team\": \"backend\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://dns.basaltic.sh/v1/zones")
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 \"name\": \"example.com\",\n \"description\": \"Production apex\",\n \"visibility\": \"public\",\n \"dnssec\": true,\n \"import_existing_records\": true,\n \"vpcs\": [\n \"c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9\"\n ],\n \"tags\": {\n \"environment\": \"production\",\n \"team\": \"backend\"\n }\n}"
response = http.request(request)
puts response.read_body{
"zone": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"crn": "crn:dns::my-account:zone/example.com",
"name": "example.com",
"description": "Public zone for the marketing site",
"nameservers": [
"028t5cy4tqkff.triton.dns.basaltic.cloud",
"028t5cy4tqkff.proteus.dns.basaltic.cloud"
],
"soa": {
"primary_ns": "028t5cy4tqkff.triton.dns.basaltic.cloud",
"admin_email": "hostmaster@basaltic.sh",
"refresh": 10800,
"retry": 3600,
"expire": 604800,
"minimum": 3600
},
"visibility": "public",
"dnssec": {
"enabled": true,
"ksk_key_tag": 12345,
"zsk_key_tag": 54321,
"algorithm": 13,
"ds_records": [
{
"key_tag": 12345,
"algorithm": 13,
"digest_type": 2,
"digest": "B8FA03AA1BB49CC702C32C5C3FE6061BBDD0808371F43A28A8E8BE2738CC9861",
"rdata": "12345 13 2 B8FA03AA1BB49CC702C32C5C3FE6061BBDD0808371F43A28A8E8BE2738CC9861"
}
]
},
"tags": {
"environment": "production",
"team": "backend"
},
"ownership": {
"verified": false,
"verified_at": "2026-01-15T09:30:00Z",
"checked_at": "2026-04-14T09:30:00Z",
"recheck_deadline": "2026-04-28T09:30:00Z"
}
}
}{
"error": {
"code": "INVALID_INPUT",
"message": "Invalid request parameters",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"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": "IDEMPOTENCY_KEY_REUSED",
"message": "This Idempotency-Key was already used with a different request payload",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal error occurred",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}Create zone
Create a new DNS zone. The SOA and NS records are generated automatically against the platform nameserver list; the zone is queryable immediately on success.
Defaults to a public zone. Pass visibility: private plus at least one
vpcs entry for a zone that resolves only inside those VPCs — a
private zone with no VPC, or a public zone carrying vpcs, is
rejected with 400 rather than silently coerced.
curl --request POST \
--url https://dns.basaltic.sh/v1/zones \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "example.com",
"description": "Production apex",
"visibility": "public",
"dnssec": true,
"import_existing_records": true,
"vpcs": [
"c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9"
],
"tags": {
"environment": "production",
"team": "backend"
}
}
'import requests
url = "https://dns.basaltic.sh/v1/zones"
payload = {
"name": "example.com",
"description": "Production apex",
"visibility": "public",
"dnssec": True,
"import_existing_records": True,
"vpcs": ["c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9"],
"tags": {
"environment": "production",
"team": "backend"
}
}
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({
name: 'example.com',
description: 'Production apex',
visibility: 'public',
dnssec: true,
import_existing_records: true,
vpcs: ['c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9'],
tags: {environment: 'production', team: 'backend'}
})
};
fetch('https://dns.basaltic.sh/v1/zones', 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",
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([
'name' => 'example.com',
'description' => 'Production apex',
'visibility' => 'public',
'dnssec' => true,
'import_existing_records' => true,
'vpcs' => [
'c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9'
],
'tags' => [
'environment' => 'production',
'team' => 'backend'
]
]),
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"
payload := strings.NewReader("{\n \"name\": \"example.com\",\n \"description\": \"Production apex\",\n \"visibility\": \"public\",\n \"dnssec\": true,\n \"import_existing_records\": true,\n \"vpcs\": [\n \"c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9\"\n ],\n \"tags\": {\n \"environment\": \"production\",\n \"team\": \"backend\"\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")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"example.com\",\n \"description\": \"Production apex\",\n \"visibility\": \"public\",\n \"dnssec\": true,\n \"import_existing_records\": true,\n \"vpcs\": [\n \"c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9\"\n ],\n \"tags\": {\n \"environment\": \"production\",\n \"team\": \"backend\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://dns.basaltic.sh/v1/zones")
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 \"name\": \"example.com\",\n \"description\": \"Production apex\",\n \"visibility\": \"public\",\n \"dnssec\": true,\n \"import_existing_records\": true,\n \"vpcs\": [\n \"c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9\"\n ],\n \"tags\": {\n \"environment\": \"production\",\n \"team\": \"backend\"\n }\n}"
response = http.request(request)
puts response.read_body{
"zone": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"crn": "crn:dns::my-account:zone/example.com",
"name": "example.com",
"description": "Public zone for the marketing site",
"nameservers": [
"028t5cy4tqkff.triton.dns.basaltic.cloud",
"028t5cy4tqkff.proteus.dns.basaltic.cloud"
],
"soa": {
"primary_ns": "028t5cy4tqkff.triton.dns.basaltic.cloud",
"admin_email": "hostmaster@basaltic.sh",
"refresh": 10800,
"retry": 3600,
"expire": 604800,
"minimum": 3600
},
"visibility": "public",
"dnssec": {
"enabled": true,
"ksk_key_tag": 12345,
"zsk_key_tag": 54321,
"algorithm": 13,
"ds_records": [
{
"key_tag": 12345,
"algorithm": 13,
"digest_type": 2,
"digest": "B8FA03AA1BB49CC702C32C5C3FE6061BBDD0808371F43A28A8E8BE2738CC9861",
"rdata": "12345 13 2 B8FA03AA1BB49CC702C32C5C3FE6061BBDD0808371F43A28A8E8BE2738CC9861"
}
]
},
"tags": {
"environment": "production",
"team": "backend"
},
"ownership": {
"verified": false,
"verified_at": "2026-01-15T09:30:00Z",
"checked_at": "2026-04-14T09:30:00Z",
"recheck_deadline": "2026-04-28T09:30:00Z"
}
}
}{
"error": {
"code": "INVALID_INPUT",
"message": "Invalid request parameters",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"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": "IDEMPOTENCY_KEY_REUSED",
"message": "This Idempotency-Key was already used with a different request payload",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}{
"error": {
"code": "INTERNAL_ERROR",
"message": "An internal error occurred",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}dns:CreateZone. 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.
255Body
Zone FQDN. Resource names must not start with the literal crn: prefix or be UUIDs (canonical, compact, braced, or urn:uuid: forms, in either case).
"example.com"
Free-form note stored with and returned on the zone.
"Production apex"
private restricts the zone to the VPCs named in vpcs and
requires at least one; public (the default) rejects vpcs
outright rather than ignoring them. Cannot be changed afterwards.
public, private "public"
Sign the zone with DNSSEC. On unless you say otherwise, and almost every zone should leave it on.
Turn it off only if this domain is served by another DNS provider at the same time as us. A signed zone puts our DS record at the parent, and that DS covers only the answers WE sign — so a validating resolver that happens to ask the other provider gets a signature it cannot verify and fails the lookup. Roughly half your queries, unpredictably, which is worse than either provider on its own. Unsigned is the only configuration that works for that setup today.
Fixed at creation. Turning signing off later breaks the domain until the DS is withdrawn at the registrar and that withdrawal has propagated, which is a sequence this API cannot drive for you.
true
Read the domain's records from the nameservers that serve it TODAY and copy them into this zone, before you move the delegation here.
Worth asking for when you are migrating a live domain. The delegation is the ownership proof, so the moment you point your registrar at this zone is the moment we start answering for it — and an empty zone answers with nothing, which takes the site and the mail down until you have retyped everything.
Runs in the background; the zone is created immediately. Poll GET /v1/zones/{zone_id}/record-import for the outcome.
Best effort, and the result says how good it was. A zone transfer is
exhaustive and almost always refused; the fallback queries a list of
common names and cannot find a record it did not think to ask for.
Check record_import.complete before you switch your old provider off.
Records you have already created are never overwritten, and records this platform manages itself — the SOA, the DNSSEC chain, the zone's nameservers — are never imported.
true
Account-owned VPC UUIDs or network/vpc CRNs the zone resolves in. Bare names are rejected with 400 because the request fixes no region. CRNs resolve in their named region; UUIDs search all regions enabled for DNS. Missing, foreign-account or unconfigured-region VPCs return 404. Incomplete UUID searches or duplicate regional UUID identities fail with a server error. References are deduplicated by UUID. Required when visibility=private, rejected when visibility=public. More can be associated later via POST /v1/zones/{zone_id}/vpc-associations.
["c3d4e5f6-a7b8-4901-c2d3-e4f5a6b7c8d9"]
Show child attributes
Show child attributes
{ "environment": "production", "team": "backend" }
Response
Zone created
Show child attributes
Show child attributes

