Skip to main content

technitiumdns-api

PyPI - Version Python versions License: GPL v3

Async + sync Python client for the Technitium DNS Server HTTP API.

Built originally to power the home-assistant-technitiumdns integration, but works in any Python project.

Features

  • 100% coverage of the documented Technitium DNS Server API (~140 endpoints).
  • Two interchangeable clients: AsyncClient (aiohttp) and Client (httpx).
  • Typed dataclass models for every response shape, with raw payloads always available.
  • Bearer-token auth with backward-compatible ?token= query-string fallback.
  • Automatic Technitium response envelope handling (status, errorMessage, invalid-token, 2fa-required).
  • Optional node="cluster" (or any node domain) on every endpoint that supports clustering.
  • File upload helpers for zone import, settings restore and app install.
  • Binary download helpers for zone export, settings backup and log download.
  • DNS-app query-log discovery (apps.list_dns_loggers()).

Install

pip install technitiumdns-api

Requires Python 3.11+.

Quick start - async

import asyncio
from technitiumdns import AsyncClient

async def main():
    async with AsyncClient("http://dns.local:5380", token="YOUR_API_TOKEN") as api:
        stats = await api.dashboard.stats(type="LastHour", utc=True)
        print(stats.stats.total_queries, "queries in the last hour")

        leases = await api.dhcp.leases_list()
        for lease in leases:
            print(lease.address, lease.hardware_address, lease.host_name)

asyncio.run(main())

Quick start - sync

from technitiumdns import Client

with Client("http://dns.local:5380", token="YOUR_API_TOKEN") as api:
    settings = api.settings.get()
    print("Blocking enabled:", settings.enable_blocking)
    api.settings.temporary_disable_blocking(minutes=5)

Login + token capture

async with AsyncClient("http://dns.local:5380") as api:
    result = await api.login(user="admin", password="admin")
    print("token:", result.token)
    # the client now uses the token automatically for subsequent calls
    print(await api.dashboard.stats(type="LastHour"))

Cluster support

Pass node="cluster" (or a specific node domain) once at construction time and every call will include it automatically:

async with AsyncClient(URL, token=TOKEN, node="cluster") as api:
    aggregate_stats = await api.dashboard.stats(type="LastDay")

Per-call overrides still work: await api.dashboard.stats(type="LastDay", node="server2.example.com").

Home Assistant integration

When using AsyncClient from inside Home Assistant, share HA's pooled aiohttp session to play nicely with the rest of the ecosystem:

from homeassistant.helpers import aiohttp_client
from technitiumdns import AsyncClient

session = aiohttp_client.async_get_clientsession(hass, verify_ssl=True)
api = AsyncClient(
    entry.data["api_url"],
    token=entry.data["token"],
    session=session,
    node="cluster" if entry.options.get("cluster_mode") else None,
)

The client will not close the session on exit when one is injected.

Endpoint namespaces

Namespace Wraps Examples
api.user /api/user/..., /api/sso/status login, check_for_update
api.dashboard /api/dashboard/... stats, metrics_json, get_top
api.zones /api/zones/... list, add_record, sign, get_dnssec_properties
api.cache /api/cache/... list, flush
api.allowed /api/allowed/... list, allow, import_zones
api.blocked /api/blocked/... list, block, import_zones
api.apps /api/apps/... list, install, set_config, list_dns_loggers
api.dns_client /api/dnsClient/... resolve
api.settings /api/settings/... get, set, temporary_disable_blocking, backup
api.dhcp /api/dhcp/... leases_list, scopes_list, set_scope
api.admin /api/admin/... list_users, cluster_state, cluster_init
api.logs /api/logs/... list, query, download, export

Exception model

All exceptions derive from TechnitiumError:

TechnitiumError
├── TransportError          - network/timeout/non-JSON errors
├── InvalidTokenError       - status=='invalid-token' or HTTP 401
├── TwoFactorRequiredError  - status=='2fa-required'
├── PermissionDeniedError   - HTTP 403
├── NotFoundError           - HTTP 404
└── ServerError             - HTTP 5xx

Every exception carries .status, .error_message, .stack_trace, .inner_error_message and the raw .response dict for debugging.

Live testing (optional)

Unit tests mock the Technitium API and run in CI. To exercise a real server locally:

cp .env.example .env   # fill in TECHNITIUM_TEST_URL and TECHNITIUM_TEST_TOKEN
export $(grep -v '^#' .env | xargs)

# Read-only pytest integration suite
pytest tests/test_live_integration.py -v -m integration

# Full endpoint sweep (manual script)
python tests/run_live_all_endpoints.py

Never commit .env or API tokens. For GitHub Actions, add TECHNITIUM_TEST_URL and TECHNITIUM_TEST_TOKEN as repository secrets and run the Live integration (manual) workflow.

Live test probes use the test user token, target user-managed zones (for example api-probe-test.local), and this-server for DNS Client resolve calls (external resolvers such as 1.1.1.1 are blocked by Technitium).

Contributing / branch protection

All changes to main must go through a pull request with passing CI. After creating the GitHub repository, apply the branch ruleset once — see .github/BRANCH_PROTECTION.md.

Releasing

CI (ci.yml) runs on every push/PR to main — it tests and builds but does not publish to PyPI.

Release (release.yml) runs only when you push a version tag:

git tag -a v0.1.0 -m "v0.1.0"
git push origin v0.1.0

That workflow re-runs lint/tests, builds the package, and publishes to PyPI via trusted publishing (after you configure the pending publisher on pypi.org).

To cut a new release:

  1. Bump the version in src/technitiumdns/_version.py.
  2. Update CHANGELOG.md with the new entry.
  3. Commit to main, then tag and push the tag as shown above.
  4. Watch the Release workflow in GitHub Actions.

For local testing of the build:

pip install build twine
python -m build
twine check dist/*

Versioning & license

Released under GPL-3.0-or-later to match the home-assistant-technitiumdns integration. Follows Semantic Versioning once the 1.0.0 stable line is published.

See CHANGELOG.md for release notes.

Metadata

Release files for technitiumdns-api 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for technitiumdns-api 0.1.1
File Size Uploaded Provenance
technitiumdns_api-0.1.1.tar.gz 44.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for technitiumdns-api 0.1.1
File Interpreter ABI Platform
technitiumdns_api-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 105.1 kB

Release files / technitiumdns_api-0.1.1.tar.gz

Download URL technitiumdns_api-0.1.1.tar.gz
Size 44.7 kB
Tags Source
SHA-256 checksum
How to use checksums
ddb9502636bc70bdcc23f72d6a205b2d0f1e39c4c7dcb4fab014b51e22c93ddd
BLAKE2b-256 checksum
How to use checksums
314b3dbb1dc64a9d3dd15ea8b57871f19c949cc7eb897a1a4e99240fc072d66e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 22, 2026.

Transparency log

Release files / technitiumdns_api-0.1.1-py3-none-any.whl

Download URL technitiumdns_api-0.1.1-py3-none-any.whl
Size 60.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6bf7cb78f956d65d006b57b90e3bef3ca43e283bc89349dca522aab9095a3f74
BLAKE2b-256 checksum
How to use checksums
0de815cb175fcbeeaecb3db83899f61f4fc4be929b6a18ba6139174c02644d85
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 22, 2026.

Transparency log

Security for technitiumdns-api 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for technitiumdns_api-0.1.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page