DNS

Client API

102 views
Published 3 weeks ago
Updated 16 hours ago

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 invalid
  • 500 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 fields
  • 401 Unauthorized - Client ID not found or invalid
  • 403 Forbidden - Domain limit reached (quota exceeded)
  • 404 Not Found - Client not found
  • 500 Internal Server Error - Server error

Quota Check:

  • The system checks if the client has reached their domain_limit before creating a new domain
  • If the limit is reached, a 403 Forbidden error 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 ID
  • 401 Unauthorized - Client ID not found or invalid
  • 404 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 Found error 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 ID
  • 401 Unauthorized - Client ID not found or invalid
  • 404 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 Found error 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_codes is 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 fields
  • 401 Unauthorized - Client ID not found or invalid
  • 404 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 Found error 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 body
  • 401 Unauthorized - Client ID not found or invalid
  • 404 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 Found error 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 ID
  • 401 Unauthorized - Client ID not found or invalid
  • 404 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 Found error 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/plain or application/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., 3600 for 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)

Supported Record Types:

  • A - IPv4 address records
  • AAAA - IPv6 address records
  • CNAME - Canonical name records
  • MX - Mail exchange records (priority extracted)
  • TXT - Text records
  • SRV - Service records (priority extracted)
  • PTR - Pointer records

Supported Formats:

  1. Standard BIND Format:

    $ORIGIN example.com.
    $TTL 3600
    @       IN  A       192.0.2.1
    www     IN  A       192.0.2.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 found
  • 401 Unauthorized - Client ID not found or invalid
  • 403 Forbidden - Domain limit reached (when creating new domain)
  • 404 Not Found - Client not found or domain access denied
  • 409 Conflict - Domain already exists (if domain was provided but doesn't belong to client)
  • 500 Internal Server Error - Server error

Behavior

  1. 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 $ORIGIN directive or inferred from record names
  2. 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
  3. 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
  4. 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 Found error 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

Supported Record Types: All record types are exported:

  • A - IPv4 address records
  • AAAA - IPv6 address records
  • CNAME - Canonical name records
  • MX - Mail exchange records (with priority)
  • TXT - Text records
  • SRV - 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 ID
  • 401 Unauthorized - Client ID not found or invalid
  • 404 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 Found error 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 invalid
  • 404 Not Found - Client not found
  • 500 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.

Was this article helpful?

Let us know if you found this article useful