Overview
The Client API provides endpoints for clients to manage their own domains and DNS records. All endpoints require authentication using a client API token.
Authentication
All Client API endpoints require authentication via the Authorization header:
Authorization: Bearer <client_api_token>
Alternatively, you can provide the token directly without the "Bearer " prefix:
Authorization: <client_api_token>
The client ID is automatically extracted from the token and set in the X-Client-ID header for use in handlers.
Base URL
All endpoints are prefixed with /api/client.
Endpoints
List Domains
Retrieve all domains owned by the authenticated client.
Endpoint: GET /api/client/domains
Response: 200 OK
[
{
"id": "uuid",
"client_id": "uuid",
"name": "string",
"active": "boolean",
"created_at": "timestamp",
"updated_at": "timestamp"
}
]
Error Responses:
401 Unauthorized- Client ID not found or invalid500 Internal Server Error- Server error
Create Domain
Create a new domain for the authenticated client. The domain quota is automatically checked.
Endpoint: POST /api/client/domains
Request Body:
{
"name": "string (required)"
}
Note: Domain names are automatically normalized (lowercased, trimmed, and a trailing dot is added if missing).
Response: 201 Created
{
"id": "uuid",
"client_id": "uuid",
"name": "string",
"active": "boolean",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Error Responses:
400 Bad Request- Invalid request body or missing required fields401 Unauthorized- Client ID not found or invalid403 Forbidden- Domain limit reached (quota exceeded)404 Not Found- Client not found500 Internal Server Error- Server error
Quota Check:
- The system checks if the client has reached their
domain_limitbefore creating a new domain - If the limit is reached, a
403 Forbiddenerror is returned with the message "Domain limit reached"
Delete Domain
Delete a domain owned by the authenticated client.
Endpoint: DELETE /api/client/domains/{id}
Path Parameters:
id(uuid, required) - Domain ID
Response: 200 OK
{
"message": "Domain deleted"
}
Error Responses:
400 Bad Request- Invalid domain ID401 Unauthorized- Client ID not found or invalid404 Not Found- Domain not found or access denied (domain doesn't belong to client)500 Internal Server Error- Server error
Access Control:
- The system verifies that the domain belongs to the authenticated client
- If the domain doesn't belong to the client, a
404 Not Founderror is returned
List Records for Domain
Retrieve all DNS records for a specific domain owned by the authenticated client.
Endpoint: GET /api/client/domains/{id}/records
Path Parameters:
id(uuid, required) - Domain ID
Response: 200 OK
[
{
"id": "uuid",
"domain_id": "uuid",
"name": "string",
"type": "string",
"ttl": "integer",
"country_codes": ["string"],
"priority": "integer",
"value": "string",
"created_at": "timestamp",
"updated_at": "timestamp"
}
]
Error Responses:
400 Bad Request- Invalid domain ID401 Unauthorized- Client ID not found or invalid404 Not Found- Domain not found or access denied (domain doesn't belong to client)500 Internal Server Error- Server error
Access Control:
- The system verifies that the domain belongs to the authenticated client
- If the domain doesn't belong to the client, a
404 Not Founderror is returned
Records
Create Record
Create a new DNS record for a domain owned by the authenticated client.
Endpoint: POST /api/client/records
Request Body:
{
"domain_id": "uuid (required)",
"name": "string (required)",
"type": "string (required)",
"value": "string (required)",
"ttl": "integer (optional, default: 3600)",
"priority": "integer (optional)",
"country_codes": ["string"] (optional, default: ["disabled"])
}
Note:
- Record type is automatically converted to uppercase
- If
country_codesis empty or not provided, it defaults to["disabled"] - Country codes can be:
["disabled"]or[]- No GeoDNS, returned to all requests["US"]- Specific country["US", "CA", "MX"]- Multiple countries (regional group)
- The system verifies that the domain belongs to the authenticated client
Response: 201 Created
{
"id": "uuid",
"domain_id": "uuid",
"name": "string",
"type": "string",
"ttl": "integer",
"country_codes": ["string"],
"priority": "integer",
"value": "string",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Error Responses:
400 Bad Request- Invalid request body or missing required fields401 Unauthorized- Client ID not found or invalid404 Not Found- Domain not found or access denied (domain doesn't belong to client)500 Internal Server Error- Server error
Access Control:
- The system verifies that the domain belongs to the authenticated client before creating the record
- If the domain doesn't belong to the client, a
404 Not Founderror is returned
Update Record
Update an existing DNS record. The record must belong to a domain owned by the authenticated client.
Endpoint: PUT /api/client/records/{id}
Path Parameters:
id(uuid, required) - Record ID
Request Body:
{
"name": "string",
"type": "string",
"value": "string",
"ttl": "integer",
"priority": "integer",
"country_codes": ["string"]
}
Note: Record type is automatically converted to uppercase.
Response: 200 OK
{
"id": "uuid",
"domain_id": "uuid",
"name": "string",
"type": "string",
"ttl": "integer",
"country_codes": ["string"],
"priority": "integer",
"value": "string",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Error Responses:
400 Bad Request- Invalid record ID or request body401 Unauthorized- Client ID not found or invalid404 Not Found- Record not found or access denied (record doesn't belong to client's domain)500 Internal Server Error- Server error
Access Control:
- The system verifies that the record belongs to a domain owned by the authenticated client
- If the record doesn't belong to the client's domain, a
404 Not Founderror is returned
Delete Record
Delete a DNS record. The record must belong to a domain owned by the authenticated client.
Endpoint: DELETE /api/client/records/{id}
Path Parameters:
id(uuid, required) - Record ID
Response: 200 OK
{
"message": "Record deleted"
}
Error Responses:
400 Bad Request- Invalid record ID401 Unauthorized- Client ID not found or invalid404 Not Found- Record not found or access denied (record doesn't belong to client's domain)500 Internal Server Error- Server error
Access Control:
- The system verifies that the record belongs to a domain owned by the authenticated client
- If the record doesn't belong to the client's domain, a
404 Not Founderror is returned
Import BIND Zone File
Import DNS records from a BIND zone file. This endpoint allows you to bulk import DNS records from other DNS providers. NS records are automatically ignored as the system uses its own name servers.
Endpoint: POST /api/client/import/bind
Request Body:
- Content-Type:
text/plainorapplication/octet-stream - The request body should contain the BIND zone file content
BIND Zone File Format: The endpoint supports standard BIND zone file format and provider-specific formats (e.g., BunnyDNS) with the following directives and record types:
Directives:
$ORIGIN <domain>- Sets the origin domain (optional, can be inferred from records)$TTL <value>- Sets the default TTL for records (optional, defaults to 3600)- Supports numeric values (e.g.,
3600for seconds) - Supports unit suffixes:
s(seconds),m(minutes),h(hours),d(days),w(weeks) - Examples:
1d(1 day = 86400 seconds),2h(2 hours = 7200 seconds),30m(30 minutes = 1800 seconds)
- Supports numeric values (e.g.,
Supported Record Types:
A- IPv4 address recordsAAAA- IPv6 address recordsCNAME- Canonical name recordsMX- Mail exchange records (priority extracted)TXT- Text recordsSRV- Service records (priority extracted)PTR- Pointer records
Supported Formats:
-
Standard BIND Format:
$ORIGIN example.com. $TTL 3600 @ IN A 192.0.2.1 www IN A 192.0.2.2 -
BunnyDNS Format:
i.b4y.net. IN 1d CNAME img-b4y-net.b-cdn.net. local.b4y.net. IN 1d A 192.168.0.71
Note: NS records are automatically ignored during import. TTL values can be specified in various formats (numeric seconds, or with unit suffixes like 1d, 2h, 30m).
Example BIND Zone File (Standard Format):
$ORIGIN example.com.
$TTL 3600
@ IN A 192.0.2.1
www IN A 192.0.2.2
mail IN A 192.0.2.3
IN MX 10 mail.example.com.
www IN CNAME example.com.
sub IN A 192.0.2.4
Example BunnyDNS Zone File:
;CNAME records
i.b4y.net. IN 1d CNAME img-b4y-net.b-cdn.net.
resumes.b4y.net. IN 1d CNAME rev.enzonix.com.
b4y.net. IN 15 CNAME b4y-site.b-cdn.net.
;A records
local.b4y.net. IN 1d A 192.168.0.71
stor1.b4y.net. IN 1d A 192.168.0.5
ny.r.b4y.net. IN 1d A 109.122.60.6
Response: 201 Created (or 206 Partial Content if some records failed)
Success Response:
{
"domain": {
"id": "uuid",
"client_id": "uuid",
"name": "example.com.",
"active": true,
"created_at": "timestamp",
"updated_at": "timestamp"
},
"records_created": 5,
"records": [
{
"id": "uuid",
"domain_id": "uuid",
"name": "example.com.",
"type": "A",
"ttl": 3600,
"country_codes": ["disabled"],
"priority": 0,
"value": "192.0.2.1.",
"created_at": "timestamp",
"updated_at": "timestamp"
}
]
}
Partial Success Response (206 Partial Content):
{
"domain": {
"id": "uuid",
"client_id": "uuid",
"name": "example.com.",
"active": true,
"created_at": "timestamp",
"updated_at": "timestamp"
},
"records_created": 3,
"partial_success": true,
"errors": [
"Failed to create record sub.example.com. A: duplicate record",
"Record invalid.example.com. does not belong to domain example.com."
],
"records": [
{
"id": "uuid",
"domain_id": "uuid",
"name": "example.com.",
"type": "A",
"ttl": 3600,
"country_codes": ["disabled"],
"priority": 0,
"value": "192.0.2.1.",
"created_at": "timestamp",
"updated_at": "timestamp"
}
]
}
Error Responses
400 Bad Request- Invalid zone file format, parsing error, or no valid records found401 Unauthorized- Client ID not found or invalid403 Forbidden- Domain limit reached (when creating new domain)404 Not Found- Client not found or domain access denied409 Conflict- Domain already exists (if domain was provided but doesn't belong to client)500 Internal Server Error- Server error
Behavior
-
Domain Handling:
- If the domain doesn't exist, it will be created automatically (subject to quota limits)
- If the domain exists, it must belong to the authenticated client
- Domain name is extracted from
$ORIGINdirective or inferred from record names
-
Record Import:
- All valid records are imported except NS records (which are ignored)
- Records are created with GeoDNS disabled by default (
country_codes: ["disabled"]) - TTL values are preserved from the zone file or use the default (3600)
- MX and SRV record priorities are extracted and stored correctly
-
Partial Success:
- If some records fail to import, the endpoint returns
206 Partial Content - The response includes both successfully created records and error messages
- Successfully imported records are still created even if others fail
- If some records fail to import, the endpoint returns
-
Validation:
- All record names must belong to the domain or be subdomains of it
- Records with invalid formats or unsupported types are skipped
- NS records are automatically filtered out
Example cURL Request:
curl -X POST https://master.example.com/api/client/import/bind \
-H "Authorization: Bearer <client_api_token>" \
-H "Content-Type: text/plain" \
--data-binary @zonefile.db
Example with inline zone file:
curl -X POST https://master.example.com/api/client/import/bind \
-H "Authorization: Bearer <client_api_token>" \
-H "Content-Type: text/plain" \
-d '$ORIGIN example.com.
$TTL 3600
@ IN A 192.0.2.1
www IN A 192.0.2.2'
Access Control:
- The system verifies that the client owns the domain (or creates it if it doesn't exist)
- All imported records are associated with the authenticated client
- If the domain already exists but belongs to another client, a
404 Not Founderror is returned
Export BIND Zone File
Export all DNS records for a domain as a BIND zone file. This endpoint allows you to download all records for a domain in standard BIND zone file format.
Endpoint: GET /api/client/domains/{id}/export/bind
Path Parameters:
id(uuid, required) - Domain ID
Response: 200 OK
- Content-Type:
text/plain - Content-Disposition:
attachment; filename="{domain}.zone" - The response body contains the BIND zone file content
BIND Zone File Format: The exported file follows standard BIND zone file format:
- Header Comments: Includes metadata about the zone file
- $ORIGIN Directive: Sets the origin domain (without trailing dot)
- $TTL Directive: Sets the default TTL (uses the first record's TTL or 3600)
- Records: All DNS records in standard BIND format
- Records at the apex domain use
@as the name - Subdomains use relative names
- Records with custom TTLs include the TTL value, others use the default
- Records at the apex domain use
Supported Record Types: All record types are exported:
A- IPv4 address recordsAAAA- IPv6 address recordsCNAME- Canonical name recordsMX- Mail exchange records (with priority)TXT- Text recordsSRV- Service records (with priority and weight)PTR- Pointer records
Example Exported Zone File:
; BIND zone file
; Domain: example.com.
; Generated by DNS Server
$ORIGIN example.com
$TTL 3600
@ IN A 192.0.2.1
www IN A 192.0.2.2
mail IN A 192.0.2.3
@ IN MX 10 mail.example.com.
www IN CNAME example.com.
sub IN A 192.0.2.4
Error Responses:
400 Bad Request- Invalid domain ID401 Unauthorized- Client ID not found or invalid404 Not Found- Domain not found or access denied (domain doesn't belong to client)500 Internal Server Error- Server error
Access Control:
- The system verifies that the domain belongs to the authenticated client
- If the domain doesn't belong to the client, a
404 Not Founderror is returned - Only records for the specified domain are exported
Example cURL Request:
curl -X GET https://master.example.com/api/client/domains/{domain_id}/export/bind \
-H "Authorization: Bearer <client_api_token>" \
-o example.com.zone
Example with wget:
wget --header="Authorization: Bearer <client_api_token>" \
https://master.example.com/api/client/domains/{domain_id}/export/bind \
-O example.com.zone
Notes:
- The exported file can be used with the import endpoint for backup/restore purposes
- The file format is compatible with standard BIND DNS servers
- Records are exported exactly as stored, including TTL values and priorities
- GeoDNS country codes are not included in the export (they are system-specific features)
Rotate API Key
Regenerate or rotate the authenticated client's API key. This endpoint generates a new secure API token and replaces the current one. The old API token will immediately become invalid.
Endpoint: POST /api/client/rotate-api-key
Request Body: None
Response: 200 OK
{
"id": "uuid",
"name": "string",
"email": "string",
"api_token": "string",
"domain_limit": "integer",
"created_at": "timestamp",
"updated_at": "timestamp"
}
Error Responses:
401 Unauthorized- Client ID not found or invalid404 Not Found- Client not found500 Internal Server Error- Server error
Security Notes:
- The new API token is automatically generated using cryptographically secure random bytes
- The old API token becomes invalid immediately after rotation
- The response includes the new API token - make sure to save it securely
- After rotation, all subsequent API requests must use the new token
Example cURL Request:
curl -X POST https://master.example.com/api/client/rotate-api-key \
-H "Authorization: Bearer <current_client_api_token>"
Important: After rotating your API key, update all applications and scripts that use the old token with the new token from the response.
Error Format
All error responses follow this format:
{
"error": "Error message"
}
GeoDNS Information
DNS records support GeoDNS routing through the country_codes field:
["disabled"]or[]: Record is returned to all DNS queries regardless of location["US"]: Record is returned only for queries originating from the United States["US", "CA", "MX"]: Record is returned for queries from multiple countries (regional group)["NA", "EU"]: Record is returned for queries from these global areas (continent & groups)
The DNS server uses geographic proximity matching when an exact country match is not found.