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

# Checking numbers before you send

> Why it saves money and protects your number, and how to pace a large list.

Before sending to a list, ask which of those numbers are actually on WhatsApp. It is one call, it sends nothing, and it is the single cheapest thing you can do to protect a WhatsApp number.

```bash theme={null}
curl -X POST https://api.bulkneo.com/v1/numbers/check \
  -H "apikey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "instance_id": "YOUR_INSTANCE_ID",
    "numbers": ["919876543210", "919812345678"]
  }'
```

## Why bother

<CardGroup cols={2}>
  <Card title="It saves your allowance" icon="wallet">
    A message to a number that is not on WhatsApp still counts as a send. Checks do not: they never touch your usage figures and never consume a trial allowance.
  </Card>

  <Card title="It protects your number" icon="shield">
    Sending repeatedly to numbers that are not on WhatsApp is one of the patterns that gets a number flagged or restricted. A restricted number stops sending to *everyone*, not just the bad entries.
  </Card>

  <Card title="It catches typos" icon="magnifying-glass">
    Missing country codes, transposed digits, a stray leading zero. Better found in a lookup than in a failed batch.
  </Card>

  <Card title="It finds the right number" icon="arrows-left-right">
    Some countries answer WhatsApp on a slightly different number than the one dialled. The response tells you which one to actually use.
  </Card>
</CardGroup>

## Reading the answer

```json theme={null}
{
  "success": true,
  "data": {
    "instance_id": "3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34",
    "submitted": 3,
    "checked": 2,
    "results": [
      { "number": "919876543210", "exists": true,  "whatsapp_number": "919876543210" },
      { "number": "919812345678", "exists": false, "whatsapp_number": null }
    ]
  },
  "request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}
```

* **`submitted` vs `checked`** — duplicates are collapsed before the lookup, so a sloppy list does not burn your batch allowance. Here 3 numbers were sent but only 2 were distinct.
* **Order is preserved** — results come back in the order you submitted them.

### The three values of `exists`

| Value   | Meaning                                              | What to do                    |
| ------- | ---------------------------------------------------- | ----------------------------- |
| `true`  | On WhatsApp                                          | Send.                         |
| `false` | Not on WhatsApp                                      | Skip. Reach them another way. |
| `null`  | **The check could not be completed for that number** | Retry later, or send anyway.  |

<Warning>
  `null` is **not** a "no". Treating it as `false` will make you skip real customers. "We could not determine this" and "this person is not on WhatsApp" lead to opposite decisions, which is exactly why they are different values.
</Warning>

### `whatsapp_number`

When it differs from `number`, **send to `whatsapp_number`**. Brazil's extra 9th digit is the classic case: you store one number, WhatsApp answers on another.

```javascript theme={null}
const target = result.whatsapp_number ?? result.number;
```

## Pacing a large list

The batch limit is **50 numbers per request**. That is a cap on one request, not a safety guarantee — bulk number-checking looks exactly like a scraping tool, and doing 50 at a time in a tight loop is a fast route to a flagged number.

<Warning>
  The 50-per-request limit and your per-minute rate limit multiply. Just because you *can* check thousands of numbers an hour does not mean your WhatsApp number will survive it.
</Warning>

**Do this instead:**

<Steps>
  <Step title="Check once, store the result">
    Whether a number is on WhatsApp changes rarely. Cache it against the contact record and re-check every few months, not before every send.
  </Step>

  <Step title="Check new contacts as they arrive">
    One number at the moment someone signs up is invisible load. Ten thousand numbers on a Monday morning is a pattern.
  </Step>

  <Step title="Spread a bulk clean-up over hours">
    If you must validate an existing database, do it in small batches with real gaps between them — and preferably overnight, over several days.
  </Step>

  <Step title="Never loop checks and sends together at full speed">
    Check the batch, pause, then send with your normal pacing.
  </Step>
</Steps>

```python Python — paced batches theme={null}
import os, time, requests

API = "https://api.bulkneo.com/v1/numbers/check"
HEADERS = {"apikey": os.environ["BULKNEO_API_KEY"]}
INSTANCE = os.environ["BULKNEO_INSTANCE_ID"]

def check_all(numbers, batch_size=50, pause_seconds=10):
    out = []
    for i in range(0, len(numbers), batch_size):
        batch = numbers[i : i + batch_size]
        res = requests.post(
            API,
            headers=HEADERS,
            json={"instance_id": INSTANCE, "numbers": batch},
            timeout=30,
        )
        res.raise_for_status()
        out.extend(res.json()["data"]["results"])
        time.sleep(pause_seconds)   # deliberate gap, not an afterthought
    return out
```

## What it costs

* **Not a message.** Checks do not appear in your usage figures and do not consume a trial message allowance.
* **Does count against your per-minute rate limit**, shared with sending. See [Rate limits](/errors#rate-limits).
* **Needs a connected number.** The lookup runs through one of your own connected numbers, so `instance_id` must point at one that is online — otherwise you get `409 instance_not_connected`.
