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

# Phone numbers and limits

> List your Blujay phone numbers, read their live sending limits and queue, and handle the limit errors a send can return.

Every message goes out from one of your Blujay phone numbers. `GET /v1/phone-numbers` tells you which numbers you have, whether each one can send right now, how full its queue is, and how much of its sending limits it has used. This page also explains the limits themselves: how many new contacts a number can message a day, how big its queue is, and the guard against messaging one person too often.

Request limits on the API itself (reads and writes per minute) are on [Rate limits](/rate-limits.md). The rules about what a message can contain before a contact replies are on [Sending rules](/messaging/sending-rules.md).

## List your numbers

`GET /v1/phone-numbers` returns the active numbers in your workspace, oldest first.

```bash curl theme={null}
curl https://api.blujay.io/v1/phone-numbers \
  -H "Authorization: Bearer blu_your_api_key"
```

```json Response (200) theme={null}
{
  "status": "success",
  "data": {
    "phone_numbers": [
      {
        "phone_number_id": "phone_abc123",
        "display_number": "+15555550100",
        "label": "Support",
        "status": "active",
        "line_type": "outbound",
        "access_type": "dedicated",
        "send_status": { "allowed": true },
        "health_status": {
          "status": "healthy",
          "score": 96,
          "as_of": "2026-10-14T17:00:00.000Z",
          "freshness": "fresh"
        },
        "limits": {
          "new_contacts": {
            "used_today": 12,
            "limit_per_day": 50,
            "remaining_today": 38,
            "reset_at": "2026-10-15T07:00:00.000Z"
          },
          "daily": {
            "used_today": 140,
            "guideline": 250,
            "remaining_to_guideline": 110,
            "enforced": false
          }
        },
        "queue": {
          "pending": 3,
          "capacity": 1500,
          "remaining": 1497
        },
        "capabilities": {
          "messaging": {
            "outbound": true,
            "inbound": true,
            "media_attachments": true,
            "link_parts": true
          },
          "voice": {
            "outbound_ready": true,
            "inbound_forwarding_ready": true
          }
        },
        "outbound_policy": {
          "inbound_first": false,
          "first_message_links_allowed": false,
          "follow_up_links_allowed": true,
          "daily_total_enforced": false
        }
      }
    ]
  },
  "trace_id": "trc_5b7d9f1a3c2e",
  "timestamp": "2026-10-14T17:00:01.000Z"
}
```

The `limits` and `queue` values above are illustrative. They differ between numbers and change over time, so read them from your own responses.

To read one number, use `GET /v1/phone-numbers/{phone_number_id}`. It also returns numbers that are no longer active, with `status: "unavailable"`, so you can still look up a number that older chats used. An unavailable number doesn't report current health, limits, queue or label.

## Understand the fields

| Field | What it tells you |
| - | - |
| `phone_number_id` | The ID to use as `from_phone_number_id` and in webhook filters. `POST /v1/chats` is the exception: it takes the `display_number` string as `from`. |
| `display_number` | The number in E.164. |
| `status` | `active`, or `unavailable` when the number can't be used. |
| `line_type` | Always `outbound` today. |
| `access_type` | `dedicated` for a number that is yours alone (Dedicated and AI Agent plans), or `shared` for a Free Plan number. See [Plans](/plans.md). |
| `send_status` | Whether the number can send right now. When `allowed` is `false`, `reason_code` and `reason` say why: `sender_retired` (the number was replaced; send from an active number), `number_inactive` or `number_setup_incomplete`. |
| `health_status` | Your own numbers only. `status` is `healthy`, `degraded`, `critical` or `unknown`, with an optional `score` from 0 to 100, the time it was scored (`as_of`) and a `freshness` hint. |
| `limits` | Your own numbers only. Live sending limits and usage. See [Sending limits](#sending-limits). |
| `queue` | Your own numbers only. Messages waiting to send from this number: `pending`, the queue's `capacity`, and how many more it can take (`remaining`). |
| `capabilities.messaging` | Whether the number can send, receive, send media and send link parts. |
| `capabilities.voice` | Your own numbers only. Whether the number is ready for calls: `outbound_ready` for outgoing calls and `inbound_forwarding_ready` for incoming calls. The API has no calling endpoints. |
| `outbound_policy` | Who the number can message, and link rules. `inbound_first` is `true` when the number can only message people who texted it first (Free Plan and AI Agent numbers); skip those when you start conversations. `first_message_links_allowed` is always `false`: a chat's first message can't contain a link. Follow-ups can, once the contact has replied. `daily_total_enforced` is always `false`, the same fact as `limits.daily.enforced`. |

Free Plan numbers leave out `health_status`, `limits`, `queue` and `capabilities.voice`. A missing field there doesn't mean the number can't send; use `send_status`.

> [!WARNING]
>
> **Common mistake:** `from` in `POST /v1/chats` is the `display_number` string (`"+15555550100"`), not the `phone_number_id`. Every other endpoint that names a number takes `from_phone_number_id` (`"phone_abc123"`).

## Sending limits

Sending limits protect each number's reputation, so its messages keep arriving. They apply to each phone number, not to your API key. The values below are today's typical values; the live `limits` and `queue` fields on your number are the source of truth, and sending limits can change at any time. There is no hourly limit on texts.

| Field | Meaning |
| - | - |
| `limits.new_contacts` | People this number has messaged for the first time today: `used_today`, `limit_per_day`, `remaining_today`, and `reset_at`, when the count resets. |
| `limits.daily` | Messages sent today against a recommended daily total: `used_today`, `guideline` and `remaining_to_guideline`. `enforced` is `false`, so the guideline is advice and Blujay doesn't reject sends because of it. |
| `queue` | `pending` messages waiting to send, the queue's `capacity`, and how many more it can take (`remaining`). |

### New contacts per day

A **new contact** is someone this number has never texted before. Each Dedicated number can message up to 50 new contacts a day once it is established. A new number, or one that was recently reassigned, may start lower and reaches the full limit over about its first month. A number's age is counted from the day it was created in Blujay; moving it to another workspace doesn't reset it.

* People who texted you first never count, and neither do follow-ups in a chat you already have.
* In a new group chat, each participant the number hasn't texted before counts.
* Over the limit, a send returns `429` [`RATE_LIMIT_EXCEEDED`](/errors/rate-limit-exceeded) with `limit_type: new_contact_daily`. Nothing is queued.

### Queue

Each number's send queue has room for 1,500 pending messages. When it is full, a send returns `429` [`QUEUE_FULL`](/errors/queue-full) with `limit_type: phone_number_queue`. Wait for `Retry-After` while queued messages go out, then retry with the same `Idempotency-Key`.

### Texts to the same person

Each number can send at most 30 messages a minute to the same person (`limit_type: recipient_loop`). It's there to stop a runaway loop in your code from flooding someone, and failed attempts count toward it too. Normal conversations never come close.

### Free Plan numbers

A Free Plan number can message only [verified contacts](/plans.md): people who texted it first and were verified in the app, up to 10 of them. The new-contact limit never applies to it, and `GET /v1/phone-numbers` doesn't report `limits` or `queue` for it. Sending to anyone else returns `403` [`CONTACT_NOT_VERIFIED`](/errors/contact-not-verified). The [sending rules](/messaging/sending-rules.md) and the same-person guard still apply.

### When limits reset

The new-contact count resets at midnight in your workspace's time zone. The API doesn't expose that time zone, so don't compute the reset yourself: read `limits.new_contacts.reset_at` on the number, or `error.details[0].reset_at` on a `429`, and wait until then. The same-person guard and the queue clear on their own within minutes; their `reset_at` says when.

### Texts in a row without a response

A separate rule limits how many texts go to a contact without a response, and what a message can contain before they reply. Those return `400` errors, not `429`, and aren't retryable. See [Sending rules](/messaging/sending-rules.md).

## Number health

Your own numbers report `health_status`: a `score` from 0 to 100 and a `status` band.

| `status` | `score` | What to do |
| - | - | - |
| `healthy` | 70 to 100 | Send normally. |
| `degraded` | 50 to 69 | Start fewer new conversations from this number, and focus on people who reply. |
| `critical` | 0 to 49 | Stop starting new conversations from this number. Keep answering people who message you, and contact [support@blujay.io](mailto:support@blujay.io). |
| `unknown` | none | Blujay doesn't have a recent score for the number yet. |

The score reflects how your conversations go: how often people reply, how long conversations last, how quickly you answer, how many conversations people start with you, how close you run to the recommended daily volume, and whether your messages look templated. See [Sending best practices](/best-practices.md). Read `health_status` as part of your preflight check and before a large batch.

## Check a number before you send

Before you start a batch, read the number and stop when it has no room. Treat any of these as a stop signal:

* `send_status.allowed` is `false`.
* `queue.remaining` is `0`.
* `limits.new_contacts.remaining_today` is `0` and you're about to message someone for the first time.

```javascript Node.js theme={null}
const response = await fetch("https://api.blujay.io/v1/phone-numbers/phone_abc123", {
  headers: { Authorization: "Bearer blu_your_api_key" },
});
const body = await response.json();
if (body.status === "error") {
  throw new Error(`${body.error.code}: ${body.error.message} (trace ${body.trace_id})`);
}
const number = body.data;

function canSend(number, { newContact }) {
  if (!number.send_status.allowed) return false;
  if (!number.limits) return true; // Free Plan numbers don't report limits.
  if (number.queue.remaining === 0) return false;
  if (newContact && number.limits.new_contacts.remaining_today === 0) return false;
  return true;
}

console.log(canSend(number, { newContact: true }));
```

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

response = requests.get(
    "https://api.blujay.io/v1/phone-numbers/phone_abc123",
    headers={"Authorization": "Bearer blu_your_api_key"},
    timeout=30,
)
body = response.json()
if body["status"] == "error":
    raise RuntimeError(f"{body['error']['code']}: {body['error']['message']} (trace {body['trace_id']})")
number = body["data"]


def can_send(number, new_contact):
    if not number["send_status"]["allowed"]:
        return False
    limits = number.get("limits")
    if limits is None:  # Free Plan numbers don't report limits.
        return True
    if number["queue"]["remaining"] == 0:
        return False
    if new_contact and limits["new_contacts"]["remaining_today"] == 0:
        return False
    return True


print(can_send(number, new_contact=True))
```

The check is a guide, not a reservation. Other sends can use up room between your check and your send, so still handle the errors below.

## Handle limit errors on sends

When a send would go over a number's limit, `POST /v1/chats` and `POST /v1/chats/{chat_id}/messages` return `429` before anything is queued. Wait for the `Retry-After` header (also `error.retry_after`), then retry with the same `Idempotency-Key`, or send from another number.

| Error | `details[0].limit_type` | Meaning |
| - | - | - |
| [`RATE_LIMIT_EXCEEDED`](/errors/rate-limit-exceeded) | `new_contact_daily` | The number has reached today's limit for messaging new contacts. |
| [`RATE_LIMIT_EXCEEDED`](/errors/rate-limit-exceeded) | `recipient_loop` | Too many messages to the same person in a short time. This guards against runaway loops. |
| [`RATE_LIMIT_EXCEEDED`](/errors/rate-limit-exceeded) | `phone_number_sendability` | The number can't send right now. Check its `send_status`. |
| [`QUEUE_FULL`](/errors/queue-full) | `phone_number_queue` | The number's queue is full. Wait for queued messages to send. |

Each entry in `details` also has `scope: "phone_number"`, the `phone_number_id`, `limit`, `used`, `remaining` and `reset_at`:

```json theme={null}
{
  "status": "error",
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "New-contact limit reached for this phone number",
    "type": "rate_limit",
    "http_status": 429,
    "retryable": true,
    "doc_url": "https://docs.blujay.io/errors/rate-limit-exceeded",
    "details": [
      {
        "limit_type": "new_contact_daily",
        "scope": "phone_number",
        "phone_number_id": "phone_abc123",
        "limit": 50,
        "used": 50,
        "remaining": 0,
        "reset_at": "2026-10-15T07:00:00.000Z"
      }
    ],
    "retry_after": 43140
  },
  "trace_id": "trc_6b2d9e0f1a3c",
  "timestamp": "2026-10-14T17:01:00.000Z"
}
```

`RATE_LIMIT_EXCEEDED` can also mean you're making API requests too quickly. In that case `details[0].limit_type` is `public_api_read`, `public_api_write` or `public_api_media`, and `scope` is `api_key`. See [Rate limits](/rate-limits.md).

## Common questions

> **Is there an hourly limit?**
>
> No. Numbers have a daily limit on new contacts, a limit on texts in a row without a response, and a per-minute guard on texts to the same person.

> **How do I send more?**
>
> Spread new conversations over the day and across your numbers. A new number's new-contact limit grows over about its first month, and people who text you first don't use it up. For more volume, add numbers.

> **Do texts I receive count?**
>
> No. Only texts you send count. A reply from the contact restarts the count of texts in a row.

> **Does a rejected send use up a limit?**
>
> A `429` or `400` queues nothing and doesn't use up the new-contact limit. Failed attempts do count toward the same-person guard.

> [!TIP]
>
> For a large send, spread messages over the day and across your numbers, and stop when `remaining_today` or `queue.remaining` reaches zero. Steady batches deliver better than bursts.

## Limitations

* A Free Plan number reports no `health_status`, `limits`, `queue` or `capabilities.voice`, and can message only verified contacts ([`CONTACT_NOT_VERIFIED`](/errors/contact-not-verified)).
* An AI Agent number only replies to people who text it first ([`CONTACT_MUST_TEXT_FIRST`](/errors/contact-must-text-first)), and it can't create group chats or send to them. The phone number object doesn't say which plan a number is on. See [Plans](/plans.md).
* A number that was replaced reports `send_status.reason_code: sender_retired`; a send from it returns [`SENDER_RETIRED`](/errors/sender-retired). Switch to the new number's `phone_number_id`.
* There is no webhook when a number's health changes or it is suspended ([`SENDER_SUSPENDED`](/errors/sender-suspended)). [`phone_number.status_updated`](/webhooks/events#phone-numbers) fires only when a number is replaced.
* The workspace time zone behind `reset_at` isn't exposed.

## Next steps

> **[Send messages](/messaging/send-messages.md)**
>
> Start a chat from one of your numbers.

> **[Sending rules](/messaging/sending-rules.md)**
>
> What a message can contain before and after a reply.

> **[Rate limits](/rate-limits.md)**
>
> API request limits and how to back off.

> **[iMessage, RCS or SMS](/messaging/choosing-a-service.md)**
>
> Check whether a number reaches someone over iMessage, RCS or SMS.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.