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

# Spam monitoring

> Monitor your outbound numbers for carrier spam/scam-likely flagging, with verification, per-carrier status, run history, and screenshots.

The Spam Monitoring API watches your outbound phone numbers and tells you when a carrier starts flagging them as **spam** or **scam-likely**. OptimalDial places periodic test calls to each monitored number across the major US carriers (AT\&T, T-Mobile, Verizon), records the label each carrier shows, captures a screenshot, and exposes the result through these endpoints — or pushes it to you over [webhooks](#webhook-alerts).

It's available to every account with an API key and an **active subscription**. Standard [rate limits and the `429` envelope](/guides/errors-and-rate-limits#rate-limits) apply, exactly as on the rest of the v1 API.

## Lifecycle

1. `POST /api/v1/spam/numbers` — add a number. Verification is **auto-initiated** (a call or SMS to the number).
2. The recipient reads back the code; you submit it to `POST /api/v1/spam/numbers/{id}/verify`. Missed it? `…/verify/resend`.
3. Once verified, OptimalDial re-tests the number **daily** across each carrier.
4. You read results via `GET /api/v1/spam/numbers` / `…/{id}` (latest per-carrier status), `…/{id}/history` (recent runs), and `…/{id}/screenshot` (the captured image) — or subscribe to the `spam.detected` and `number.verified` [webhook events](#webhook-alerts).

## The MonitoredNumber object

| Field                  | Type                      | Notes                                                                                                                |
| ---------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `id`                   | string (UUID)             | Stable identifier.                                                                                                   |
| `phone_number`         | string                    | E.164-normalized phone (US/CA only).                                                                                 |
| `verified`             | boolean                   | Whether the number completed verification. Monitoring runs only for verified numbers.                                |
| `carrier`              | string \| null            | The number's own carrier, when known. Used to flag same-carrier tests.                                               |
| `same_carrier_warning` | boolean                   | `true` when a test ran from the same carrier as the number, which can skew the result. Treat such results with care. |
| `status`               | `"active"` \| `"paused"`  | Whether monitoring is currently running for this number.                                                             |
| `created_at`           | string (ISO 8601)         | UTC timestamp.                                                                                                       |
| `updated_at`           | string (ISO 8601) \| null | UTC timestamp of the last change.                                                                                    |
| `carrier_statuses`     | CarrierStatus\[]          | Latest status for each carrier tested. See below.                                                                    |

### The CarrierStatus object

| Field                  | Type                                                      | Notes                                                                                  |
| ---------------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `carrier`              | string                                                    | Carrier this row applies to — one of `at&t`, `t-mobile`, `verizon`.                    |
| `status`               | `"pending"` \| `"running"` \| `"completed"` \| `"failed"` | State of the latest test for this carrier.                                             |
| `spam_detected`        | boolean \| null                                           | Whether the number was flagged on this carrier. `null` until the latest run completes. |
| `spam_label`           | string \| null                                            | The carrier-displayed label (e.g. `"Scam Likely"`), when flagged.                      |
| `test_run_id`          | string \| null                                            | The run that produced this status.                                                     |
| `captured_at`          | string (ISO 8601) \| null                                 | When the latest result was captured.                                                   |
| `screenshot_available` | boolean                                                   | Whether a screenshot can be fetched for this carrier's latest run.                     |

***

## Verification (SMS or call)

Verification is **required for the monitoring service to function** — OptimalDial must verify the number before it can place test calls on its behalf. It is **not** an ownership or security gate; it's a carrier prerequisite for the test traffic.

* The **add** endpoint auto-initiates verification. By default it places a **call** (`verification_method: "call"`); pass `"sms"` to receive a text instead.
* The recipient reads back (or copies) the code and you submit it to `…/verify`.
* **SMS isn't deliverable to landlines.** Requesting `"sms"` for a non-SMS-capable number returns `400` — fall back to `call`.
* Use `…/verify/resend` to re-send the code, optionally switching method (e.g. from `sms` to `call`).

A number's `verified` flag flips to `true` once a correct code is submitted, and the [`number.verified`](#webhook-alerts) webhook fires.

***

## Add a monitored number

```http theme={null}
POST /api/v1/spam/numbers
```

Registers a number for monitoring and auto-sends a verification code. Returns `201` with the new [`MonitoredNumber`](#the-monitorednumber-object) and a `verification` block describing how the code was dispatched.

### Request body

| Field                 | Type                | Required | Notes                                                                                     |
| --------------------- | ------------------- | -------- | ----------------------------------------------------------------------------------------- |
| `phone_number`        | string              | yes      | Any common format. Normalized to E.164. Must be US or Canadian.                           |
| `verification_method` | `"call"` \| `"sms"` | no       | How to deliver the code. Defaults to `call`. `sms` is only valid for SMS-capable numbers. |

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST https://api.optimaldial.com/api/v1/spam/numbers \
      -H "Authorization: Bearer $OPTIMALDIAL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "phone_number": "+15551234567",
        "verification_method": "call"
      }'
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const res = await fetch("https://api.optimaldial.com/api/v1/spam/numbers", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.OPTIMALDIAL_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        phone_number: "+15551234567",
        verification_method: "call",
      }),
    });
    const { number, verification } = await res.json();
    // Tell the recipient: verification.instructions
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    resp = requests.post(
        "https://api.optimaldial.com/api/v1/spam/numbers",
        headers={
            "Authorization": f"Bearer {os.environ['OPTIMALDIAL_API_KEY']}",
            "Content-Type": "application/json",
        },
        json={
            "phone_number": "+15551234567",
            "verification_method": "call",
        },
    )
    data = resp.json()
    number, verification = data["number"], data["verification"]
    ```
  </Tab>
</Tabs>

### Response

`201 Created`:

```json theme={null}
{
  "number": {
    "id": "7c2a1f0e-9b3d-4a8e-bf21-0c5d6e7f8a90",
    "phone_number": "+15551234567",
    "verified": false,
    "carrier": null,
    "same_carrier_warning": false,
    "status": "active",
    "created_at": "2026-06-26T15:04:01+00:00",
    "updated_at": null,
    "carrier_statuses": []
  },
  "verification": {
    "required": true,
    "sent": true,
    "method": "call",
    "instructions": "We're calling +15551234567 now. Enter the 6-digit code the call reads out to finish verification.",
    "error": null
  }
}
```

### Errors

| Status | When                                                                                                                  |
| ------ | --------------------------------------------------------------------------------------------------------------------- |
| `400`  | Phone is unparseable/not US-CA, or `verification_method: "sms"` was requested for a non-SMS-capable (landline) number |
| `401`  | Auth failure                                                                                                          |
| `403`  | No active subscription, or your plan's spam-monitoring number limit is reached                                        |
| `409`  | This number is already monitored in your organization                                                                 |
| `429`  | Rate limit hit                                                                                                        |

***

## Submit a verification code

```http theme={null}
POST /api/v1/spam/numbers/{number_id}/verify
```

Submits the code the recipient received. On success the number becomes `verified` and daily monitoring begins.

### Request body

| Field  | Type   | Required | Notes                                       |
| ------ | ------ | -------- | ------------------------------------------- |
| `code` | string | yes      | The code from the verification call or SMS. |

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST "https://api.optimaldial.com/api/v1/spam/numbers/$NUMBER_ID/verify" \
      -H "Authorization: Bearer $OPTIMALDIAL_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{"code": "123456"}'
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const res = await fetch(
      `https://api.optimaldial.com/api/v1/spam/numbers/${numberId}/verify`,
      {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.OPTIMALDIAL_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ code: "123456" }),
      },
    );
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    requests.post(
        f"https://api.optimaldial.com/api/v1/spam/numbers/{number_id}/verify",
        headers={
            "Authorization": f"Bearer {os.environ['OPTIMALDIAL_API_KEY']}",
            "Content-Type": "application/json",
        },
        json={"code": "123456"},
    )
    ```
  </Tab>
</Tabs>

### Response

```json theme={null}
{
  "success": true,
  "verified": true,
  "phone_number": "+15551234567",
  "message": "Number verified. Daily spam monitoring is now active."
}
```

### Errors

| Status | When                                                 |
| ------ | ---------------------------------------------------- |
| `400`  | Code is incorrect, or the number is already verified |
| `401`  | Auth failure                                         |
| `404`  | Number not found, or belongs to another organization |
| `429`  | Rate limit hit                                       |

***

## Resend a verification code

```http theme={null}
POST /api/v1/spam/numbers/{number_id}/verify/resend
```

Re-sends the code, optionally switching delivery method. Use this if the first call/SMS was missed, or to fall back to `call` when `sms` couldn't be delivered.

### Request body (optional)

| Field    | Type                | Required | Notes                                                     |
| -------- | ------------------- | -------- | --------------------------------------------------------- |
| `method` | `"call"` \| `"sms"` | no       | Delivery method for the re-sent code. Defaults to `call`. |

```bash theme={null}
curl -X POST "https://api.optimaldial.com/api/v1/spam/numbers/$NUMBER_ID/verify/resend" \
  -H "Authorization: Bearer $OPTIMALDIAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"method": "sms"}'
```

### Response

```json theme={null}
{
  "success": true,
  "phone_number": "+15551234567",
  "method": "sms",
  "message": "A new code is on its way by SMS."
}
```

### Errors

| Status | When                                                           |
| ------ | -------------------------------------------------------------- |
| `401`  | Auth failure                                                   |
| `404`  | Number not found, or belongs to another organization           |
| `429`  | Rate limit hit                                                 |
| `502`  | The verification could not be sent — retry, or switch `method` |

***

## List monitored numbers

```http theme={null}
GET /api/v1/spam/numbers
```

Returns your monitored numbers, each with its latest per-carrier status, plus the maximum your plan allows.

### Query parameters

| Param    | Type            | Default | Notes                   |
| -------- | --------------- | ------- | ----------------------- |
| `limit`  | integer (1–500) | 100     | Page size.              |
| `offset` | integer (≥0)    | 0       | Number of rows to skip. |

```bash theme={null}
curl "https://api.optimaldial.com/api/v1/spam/numbers?limit=100&offset=0" \
  -H "Authorization: Bearer $OPTIMALDIAL_API_KEY"
```

### Response

```json theme={null}
{
  "numbers": [
    {
      "id": "7c2a1f0e-9b3d-4a8e-bf21-0c5d6e7f8a90",
      "phone_number": "+15551234567",
      "verified": true,
      "carrier": "verizon",
      "same_carrier_warning": false,
      "status": "active",
      "created_at": "2026-06-26T15:04:01+00:00",
      "updated_at": "2026-06-26T15:09:42+00:00",
      "carrier_statuses": [
        {
          "carrier": "at&t",
          "status": "completed",
          "spam_detected": false,
          "spam_label": null,
          "test_run_id": "run_8f1c…",
          "captured_at": "2026-06-26T15:08:10+00:00",
          "screenshot_available": true
        },
        {
          "carrier": "t-mobile",
          "status": "completed",
          "spam_detected": true,
          "spam_label": "Scam Likely",
          "test_run_id": "run_8f1d…",
          "captured_at": "2026-06-26T15:08:55+00:00",
          "screenshot_available": true
        },
        {
          "carrier": "verizon",
          "status": "running",
          "spam_detected": null,
          "spam_label": null,
          "test_run_id": "run_8f1e…",
          "captured_at": null,
          "screenshot_available": false
        }
      ]
    }
  ],
  "total": 12,
  "spam_monitoring_limit": 20
}
```

`spam_monitoring_limit` is your **effective** limit: the numbers included with your plan plus
any additional numbers purchased at \$5/month each. It is `null` when your plan (or an admin
account) has no cap.

Adding a number beyond this limit returns `403`. Numbers already monitored beyond the limit
(for example after a downgrade) are kept but not tested — the oldest numbers up to the limit
are the ones that receive daily tests. Owners can raise the limit from **Billing → Spam
Monitoring Add-On** in the dashboard.

### Errors

| Status | When                                       |
| ------ | ------------------------------------------ |
| `401`  | Auth failure                               |
| `403`  | No active subscription on the organization |
| `429`  | Rate limit hit                             |

***

## Retrieve a monitored number

```http theme={null}
GET /api/v1/spam/numbers/{number_id}
```

Returns a single [`MonitoredNumber`](#the-monitorednumber-object) with its latest per-carrier status.

```bash theme={null}
curl "https://api.optimaldial.com/api/v1/spam/numbers/$NUMBER_ID" \
  -H "Authorization: Bearer $OPTIMALDIAL_API_KEY"
```

### Errors

| Status | When                                                 |
| ------ | ---------------------------------------------------- |
| `401`  | Auth failure                                         |
| `403`  | No active subscription on the organization           |
| `404`  | Number not found, or belongs to another organization |
| `429`  | Rate limit hit                                       |

***

## List recent test runs

```http theme={null}
GET /api/v1/spam/numbers/{number_id}/history
```

Returns the number's individual test runs from the **last 7 days**, newest first. Each row is one carrier's test on one day.

```bash theme={null}
curl "https://api.optimaldial.com/api/v1/spam/numbers/$NUMBER_ID/history" \
  -H "Authorization: Bearer $OPTIMALDIAL_API_KEY"
```

### Response

```json theme={null}
{
  "number_id": "7c2a1f0e-9b3d-4a8e-bf21-0c5d6e7f8a90",
  "phone_number": "+15551234567",
  "results": [
    {
      "test_run_id": "run_8f1d…",
      "carrier": "t-mobile",
      "status": "completed",
      "spam_detected": true,
      "spam_label": "Scam Likely",
      "sip_response_code": "200",
      "caller_id_verified": false,
      "screenshot_available": true,
      "tested_at": "2026-06-26T15:08:55+00:00"
    },
    {
      "test_run_id": "run_8e90…",
      "carrier": "t-mobile",
      "status": "completed",
      "spam_detected": false,
      "spam_label": null,
      "sip_response_code": "200",
      "caller_id_verified": true,
      "screenshot_available": true,
      "tested_at": "2026-06-25T15:07:11+00:00"
    }
  ],
  "total": 9
}
```

### The TestRunHistoryItem object

| Field                  | Type                                                      | Notes                                                                        |
| ---------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------- |
| `test_run_id`          | string                                                    | Identifier for this carrier-test run.                                        |
| `carrier`              | string                                                    | Carrier tested (e.g. `at&t`, `t-mobile`, `verizon`).                         |
| `status`               | `"pending"` \| `"running"` \| `"completed"` \| `"failed"` | State of this run.                                                           |
| `spam_detected`        | boolean \| null                                           | Whether the number was flagged on this run.                                  |
| `spam_label`           | string \| null                                            | The carrier-displayed label, when flagged.                                   |
| `sip_response_code`    | string \| null                                            | SIP response observed on the test call (e.g. `"200"`, `"603"`).              |
| `caller_id_verified`   | boolean \| null                                           | Whether the carrier showed the number as a verified caller ID (STIR/SHAKEN). |
| `screenshot_available` | boolean                                                   | Whether a screenshot can be fetched for this run.                            |
| `tested_at`            | string (ISO 8601)                                         | When the test ran.                                                           |

### Errors

| Status | When                                                 |
| ------ | ---------------------------------------------------- |
| `401`  | Auth failure                                         |
| `403`  | No active subscription on the organization           |
| `404`  | Number not found, or belongs to another organization |
| `429`  | Rate limit hit                                       |

***

## Get the latest screenshot

```http theme={null}
GET /api/v1/spam/numbers/{number_id}/screenshot
```

Streams the most recent captured screenshot for the number as a **binary `image/jpeg`** — not JSON. Pass `carrier` to select a specific carrier's screenshot; omit it for the most recent across carriers.

### Query parameters

| Param     | Type                                    | Default | Notes                                                           |
| --------- | --------------------------------------- | ------- | --------------------------------------------------------------- |
| `carrier` | `"at&t"` \| `"t-mobile"` \| `"verizon"` | —       | Which carrier's screenshot to return. Omit for the most recent. |

```bash theme={null}
curl "https://api.optimaldial.com/api/v1/spam/numbers/$NUMBER_ID/screenshot?carrier=verizon" \
  -H "Authorization: Bearer $OPTIMALDIAL_API_KEY" \
  --output screenshot.jpg
```

The response body is the raw JPEG bytes with `Content-Type: image/jpeg`. Write it straight to a file (as above) rather than parsing it as JSON. Screenshots captured before the JPEG rollout may still return `image/png` for up to 7 days (the screenshot retention window) — check the `Content-Type` header if the distinction matters to you.

### Errors

| Status | When                                                                                                |
| ------ | --------------------------------------------------------------------------------------------------- |
| `401`  | Auth failure                                                                                        |
| `403`  | No active subscription on the organization                                                          |
| `404`  | No screenshot available for this number (or carrier), or the number belongs to another organization |
| `429`  | Rate limit hit                                                                                      |

***

## Stop monitoring a number

```http theme={null}
DELETE /api/v1/spam/numbers/{number_id}
```

Removes the number from monitoring. No further test runs are scheduled.

```bash theme={null}
curl -X DELETE "https://api.optimaldial.com/api/v1/spam/numbers/$NUMBER_ID" \
  -H "Authorization: Bearer $OPTIMALDIAL_API_KEY"
```

### Response

```json theme={null}
{ "status": "deleted", "id": "7c2a1f0e-9b3d-4a8e-bf21-0c5d6e7f8a90" }
```

### Errors

| Status | When                                                 |
| ------ | ---------------------------------------------------- |
| `401`  | Auth failure                                         |
| `404`  | Number not found, or belongs to another organization |
| `429`  | Rate limit hit                                       |

***

## Webhook alerts

Instead of polling, subscribe to spam-monitoring events on your existing [webhook endpoints](/api-reference/webhooks). They use the same HMAC signing, headers, and retry behaviour as every other event — see the [Receiving webhooks](/guides/webhooks) guide. Register interest by including the event types when you create or update an endpoint:

```bash theme={null}
curl -X POST https://api.optimaldial.com/api/v1/webhooks \
  -H "Authorization: Bearer $OPTIMALDIAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://hooks.example.com/optimaldial",
    "events": ["spam.detected", "number.verified"]
  }'
```

| Event             | Fires when                                                                                                                                                            |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `spam.detected`   | A carrier transitions a number from clean → flagged. **Edge-triggered**: fires once on the transition per carrier, not on every daily re-test while it stays flagged. |
| `number.verified` | A monitored number completes verification.                                                                                                                            |

See [the webhooks guide](/guides/webhooks#event-types) for the full payload bodies. In short, `spam.detected` carries the flagging carrier, label, SIP response, and a `screenshot_available` flag; `number.verified` carries the number's id, phone, carrier, and verification time.
