> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dmand.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Scale Dmand AI Enrichment with Batching and Rate Limits

> Scale Dmand AI enrichment by chunking NPIs into bulk batches, staying within the rate limit, and using queue workers with retry backoff.

When you embed Dmand AI in a product with many users, enrichment requests can spike. This page shows how to batch NPIs, stay within the default limits, and queue work so your integration stays reliable at scale.

## Know the limits

| Limit                 | Default |
| --------------------- | ------- |
| Requests per minute   | `100`   |
| NPIs per bulk request | `60`    |

## Chunking into batches

Instead of sending one NPI per request, group NPIs into batches of `60` and call the bulk endpoint. This reduces HTTP overhead and maximizes throughput.

```python theme={null}
import requests
import time

API_KEY = "YOUR_API_KEY"
BASE_URL = "https://ext-api.dmand.ai/api/v1"

def submit_bulk(npis, webhook_url=None, webhook_mode="batch", custom_data=None):
    """Submit a batch of up to 60 NPIs."""
    payload = {
        "npis": npis,
        "enrichment_email_type": "any",
    }
    if webhook_url:
        payload["webhook"] = {"url": webhook_url, "mode": webhook_mode}
    if custom_data:
        payload["custom_data"] = custom_data

    response = requests.post(
        f"{BASE_URL}/email/bulk",
        headers={
            "Authorization": f"Bearer {API_KEY}",
            "Content-Type": "application/json",
        },
        json=payload,
    )
    response.raise_for_status()
    return response.json()


def chunk_and_submit(all_npis, chunk_size=60):
    """Split a long list of NPIs into batches and submit each."""
    batch_ids = []
    request_ids = []
    for i in range(0, len(all_npis), chunk_size):
        chunk = all_npis[i : i + chunk_size]
        result = submit_bulk(
            npis=chunk,
            webhook_url="https://your-app.com/webhooks/dmand",
            webhook_mode="batch",
            custom_data={"job_id": f"job_{i // chunk_size}"},
        )
        batch_ids.append(result.get("batch_id"))
        for req in result.get("requests", []):
            request_ids.append(req["request_id"])
        # Stay under 100 req/min
        time.sleep(60 / 100)
    return batch_ids, request_ids


# Example: submit 500 NPIs in 5 batches
npis = ["1003158791"] * 500
batch_ids, request_ids = chunk_and_submit(npis)
print("Submitted batches:", batch_ids)
```

## Check credits before large jobs

Before you enqueue a large number of NPIs, verify you have enough credits:

```bash theme={null}
curl https://ext-api.dmand.ai/api/v1/credits \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Response:

```json theme={null}
{ "balance": 850, "total": 1000, "used": 150 }
```

Make sure your balance covers the batch: credits are held at submit for every NPI (up to `3` per NPI for `personal`) and refunded for those without an email. See [GET /credits](/api-reference/account/credits).

## Queue and worker pattern

At high volume, enqueue batches on your side and consume them with a worker pool. This isolates Dmand AI's limits from your application traffic and prevents data loss during traffic spikes.

Recommended worker behavior:

1. Read a batch from your queue.
2. Send the batch to Dmand AI with a unique `Idempotency-Key` for safe retries.
3. On HTTP 202, store the returned `batch_id` and `request_id` values, and mark the batch as submitted.
4. If the request is rejected for rate limiting, pause the worker briefly and re-queue the batch.
5. On 5xx errors, retry with exponential backoff (for example, 1s, 2s, 4s, 8s) up to a maximum number of attempts.

```python theme={null}
import random
import time

def send_with_backoff(request_fn, max_retries=5):
    for attempt in range(max_retries):
        response = request_fn()
        if response.status_code == 202:
            return response
        if response.status_code in (403, 422):
            response.raise_for_status()  # don't retry auth or validation errors
        time.sleep((2 ** attempt) + random.random())
    raise Exception("Max retries exceeded")
```

## Webhook mode for large jobs

For large background jobs, set `webhook.mode` to `batch` so you receive a single webhook when the entire batch completes. This reduces webhook traffic and simplifies bookkeeping. Use `real-time` only when you need per-NPI UI updates, such as in an interactive workflow.

## Live lookup latency

Dmand returns emails that were validated within the last `30` days. If no stored email exists, Dmand runs a live enrichment lookup, which can take up to about `15` minutes before timing out. Design your UI to show a pending state while live lookups are in progress, and rely on webhooks or polling to update the record when the result is ready.

## Credit planning

Credits are charged only for NPIs where an email is found. Holds for not-found or failed NPIs are refunded. Each found email costs `1` credit for `any`, `1` for `professional`, and `3` for `personal`. See [Credits](/general/credits) or contact [`hello@dmand.ai`](mailto:hello@dmand.ai) for plan details.

* Fill batches close to `60` NPIs when possible.
* Put shared context (tenant, job ID) in `custom_data`. Results come back per NPI, so you can split them by `npi`.
* Use webhooks rather than polling so you don't spend quota checking status.
* Make your webhook handler idempotent by deduplicating on `request_id`, because Dmand retries delivery when your endpoint returns a non-2xx response.
