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

# Safe Mode

> Make a number send the way a person does with presence, typing indicators, read receipts, and pacing, and see exactly what your contacts see.

Safe Mode makes a number behave the way a person using WhatsApp behaves: it can
go online while it sends, show a typing indicator before a message, mark
incoming messages read before answering them, and leave human-shaped gaps
between sends.

Safe Mode is a BanSafe Lite feature, included in BanSafe Lite (Standard and
Pro). Every setting is off by default on every number, existing and new, and
only an explicit request turns one on.

<Warning>
  Presence and read receipts are visible to the people you message and cannot be
  taken back. Read [What your contacts see](#what-your-contacts-see) before you
  turn either on.
</Warning>

## The four settings

| Setting    | Values                                         | Default |
| ---------- | ---------------------------------------------- | ------- |
| `presence` | `dark`, `online_while_sending`, `online_hours` | `dark`  |
| `typing`   | `off`, `before_text`, `before_all`             | `off`   |
| `reads`    | `off`, `replied_chats`, `all_inbound`          | `off`   |
| `pacing`   | `off`, `jittered`, `conversation`              | `off`   |

### Presence

| Value                  | Behaviour                                                                                                                      |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `dark`                 | The number stays offline to WhatsApp. Nothing changes.                                                                         |
| `online_while_sending` | The number goes online before its first send and stays online while sending, then goes offline 90 seconds after the last send. |
| `online_hours`         | As above, and the number also stays online continuously between `onlineStart` and `onlineEnd` in its own country's local time. |

Both online values also stop the number's delivery receipts being marked as
sent from a backgrounded client, which is the single clearest difference
between a linked device that is in use and one that never is.

Going online needs a display name on the account. When WhatsApp has none, the
number stays dark and the response reports the applied presence as `dark`.

Setting the number's presence yourself with `POST /messaging/{session}/presence`
always wins: Safe Mode does not undo it. Your value holds until you set it
again, until the session reconnects, or — with `online_hours` — until the
window next opens or closes.

### Typing

| Value         | Behaviour                                                                                             |
| ------------- | ----------------------------------------------------------------------------------------------------- |
| `off`         | Nothing.                                                                                              |
| `before_text` | Before each text message to an individual chat, the number shows a typing indicator, then sends.      |
| `before_all`  | As `before_text`, plus a typing indicator before media and a recording indicator before a voice note. |

The indicator lasts as long as the message would take to type: about 4.2
characters a second, never less than 900 ms and never more than 9 seconds. A
voice note shows the recording indicator for 2.5 to 4 seconds and media shows
the typing indicator for 1.2 to 2.6 seconds, both varied per message.

Typing indicators are never sent to a channel, a bot, or a broadcast list, and
never to a group.

### Reads

| Value           | Behaviour                                                                                                                                              |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `off`           | Nothing.                                                                                                                                               |
| `replied_chats` | Immediately before replying to a chat with unanswered incoming messages, the number marks the most recent one read, then sends.                        |
| `all_inbound`   | As above, and every incoming message in an individual chat is marked read after a varied delay of 4 to 25 seconds. Group messages are not marked read. |

### Pacing

| Value          | Behaviour                                                                                                     |
| -------------- | ------------------------------------------------------------------------------------------------------------- |
| `off`          | Nothing beyond your plan's throughput limit.                                                                  |
| `jittered`     | Each send waits until at least 0.7 to 2.6 seconds have passed since the number's previous send.               |
| `conversation` | As `jittered`, and a reply also waits until 2 to 15 seconds have passed since the message it answers arrived. |

## Delays never stack

A send takes **one** pause, not four. The typing indicator, the pacing gap, and
the reply floor produce a budget; the largest one wins, and the time your
request already spent waiting for the throughput limit is subtracted from it.
The pause never exceeds 9 seconds.

If your request declares how long it will wait for WhatsApp to acknowledge the
send, Safe Mode shortens the pause rather than exceeding your deadline. A
request sent with `Prefer: respond-async` is unaffected.

Campaign sends are already paced by the campaign's own rate, so a Safe Mode
pause on a campaign send takes at most 60% of the gap between recipients and
never reduces the campaign's throughput.

## Campaign sends always get typing and pacing

A send that originates from a campaign always uses at least `typing:
before_text` and `pacing: jittered`, whatever the number's Safe Mode settings
are. Campaigns are the safe path for reaching people who have not written to
you, so they behave that way by default.

## What your contacts see

Every value except `pacing` changes what the people you message see. These
actions cannot be undone once taken.

| Setting                                            | What a contact sees                                                                                                                                                                           |
| -------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `presence: online_while_sending` or `online_hours` | The number appears **online**, and its **last seen** becomes visible to whoever the account's privacy settings allow. Delivery receipts stop being marked as sent from a backgrounded client. |
| `typing: before_text` or `before_all`              | A **typing…** indicator, or **recording audio…** before a voice note, in the chat before each message.                                                                                        |
| `reads: replied_chats`                             | **Blue ticks** on the message the number is replying to.                                                                                                                                      |
| `reads: all_inbound`                               | **Blue ticks on every incoming message**, within about half a minute.                                                                                                                         |
| `pacing`                                           | Nothing. Messages arrive with human-shaped gaps.                                                                                                                                              |

This is why nothing is on by default. If your number is expected to stay dark,
leave every setting where it is; BanSafe reports the findings either way.

## Where to set it

Safe Mode is in Console and on both APIs, with the same settings, the same
rules, and the same responses:

| Where         | Paths                                                                                                                 | Who uses it                                                          |
| ------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Console       | **Safety → Safe Mode** for the project ceiling, and **Safe Mode for this number** in the panel beside the number list | A signed-in team member                                              |
| Platform API  | `/platform/projects/{projectId}/safe-mode`, `/platform/sessions/{sessionId}/safe-mode`                                | An organization API key, a project token, or a signed-in team member |
| Messaging API | `/messaging/projects/{projectId}/safe-mode`, `/messaging/{session}/safe-mode`                                         | An organization API key or a project token                           |

Console names the values in plain words rather than the API's: presence is
**Dark**, **Online while sending** or **Online during set hours**; typing is
**Off**, **Before each text message** or **Before every message**; read
receipts are **Off**, **Chats this number replies to** or **Every incoming
message**; pacing is **Off**, **Varied gaps** or **Conversation pace**. A
number's control also offers **Follow the project**, which is `inherit`. See
[Safety](/console/safety#safe-mode).

Reading needs `sessions:read`; changing needs `sessions:manage`. On the
Platform API, a signed-in team member must be an organization owner, admin, or
developer to change Safe Mode; reading needs organization membership.

The examples below use the Platform API. `{sessionId}` accepts a session UUID
or a stable session slug, and so does the `{session}` segment on the Messaging
API.

## Set the project ceiling

The project ceiling is the most visible behaviour any number in the project may
use.

```http theme={null}
GET  /platform/projects/{projectId}/safe-mode
PUT  /platform/projects/{projectId}/safe-mode
```

The body is a partial update: send only the settings you want to change.

```bash theme={null}
curl -X PUT "https://api.polymorfa.com/platform/projects/$PROJECT_ID/safe-mode" \
  -H "Authorization: Bearer $POLYMORFA_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "typing": "before_text", "pacing": "jittered" }'
```

`onlineStart` and `onlineEnd` are whole local hours from 0 to 23 and default to
9 and 21. They apply only to `presence: online_hours`.

The `GET` response tells you whether the plan allows a raise before you send
one. `entitled` is `true` when at least one number in the project is on a plan
that includes BanSafe Lite, and `entitlementReason` carries the sentence a
refused raise would return, or `null` when a raise is allowed.

```json theme={null}
{
  "projectId": "3f9c...",
  "ceiling": { "presence": "dark", "typing": "off", "reads": "off", "pacing": "off", "onlineStart": 9, "onlineEnd": 21 },
  "entitled": false,
  "entitlementReason": "Safe Mode is part of BanSafe Lite, which is not included on this number's plan"
}
```

## Set one number's override

```http theme={null}
GET  /platform/sessions/{sessionId}/safe-mode
PUT  /platform/sessions/{sessionId}/safe-mode
```

Each setting on a number accepts `inherit` to follow the project ceiling. An
override may only be **equally or more conservative** than the ceiling; a more
visible value is refused:

```json theme={null}
{
  "error": "the override is more visible than the project ceiling allows",
  "code": "safe_mode_exceeds_project_ceiling"
}
```

Raise the project ceiling first, then set the number override.

The response carries the whole picture:

| Field               | Meaning                                                                             |
| ------------------- | ----------------------------------------------------------------------------------- |
| `project`           | The project ceiling.                                                                |
| `override`          | This number's override, with `inherit` where it follows the ceiling.                |
| `effective`         | The policy in force for this number.                                                |
| `applied`           | What the number last reported it is actually running, with the time it reported it. |
| `mismatch`          | `true` when `applied` differs from `effective`.                                     |
| `entitled`          | `true` when this number's plan includes BanSafe Lite, so a raise is allowed.        |
| `entitlementReason` | Why a raise would be refused, or `null` when `entitled` is `true`.                  |

`applied` is an observation, not a claim: it comes from the number itself. A
number that is disconnected reports nothing and `applied` is `null`.

## Responses to expect

| Status | Code                                | Meaning                                                                                                                                                    |
| ------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `safe_mode_exceeds_project_ceiling` | The number override is more visible than the project ceiling. Raise the ceiling first.                                                                     |
| `402`  | `safe_mode_not_entitled`            | This number's plan does not include BanSafe Lite. Any request that raises a setting above its default is refused; turning settings back down always works. |
| `503`  | `propagation_pending`               | The policy was stored, but a connected number did not confirm it. Retry the request. The stored policy is already correct and is retried automatically.    |

A `503` never means the change was lost. Polymorfa persists the policy before
telling the number about it, so an effective mode is never reported as applied
when it is not.

## Which numbers can use it

Safe Mode is included with BanSafe Lite.

| Number plan | BanSafe Lite (Safe Mode) |
| ----------- | ------------------------ |
| Free        | Not included             |
| Standard    | Included                 |
| Pro         | Included                 |

Both `GET` responses report this for you in `entitled` and
`entitlementReason`, so you can tell a caller why a change is unavailable
without attempting it.

Eligible production Linked Device sessions receive Health estimates. BanSafe
Lite adds Safe Mode, which changes how the number sends. See [BanSafe
Health](/guides/bansafe/overview).
