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

# Concurrency Patterns

> Run the synchronous DevHelm Python client concurrently using thread pools or asyncio.to_thread

The Python SDK exports a single, synchronous `Devhelm` client built on `httpx.Client`. There's no `AsyncDevhelm` as of v1.3.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.

<Note>
  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](https://github.com/devhelmhq/sdk-python/issues) — until then, the patterns below cover essentially every concurrent workload.
</Note>

## When to use concurrency

| Use case                                       | Recommended approach                   |
| ---------------------------------------------- | -------------------------------------- |
| Single script with a handful of calls          | Sync calls, no concurrency             |
| Bulk operations (many creates/updates)         | Thread pool (see below)                |
| FastAPI / async web handler                    | `asyncio.to_thread(...)` per call      |
| Background worker fanning out to many monitors | Thread 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.

```python theme={null}
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.results, m.id, limit=1): m
        for m in monitors
    }
    for future in as_completed(future_map):
        monitor = future_map[future]
        results = future.result()  # CursorPage[CheckResultDto]
        latest = results.data[0] if results.data else None
        status = f"{latest.response_time_ms}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:

```python theme={null}
import asyncio
import os
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

```python theme={null}
import os
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 with exponential delay (the exception carries `status`, `code`, and `request_id` — there's no `retry_after` attribute; see [error handling](/sdk/python/error-handling#retry-on-rate-limits-and-5xx)).
* Reuse a single `Devhelm` instance across threads — its connection pool amortizes TLS handshakes.

## Next steps

<CardGroup cols={2}>
  <Card title="Client reference" icon="book" href="/sdk/python/client-reference">
    Full method reference for all resources.
  </Card>

  <Card title="Error handling" icon="triangle-exclamation" href="/sdk/python/error-handling">
    Exception types and retry patterns.
  </Card>
</CardGroup>
