technitiumdns-api
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) andClient(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:
- Bump the version in src/technitiumdns/_version.py.
- Update CHANGELOG.md with the new entry.
- Commit to
main, then tag and push the tag as shown above. - 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)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| technitiumdns_api-0.1.1.tar.gz | 44.7 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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 logRelease 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 logSecurity 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.
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 logA 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.