Email for your domain, with DNS setup handled. PostScale

    Sign Up

    Manage DNS with the Python SDK

    6 min readIntermediate

    Quick answer

    Install the dnscale package for Python 3.10+, set DNSCALE_API_KEY, and use DNScale to manage zones and records. Start with read-only calls, use iterators for pagination, and retain the record ID returned after each update. The SDK supports async calls and retries safe reads, but never automatically retries DNS changes.

    What you'll learn
    • Install the Python SDK and configure a scoped API key
    • List zones and records without missing additional pages
    • Create a zone and safely exercise a test record lifecycle
    • Use async operations and handle API and transport errors
    • Find the API reference and generated client for advanced operations

    The official DNScale Python SDK lets you work with DNS zones and records from scripts, services, and background jobs. This guide uses version 1.0.0 and starts with read-only calls before introducing changes.

    Install the package from PyPI; find the source and issue tracker in dnscaleou/dnscale-python. The install name and import name are both dnscale.

    Install and authenticate

    You need Python 3.10 or newer, a DNScale account, and an API key. In your Python virtual environment, install the release used by this guide:

    python -m pip install "dnscale==1.0.0"

    Create a key in the dashboard and supply it as the DNSCALE_API_KEY environment variable. Use a secret manager or a hidden local prompt rather than putting a real token in a script or shell history. For example, in Bash or Zsh:

    export DNSCALE_API_KEY="$(python -c 'import getpass; print(getpass.getpass("DNScale API key: "))')"

    Grant only the permissions the task needs:

    TaskAPI scopes
    List or read zoneszones:read
    List or read recordszones:read, records:read
    Create, update, or delete recordszones:read, records:write
    Create, update, or delete zoneszones:read, zones:write

    Zone and record-name restrictions still apply. For existing-zone automation, restrict the key to the zone and names it needs. Zone creation needs permission to create new zones; do not broaden a production record-only key just to try that example. See API authentication and scopes.

    DNScale() reads DNSCALE_API_KEY automatically and uses https://api.dnscale.eu/v1. You can also pass api_key= from your application's secret store. Do not commit keys or print them in logs.

    List zones and records

    This example is read-only. It traverses the zones and records visible to your API key:

    from dnscale import DNScale
     
    with DNScale() as dns:
        for zone in dns.zones.iter(page_size=100):
            print(zone.id, zone.name)
            for record in dns.records.iter(zone.id, page_size=100):
                print(record.id, record.name, record.type_, record.content)

    The context manager closes the HTTP client. iter() fetches pages as you consume them; list() returns just one page. To inspect a single page:

    from dnscale import DNScale
     
    with DNScale() as dns:
        page = dns.zones.list(limit=25, offset=0)
        print("Total zones:", page.pagination.total)
        for zone in page.zones:
            print(zone.id, zone.name)

    Responses are typed model objects, not dictionaries. Use attributes such as zone.id, page.zones, and record.content. Offset pagination is not an atomic snapshot: avoid treating a listing as a consistent backup while another process is changing the same resources.

    Create a zone

    Skip this step if you already have a test zone. This request creates a real zone in your account and is subject to your plan's limits. Set DNSCALE_ZONE_NAME to a domain or test subdomain you control and do not already manage in DNScale.

    import os
     
    from dnscale import DNScale
     
    with DNScale() as dns:
        zone = dns.zones.create(
            os.environ["DNSCALE_ZONE_NAME"],
            type_="master",
            region="EU",
        )
        print("Zone ID:", zone.id)
        print("Zone name:", zone.name)

    The SDK uses type_ for fields named type, and master is the API's primary-zone value. Creating a zone does not change your registrar's nameserver delegation. You can test API operations without delegating a live domain; plan delegation separately when you are ready to serve production DNS.

    For an existing zone, copy its UUID from the dashboard or the listing above and set it for the examples below:

    export DNSCALE_ZONE_ID="replace-with-your-test-zone-uuid"

    See the zone API reference for the available operations.

    Create, update, and delete a test record

    The following example changes DNS. Use a test zone and make sure the _sdk-demo TXT name is unused. The API key needs zones:read, records:read, and records:write. The values below are demonstration data, not production verification tokens.

    The script creates one TXT record, updates it, reads it back, and deletes that record. It does not delete the zone or any other records.

    import os
     
    from dnscale import DNScale
     
    zone_id = os.environ["DNSCALE_ZONE_ID"]
     
    with DNScale() as dns:
        created = dns.records.create(
            zone_id,
            name="_sdk-demo",
            type_="TXT",
            content="sdk-demo-first",
            ttl=300,
        )
        print("Created:", created.id)
     
        updated = dns.records.update(
            zone_id,
            created.id,
            name=created.name,
            type_="TXT",
            content="sdk-demo-second",
            ttl=300,
        )
        print("Updated:", updated.id)
     
        current = dns.records.get(zone_id, updated.id)
        print(current.name, current.content)
     
        dns.records.delete(zone_id, updated.id)
        print("Deleted the demo record")

    Record IDs are opaque and content-derived. Use the ID returned after an update, not the previous ID. Keep the intended record data when updating: the ID-based update helper sends a full record body rather than a partial patch.

    If the script stops after a successful create or update, the demo record may remain. Inspect the test zone and remove that specific value before rerunning. A timeout does not prove a write failed; do not blindly repeat a creation request.

    The SDK also has update_by_name() and delete_by_name() helpers. When using delete_by_name(), supply content to select one value. Omitting content deletes the entire RRset: all values with that name and type. Read the record API reference before using these helpers on shared names such as _acme-challenge.

    Use the async client

    Use async with and the async equivalents in an asynchronous application. This example only reads records from the selected test zone:

    import asyncio
    import os
     
    from dnscale import DNScale
     
     
    async def main() -> None:
        async with DNScale() as dns:
            async for record in dns.records.aiter(os.environ["DNSCALE_ZONE_ID"]):
                print(record.id, record.name, record.content)
     
     
    asyncio.run(main())

    The resource methods include alist(), aget(), acreate(), aupdate(), and adelete(). Use aiter() with async for for pagination. In a framework or notebook that already runs an event loop, await your function instead of starting another loop with asyncio.run().

    Handle errors and retries

    The zone and record helpers raise APIError for API failures and RateLimitError for rate limiting. Network and timeout failures remain httpx exceptions.

    import httpx
     
    from dnscale import APIError, DNScale, RateLimitError
     
    try:
        with DNScale(timeout=15.0, max_retries=2) as dns:
            page = dns.zones.list(limit=10)
            print("Total zones:", page.pagination.total)
    except RateLimitError as error:
        print("Rate limited; Retry-After:", error.retry_after)
    except APIError as error:
        print(error.status_code, error.code, error.message, error.request_id)
    except httpx.RequestError as error:
        print("Transport failure:", type(error).__name__)

    Catch RateLimitError before APIError because it is a subclass. Missing API keys or invalid UUIDs can raise ValueError locally before a request is sent.

    By default, the client makes up to two retries for transient failures on safe methods (GET, HEAD, and OPTIONS). Set max_retries=0 to disable them. It never automatically retries mutations such as zone or record creation, updates, or deletion.

    The SDK honours numeric and HTTP-date Retry-After values within its bounded retry policy. A server delay above the 30-second waiting budget is surfaced immediately instead of retrying earlier than permitted. error.retry_after contains the header value when present; it is not necessarily an integer. For a failed write, inspect the current state before deciding whether another attempt is safe.

    Access the wider API

    dnscale provides convenience methods for zones and records. The same distribution includes dnscale_api, generated from the bundled OpenAPI contract, for operations such as DNSSEC, usage, billing, users, and invitations.

    For example, use the usage endpoint with a key that has usage:read:

    from dnscale import DNScale
    from dnscale_api.api.usage import get_current_usage
     
    with DNScale() as dns:
        response = get_current_usage.sync_detailed(client=dns.client)
        print(response.status_code)
        print(response.parsed)

    Generated operations offer sync, sync_detailed, asyncio, and asyncio_detailed variants. Models live in dnscale_api.models. Detailed responses expose the status, headers, and parsed body. Unlike the zone and record convenience helpers, generated operations can return parsed error models; check the status and response type before treating the result as success.

    Choose the right automation tool

    Use this SDK for Python scripts, tenant-onboarding flows, internal tools, and application-driven records. It does not maintain desired state, provide a dry-run plan, or automatically roll back changes.

    • Terraform fits DNS managed alongside infrastructure state.
    • DNSControl fits DNS-first configuration repositories.
    • The REST API works with other languages and custom HTTP clients.
    • DNS automation covers ownership, review, and safe deployment patterns.

    Choose one routine writer for each record set. After a production change, verify authoritative answers and allow for resolver caches; an API success response does not mean every resolver already has the new answer. Use the DNS lookup tool and propagation checker to check the result.

    Frequently asked questions

    What is the DNScale Python package called?
    Install dnscale from PyPI and import DNScale from dnscale. The source repository is dnscaleou/dnscale-python. The package also includes dnscale_api for generated endpoint clients and models.
    Which Python versions does the SDK support?
    DNScale Python SDK 1.0.0 supports Python 3.10 and newer.
    Does the SDK support async code?
    Yes. Use async with DNScale() and async methods such as zones.alist(), records.acreate(), and the zones.aiter() and records.aiter() async iterators.
    Does the SDK automatically retry DNS changes?
    No. Automatic retries are limited to safe read methods. After a failed or timed-out write, inspect the current state before deciding whether to repeat the operation.
    Should I use the SDK or Terraform?
    Use the SDK for application-driven operations and Python scripts. Use Terraform or DNSControl when DNS configuration is managed as desired state. Avoid having multiple tools routinely write to the same record set.

    Put the guide into practice

    Manage authoritative DNS records, review changes, and monitor your zones with DNScale.

    Start free