Package contract
The generated source distribution is nordic-devhouse and the import package is nordic_devhouse. The generated transport uses httpx and targets a typed Python client.
Use the synchronous client
Use NordicDevhouse in scripts, workers, and synchronous web frameworks. A context manager closes pooled connections deterministically. The client may also be closed explicitly when its lifecycle is managed by a dependency container.
import os
from nordic_devhouse import NordicDevhouse
with NordicDevhouse(
api_key=os.environ["NORDIC_DEVHOUSE_API_KEY"],
timeout=30.0,
max_retries=3,
) as ndh:
result = ndh.scrape.run(
url="https://example.com/products",
formats=["json"],
idempotency_key="catalog-2026-08-11",
)
print(result.job_id, result.records, result.data)Use the asynchronous client
Use AsyncNordicDevhouse inside an existing event loop. It has the same resource and method names as the synchronous client; only network methods are awaited. Do not call the synchronous client from an async request handler.
import os
from nordic_devhouse import AsyncNordicDevhouse
async with AsyncNordicDevhouse(
api_key=os.environ["NORDIC_DEVHOUSE_API_KEY"]
) as ndh:
job = await ndh.scrape.start(
url="https://example.com/products",
formats=["json"],
)
result = await ndh.jobs.wait(job.id, timeout=600.0)Models and return values
Public responses are immutable, typed data models with snake_case attributes and from_dict/to_dict helpers. Unknown server fields remain available through model.extra so a minor API addition does not discard data.
| Model | Key attributes |
|---|---|
| Job | id, status, created_at, completed_at, progress, warnings |
| JobResult[T] | job_id, records, data, metadata, request_id |
| DatasetExport | id, status, format, download_url, expires_at |
| WebhookEvent[T] | id, type, created_at, data |
| RequestOptions | timeout, max_retries, idempotency_key |
Handle structured errors
All package failures extend NordicDevhouseError. Structured attributes are safe to log, but string representations redact credentials and signed payloads. Retryable errors expose retry_after when the server supplies it.
from nordic_devhouse import (
NordicDevhouseError,
RateLimitError,
ValidationError,
)
try:
result = ndh.scrape.run(url=url, formats=["json"])
except RateLimitError as error:
logger.warning("rate limited", extra={
"retry_after": error.retry_after,
"request_id": error.request_id,
})
except ValidationError as error:
logger.error("invalid request", extra={"issues": error.issues})
except NordicDevhouseError as error:
logger.exception("SDK request failed", extra={
"code": error.code,
"request_id": error.request_id,
})Verify the raw webhook body
Pass the exact request bytes before JSON parsing. Verification checks HMAC-SHA256 and timestamp tolerance, then returns a typed event. Framework adapters may extract the raw body, but the cryptographic verification stays in the SDK module. Persist the verified event ID to prevent duplicate processing.
event = ndh.webhooks.verify(
payload=request.body,
signature=request.headers["Nordic-Signature"],
secret=os.environ["NORDIC_WEBHOOK_SECRET"],
)
if event.type == "scrape.completed":
import_result(event.data.job_id)Lifecycle and concurrency rules
- 1Reuse one client per process or worker lifecycle
- 2A synchronous client supports normal worker concurrency; an async client is scoped to its event loop
- 3Context managers are the preferred cleanup interface
- 4Cancellation stops polling but does not cancel a server-side scraping job
- 5Expose custom HTTP transports only as a test seam, not as a second public networking interface