Skip to documentation content

Python SDK

Synchronous and asynchronous clients with the same resource model.

In this guide
  1. 1Choose sync or async
  2. 2Reuse the client
  3. 3Handle structured errors
  4. 4Verify raw webhook bodies
In this guide
  1. 1Choose sync or async
  2. 2Reuse the client
  3. 3Handle structured errors
  4. 4Verify raw webhook bodies

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.

Python
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.

Python
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.

ModelKey attributes
Jobid, status, created_at, completed_at, progress, warnings
JobResult[T]job_id, records, data, metadata, request_id
DatasetExportid, status, format, download_url, expires_at
WebhookEvent[T]id, type, created_at, data
RequestOptionstimeout, 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.

Python
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.

Python
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

  1. 1
    Reuse one client per process or worker lifecycle
  2. 2
    A synchronous client supports normal worker concurrency; an async client is scoped to its event loop
  3. 3
    Context managers are the preferred cleanup interface
  4. 4
    Cancellation stops polling but does not cancel a server-side scraping job
  5. 5
    Expose custom HTTP transports only as a test seam, not as a second public networking interface
Was this page helpful?