Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.devhelm.io/llms.txt

Use this file to discover all available pages before exploring further.

The Python SDK exports a single, synchronous Devhelm client built on httpx.Client. There’s no AsyncDevhelm in v0.4.0 — but because httpx releases the GIL for I/O, the sync client parallelizes well from a thread pool, which covers nearly every concurrent workload.
A native async client (AsyncDevhelm on httpx.AsyncClient) is on the roadmap. If you need true asyncio integration, file a request at github.com/devhelmhq/sdk-python/issues — until then, the patterns below cover essentially every concurrent workload.

When to use concurrency

Use caseRecommended approach
Single script with a handful of callsSync calls, no concurrency
Bulk operations (many creates/updates)Thread pool (see below)
FastAPI / async web handlerasyncio.to_thread(...) per call
Background worker fanning out to many monitorsThread pool with bounded max_workers

Pattern 1: Thread pool

concurrent.futures.ThreadPoolExecutor is the simplest way to parallelize SDK calls. Set max_workers to a modest number (8–16) to avoid hitting API rate limits.
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from devhelm import Devhelm

client = Devhelm(token=os.environ["DEVHELM_API_TOKEN"])

monitors = client.monitors.list()

with ThreadPoolExecutor(max_workers=8) as pool:
    future_map = {
        pool.submit(client.monitors.list_check_results, m.id, limit=1): m
        for m in monitors
    }
    for future in as_completed(future_map):
        monitor = future_map[future]
        results = future.result()
        latest = results.items[0] if results.items else None
        status = f"{latest.responseTimeMs}ms" if latest else "no data"
        print(f"{monitor.name}: {status}")
Devhelm is safe to share across threads — it wraps a single httpx.Client with a connection pool.

Pattern 2: asyncio.to_thread from async code

Inside async def handlers (FastAPI, Starlette, aiohttp, etc.), wrap each call in asyncio.to_thread to keep the event loop free:
import asyncio
from devhelm import Devhelm

client = Devhelm(token=os.environ["DEVHELM_API_TOKEN"])

async def fetch_monitor_async(monitor_id: str):
    return await asyncio.to_thread(client.monitors.get, monitor_id)

async def fetch_many(ids: list[str]):
    return await asyncio.gather(*(fetch_monitor_async(i) for i in ids))
Each call runs on the default thread executor, so concurrency scales with the executor’s thread count (defaults to min(32, os.cpu_count() + 4)).

Bulk creates with error isolation

from concurrent.futures import ThreadPoolExecutor
from devhelm import Devhelm, DevhelmError

client = Devhelm(token=os.environ["DEVHELM_API_TOKEN"])

configs = [
    {"name": "API Health", "type": "HTTP",
     "config": {"url": "https://api.example.com/health"},
     "frequencySeconds": 60},
    {"name": "Web Health", "type": "HTTP",
     "config": {"url": "https://example.com"},
     "frequencySeconds": 300},
]

def safe_create(cfg):
    try:
        return client.monitors.create(cfg)
    except DevhelmError as exc:
        return exc

with ThreadPoolExecutor(max_workers=4) as pool:
    for result in pool.map(safe_create, configs):
        if isinstance(result, DevhelmError):
            print(f"Failed: {result}")
        else:
            print(f"Created: {result.name} ({result.id})")

Rate-limiting tips

  • Cap max_workers to 8–16 by default. Higher concurrency rarely improves throughput against a rate-limited API.
  • Catch DevhelmRateLimitError and back off using e.retry_after.
  • Reuse a single Devhelm instance across threads — its connection pool amortizes TLS handshakes.

Next steps

Client reference

Full method reference for all resources.

Error handling

Exception types and retry patterns.