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

# Check iMessage availability

> Look up whether an address can receive iMessage before you send, one at a time or up to 100 per call.

## Overview

`POST /check-imessage` tells you whether a single address can receive an iMessage, before you spend a send on it. You pass one phone number or email address. You get back two booleans: `imessage` and `rcs`.

`POST /check-imessage-bulk` does the same thing for up to 100 addresses in one call and returns a result for each one.

Both endpoints are read only. Nothing is sent to the address, and the person on the other end sees nothing. They use `POST` instead of `GET` so contact data stays in the request body and never lands in a URL, a proxy log, or a browser history.

Base URL for every BlueReacher endpoint:

```
https://api.bluereacher.com/functions/v1/
```

## Why check before you send

Checking first is the difference between a clean campaign and a broken one.

* **You stop burning sends.** An iMessage aimed at an Android number cannot be delivered as an iMessage. Check first, and you route that contact to RCS or SMS instead of watching the send fail.
* **Your reply rate stays honest.** Undeliverable addresses sit in your denominator and drag every campaign metric down. Filter them out before launch and your numbers describe real conversations.
* **Your line stays healthy.** Repeatedly pushing traffic at addresses that cannot receive it is a bad pattern. BlueReacher gives you dedicated iMessage lines, managed for you, with no A2P registration required, and the fastest way to protect that line is to only message people who can actually receive the message.
* **Your list stays current.** People switch phones. A number that was on iMessage in March can be on Android in July. A bulk check the morning of a send catches that.
* **Checks are free.** Availability checks are not billed as messages and do not count against your message volume. Only rate limits apply.

The practical pattern: run `check-imessage-bulk` over your list right before a campaign, split the results into iMessage, RCS, and SMS groups, then send each group on the channel it can receive.

## Authentication

Every request needs your API key in the `Authorization` header and a JSON content type:

```
Authorization: Bearer bluereacher_your_api_key
Content-Type: application/json
```

Keys are workspace scoped and are created in your BlueReacher dashboard. Call these endpoints from your server. A key shipped in browser or mobile code can be read by anyone who opens the network tab.

***

## Check one address

**`POST https://api.bluereacher.com/functions/v1/check-imessage`**

Looks up a single address and returns its channel availability.

### Request fields

| Field | Type   | Required | Description                                                                                                                                                                                                    |
| ----- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `to`  | string | Yes      | The address to check. A phone number in E.164 format (`+15551234567`) or an email address used as an Apple ID (`ana@example.com`). Numbers without a `+` and country code are rejected with `invalid_address`. |

No other fields are accepted. Unknown fields in the body are ignored.

### Example request

```bash theme={null}
curl -X POST https://api.bluereacher.com/functions/v1/check-imessage \
  -H "Authorization: Bearer bluereacher_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+15551234567"
  }'
```

### Response fields

| Field        | Type    | Description                                                                                                                                                                     |
| ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address`    | string  | The address that was checked, normalized. Phone numbers come back in E.164. Emails come back lowercased.                                                                        |
| `imessage`   | boolean | `true` if the address can receive an iMessage. `false` if it cannot.                                                                                                            |
| `rcs`        | boolean | `true` if the address can receive RCS. Always `false` for email addresses.                                                                                                      |
| `checked_at` | string  | ISO 8601 UTC timestamp of when this result was determined. If it is older than your request, you received a cached result. See [Freshness and caching](#freshness-and-caching). |

Successful lookups return `200 OK`.

### Example response

```json theme={null}
{
  "address": "+15551234567",
  "imessage": true,
  "rcs": false,
  "checked_at": "2026-07-24T16:42:09Z"
}
```

A number that is not on iMessage but can take RCS:

```json theme={null}
{
  "address": "+15559876543",
  "imessage": false,
  "rcs": true,
  "checked_at": "2026-07-24T16:42:11Z"
}
```

### Error notes

`400` with `invalid_address` is the one you will hit most. It almost always means the number was not in E.164. Normalize before you call: strip spaces, parentheses, and dashes, then add the country code. Full list in [Errors](#errors).

***

## Check up to 100 addresses

**`POST https://api.bluereacher.com/functions/v1/check-imessage-bulk`**

Checks a batch of addresses in one call. Use this for list hygiene and pre-campaign segmentation. It is roughly ten times more throughput per minute than looping the single endpoint.

### Request fields

| Field       | Type             | Required | Description                                                                                                                                                                       |
| ----------- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `addresses` | array of strings | Yes      | The addresses to check. Same format rules as `to`. Minimum 1, maximum 100 per call. An empty array returns `400 invalid_request`. More than 100 returns `400 too_many_addresses`. |

Duplicates are collapsed and checked once. Results come back in the order each address first appears in your array, so you can zip them against your input after deduping.

### Example request

```bash theme={null}
curl -X POST https://api.bluereacher.com/functions/v1/check-imessage-bulk \
  -H "Authorization: Bearer bluereacher_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "addresses": [
      "+15551234567",
      "+15559876543",
      "ana@example.com",
      "5551112222"
    ]
  }'
```

### Response fields

| Field                  | Type             | Description                                                                                                          |
| ---------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------- |
| `count`                | integer          | Number of results returned. Equals the number of unique addresses in your request.                                   |
| `results`              | array of objects | One object per unique address, in first-appearance order.                                                            |
| `results[].address`    | string           | The address that was checked, normalized. Addresses that failed validation are echoed back exactly as you sent them. |
| `results[].imessage`   | boolean or null  | `true` or `false` for a successful lookup. `null` when that address has an `error`.                                  |
| `results[].rcs`        | boolean or null  | `true` or `false` for a successful lookup. `null` when that address has an `error`.                                  |
| `results[].checked_at` | string or null   | ISO 8601 UTC timestamp of the result. `null` when that address has an `error`.                                       |
| `results[].error`      | object or null   | Present only when this address could not be checked. Contains `code` and `message`.                                  |

A well formed request returns `200 OK` even when some addresses fail. Per-address problems live inside `results`, not in the HTTP status. Check `results[].error` on every item.

Per-address error codes:

| Code              | Meaning                                                    | What to do                                           |
| ----------------- | ---------------------------------------------------------- | ---------------------------------------------------- |
| `invalid_address` | Not a valid E.164 number or email address.                 | Fix the format in your list. Retrying will not help. |
| `lookup_failed`   | The availability lookup did not complete for this address. | Retry this address on its own after a few seconds.   |

### Example response

```json theme={null}
{
  "count": 4,
  "results": [
    {
      "address": "+15551234567",
      "imessage": true,
      "rcs": false,
      "checked_at": "2026-07-24T16:44:02Z",
      "error": null
    },
    {
      "address": "+15559876543",
      "imessage": false,
      "rcs": true,
      "checked_at": "2026-07-24T16:44:02Z",
      "error": null
    },
    {
      "address": "ana@example.com",
      "imessage": true,
      "rcs": false,
      "checked_at": "2026-07-24T16:44:02Z",
      "error": null
    },
    {
      "address": "5551112222",
      "imessage": null,
      "rcs": null,
      "checked_at": null,
      "error": {
        "code": "invalid_address",
        "message": "Phone numbers must be in E.164 format, for example +15551112222."
      }
    }
  ]
}
```

### Error notes

The whole call fails with a `4xx` only when the request itself is wrong: bad JSON, missing `addresses`, an empty array, or more than 100 entries. Anything address-specific comes back inside `results` with a `200`.

### Splitting a larger list

Lists longer than 100 have to be chunked. Both examples below also handle a `429` by waiting the number of seconds in the `Retry-After` header, then retrying the same chunk.

```javascript theme={null}
const URL = "https://api.bluereacher.com/functions/v1/check-imessage-bulk";
const HEADERS = {
  "Authorization": "Bearer bluereacher_your_api_key",
  "Content-Type": "application/json"
};

async function checkAll(addresses) {
  const results = [];

  for (let i = 0; i < addresses.length; i += 100) {
    const chunk = addresses.slice(i, i + 100);
    const res = await fetch(URL, {
      method: "POST",
      headers: HEADERS,
      body: JSON.stringify({ addresses: chunk })
    });

    if (res.status === 429) {
      const wait = Number(res.headers.get("Retry-After") || 5);
      await new Promise(r => setTimeout(r, wait * 1000));
      i -= 100; // step back so the loop retries this same chunk
      continue;
    }

    if (!res.ok) {
      throw new Error(`check-imessage-bulk failed: ${res.status}`);
    }

    const data = await res.json();
    results.push(...data.results);
  }

  return results;
}

// Split the results into send groups
const checked = await checkAll(myList);
const imessage = checked.filter(r => r.imessage === true);
const rcs = checked.filter(r => r.imessage === false && r.rcs === true);
const sms = checked.filter(r => r.imessage === false && r.rcs === false);
```

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

URL = "https://api.bluereacher.com/functions/v1/check-imessage-bulk"
HEADERS = {
    "Authorization": "Bearer bluereacher_your_api_key",
    "Content-Type": "application/json",
}

def check_all(addresses):
    results = []

    for i in range(0, len(addresses), 100):
        chunk = addresses[i:i + 100]

        while True:
            r = requests.post(URL, headers=HEADERS, json={"addresses": chunk})

            if r.status_code == 429:
                time.sleep(int(r.headers.get("Retry-After", 5)))
                continue

            r.raise_for_status()
            results.extend(r.json()["results"])
            break

    return results
```

***

## Picking a channel from the result

The `channel` field on a send accepts `"imessage"`, `"rcs"`, or `"sms"`. Map the check straight onto it:

| `imessage` | `rcs`    | Send with    |
| ---------- | -------- | ------------ |
| `true`     | anything | `"imessage"` |
| `false`    | `true`   | `"rcs"`      |
| `false`    | `false`  | `"sms"`      |

Skip any result where `error` is not `null`. You do not know that contact's availability, so do not guess a channel for it.

## Freshness and caching

Results are cached for 24 hours per address. A repeat check inside that window returns the stored result, and `checked_at` shows the original lookup time rather than the time of your call. Compare `checked_at` to now if you need to know how fresh a result is.

Cached responses still count against your rate limit.

Two habits that keep this accurate:

1. Check within 24 hours of sending, not weeks ahead. Availability changes when people change phones.
2. Store `imessage`, `rcs`, and `checked_at` on your contact record. Re-check anything older than 24 hours instead of trusting a stale flag.

## Rate limits

Limits are per API key, counted across your whole workspace, over a rolling 60 second window.

| Endpoint                    | Limit                   | Addresses per minute |
| --------------------------- | ----------------------- | -------------------- |
| `POST /check-imessage`      | 600 requests per minute | 600                  |
| `POST /check-imessage-bulk` | 60 requests per minute  | 6,000                |

Batching is the cheaper path by a wide margin. Use `check-imessage-bulk` for anything over a handful of addresses and save the single endpoint for real time lookups, like a form submission or a CRM record opening.

Every response carries the current state of your limit:

| Header                  | Description                                            |
| ----------------------- | ------------------------------------------------------ |
| `X-RateLimit-Limit`     | Requests allowed in the window for this endpoint.      |
| `X-RateLimit-Remaining` | Requests left in the current window.                   |
| `X-RateLimit-Reset`     | Unix timestamp in seconds when the window resets.      |
| `Retry-After`           | Seconds to wait before retrying. Sent only on a `429`. |

Going over returns `429 Too Many Requests`:

```json theme={null}
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded for check-imessage-bulk. Retry after 12 seconds."
  }
}
```

Wait the number of seconds in `Retry-After`, then retry the same request. Both endpoints are read only and have no side effects, so retrying is always safe. If you are running a large list check on a schedule, watch `X-RateLimit-Remaining` and slow down before you hit zero rather than driving into `429`s.

## Errors

Errors use one shape across the whole API:

```json theme={null}
{
  "error": {
    "code": "invalid_address",
    "message": "Phone numbers must be in E.164 format, for example +15551234567."
  }
}
```

| Status | Code                     | Meaning                                                                      | Fix                                                                                                    |
| ------ | ------------------------ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `400`  | `invalid_request`        | Body is not valid JSON, `to` is missing, or `addresses` is missing or empty. | Check the payload against the request field table above.                                               |
| `400`  | `invalid_address`        | `to` is not a valid E.164 number or email address.                           | Normalize the address. On the bulk endpoint this appears per address inside `results`, not as a `400`. |
| `400`  | `too_many_addresses`     | More than 100 entries in `addresses`.                                        | Split into chunks of 100.                                                                              |
| `401`  | `unauthorized`           | API key missing, malformed, or revoked.                                      | Send `Authorization: Bearer bluereacher_your_api_key` with a current key.                              |
| `403`  | `account_inactive`       | Key is valid but the workspace is suspended or not yet provisioned.          | Contact BlueReacher support.                                                                           |
| `405`  | `method_not_allowed`     | Request used a method other than `POST`.                                     | Use `POST`.                                                                                            |
| `415`  | `unsupported_media_type` | `Content-Type` was not `application/json`.                                   | Set the header.                                                                                        |
| `429`  | `rate_limited`           | Rate limit exceeded.                                                         | Wait `Retry-After` seconds and retry.                                                                  |
| `500`  | `internal_error`         | Something broke on our side.                                                 | Retry once after a short backoff. If it persists, contact support.                                     |
| `503`  | `lookup_unavailable`     | The availability lookup is temporarily unavailable.                          | Retry with exponential backoff. Do not treat this as `imessage: false`.                                |

One rule worth writing into your code: never read an error as a negative result. `imessage: false` means the address cannot receive iMessage. An error means you do not know yet. Retry or hold the contact back instead of downgrading them to SMS on bad information.
