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

# Wire a brand-identity watchlist and run smishing takedown cases

> End-to-end operator walkthrough for the brand-impersonation monitor: register your brand names, domains, and sender IDs on a watchlist, scan observed candidates, open a takedown case with a filing-ready evidence pack, and drive it through its lifecycle — all over the API.

# Wire a brand-identity watchlist and run smishing takedown cases

This guide walks the full operator loop end to end: declare which tokens are
legitimately yours, scan candidates you observe in the wild, open a takedown
case for a hostile candidate, and work that case through to resolution. The
reference material — detection signals, band thresholds, matching rules —
lives in
[Brand impersonation watchlist, scan & takedown cases](/compliance/brand-impersonation-takedown);
this page is the task walkthrough. Every step uses cURL against the six
endpoints under `/api/v1/brand-identity/impersonation/*`. The same surface
is in the dashboard under **Settings → Compliance → Brand impersonation
watchlist**.

## 1. What the impersonation monitor is

The impersonation monitor is a **tenant-owned watchlist**: you declare the
brand names, domains, and sender IDs that are legitimately yours, and the
scanner compares candidates you observe against those tokens. The watchlist
persists under your organization settings
(`brand_impersonation_watchlist`), so it is tenant-isolated and needs no
schema migration — the same posture as other settings-backed compliance
modules.

Candidates come in three classes:

* **Lookalike domains** — the host behind a smishing shortlink
  (`acme-verify.tk`, `acnne.com`).
* **Spoofed sender IDs** — an SMS/RCS sender string that reads as your brand.
* **Display-name lookalikes** — the sender name shown on WhatsApp, RCS, or a
  messaging platform.

The service is **advisory only**. It scores and prepares evidence; it never
routes or sends anything, and no case status advances on its own. Filing a
takedown notice with a registrar, carrier, or platform abuse desk is your
action, done outside the product.

## 2. Create the watchlist

A PUT replaces the whole watchlist, so send all three lists every time:

```bash cURL theme={null}
curl -X PUT "https://api.orbit.devotel.io/api/v1/brand-identity/impersonation/watchlist" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "brand_names": ["Acme", "Acme Pay"],
    "protected_domains": ["acme.com", "acmepay.com"],
    "protected_sender_ids": ["ACME", "AcmePay"]
  }'
```

```typescript Node SDK theme={null}
const watchlist = await orbit.request(
  'PUT',
  '/api/v1/brand-identity/impersonation/watchlist',
  {
    brand_names: ['Acme', 'Acme Pay'],
    protected_domains: ['acme.com', 'acmepay.com'],
    protected_sender_ids: ['ACME', 'AcmePay'],
  },
);
```

Constraints, validated at the edge:

| Field                  | Item limit       | List limit       |
| ---------------------- | ---------------- | ---------------- |
| `brand_names`          | 1–120 chars each | up to 20 entries |
| `protected_domains`    | 1–253 chars each | up to 50 entries |
| `protected_sender_ids` | 1–20 chars each  | up to 50 entries |

Reads resolve to empty lists when you have never configured the watchlist;
writes require an **owner**, **admin**, or **developer** role. Every
replacement is recorded in your audit log
(`brand_identity.impersonation_watchlist.updated`) with the entry counts,
so you can trace who changed the declaration and when. Read the current
watchlist back with `GET` on the same path.

## 3. Score a candidate

`POST /brand-identity/impersonation/scan` scores a batch of candidates
against your watchlist. It is read-only: nothing persists, and repeating the
same batch returns the same verdicts, so use it freely to triage before
deciding whether a candidate deserves a case.

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/brand-identity/impersonation/scan" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "candidates": [
      {
        "type": "domain",
        "value": "acme-verify.tk",
        "channel": "sms",
        "sample_text": "Acme: confirm your account at acme-verify.tk"
      },
      {
        "type": "sender_id",
        "value": "AcmeSupport"
      }
    ]
  }'
```

Each candidate's verdict carries a `score` (0–100), a `band`
(`clean` / `suspicious` / `malicious`), a `recommendation`
(`monitor` / `investigate` / `urgent_takedown`), and the named findings that
built the score. A sample verdict:

```json theme={null}
{
  "data": {
    "candidateCount": 1,
    "worstBand": "malicious",
    "urgentCount": 1,
    "investigateCount": 0,
    "verdicts": [
      {
        "candidate": {
          "type": "domain",
          "value": "acme-verify.tk",
          "channel": "sms",
          "observedAt": null,
          "sampleText": "Acme: confirm your account at acme-verify.tk",
          "sourceUrl": null
        },
        "matchedBrandToken": "acme",
        "score": 60,
        "band": "malicious",
        "recommendation": "urgent_takedown",
        "findings": [
          {
            "code": "combosquat",
            "severity": "block",
            "points": 40,
            "detail": "\"acme-verify.tk\" embeds the protected brand token \"acme\" but is not the tenant's registered asset"
          },
          {
            "code": "abused_tld",
            "severity": "warn",
            "points": 20,
            "detail": "Candidate domain uses the commonly-abused TLD \".tk\""
          }
        ]
      }
    ]
  },
  "meta": { "request_id": "req_01J…", "timestamp": "2026-08-24T12:00:00.000Z" }
}
```

Batches are capped at 50 candidates, duplicates collapse by `(type, value)`,
and verdicts sort worst score first. Your own registered assets always
return a clean verdict — matching your own brand is not impersonation.

## 4. Assemble a takedown case

When a verdict deserves action, post the candidate to `cases`. The endpoint
re-scores it, assembles a filing-ready **evidence pack**, stores the case,
and returns its id:

```bash cURL theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/brand-identity/impersonation/cases" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "candidate": {
      "type": "domain",
      "value": "acme-verify.tk",
      "channel": "sms",
      "sample_text": "Acme: confirm your account at acme-verify.tk"
    },
    "note": "Reported by support on 2026-08-24; customer received smishing SMS."
  }'
```

The response's `data.caseId` (formatted `bpcase_…`) is the handle you PATCH
later. The evidence pack fields:

| Field                       | What it carries                                                 |
| --------------------------- | --------------------------------------------------------------- |
| `candidate`                 | The candidate fingerprint you sent, echoed back.                |
| `brandDisplayName`          | Your first watchlisted brand name, used in the notice.          |
| `matchedBrandToken`         | Which protected token the candidate matched.                    |
| `score`, `band`, `findings` | The scoring detail, verbatim.                                   |
| `recommendedRecipient`      | The abuse-desk class best positioned to act (recipients below). |
| `noticeSummary`             | A ready-to-send takedown notice text.                           |

Recipient classes map from the candidate type:

| Candidate type | `recommendedRecipient`     |
| -------------- | -------------------------- |
| `domain`       | `domain_registrar_abuse`   |
| `sender_id`    | `carrier_sms_abuse`        |
| `display_name` | `messaging_platform_abuse` |

For a lookalike domain you may also need `hosting_provider_abuse` — pick the
desk that owns the infrastructure the attacker is using. The pack assembles
the evidence; it transmits nothing. Supply the screenshots and sample
messages you collected through the candidate's `sample_text` and `source_url`
fields so they ride along into the stored pack.

## 5. Work the case

List cases, newest first, optionally filtered to one status:

```bash cURL theme={null}
curl "https://api.orbit.devotel.io/api/v1/brand-identity/impersonation/cases?status=open" \
  -H "Authorization: Bearer $ORBIT_API_KEY"
```

Advance or dismiss with `PATCH` on the case id:

```bash cURL theme={null}
curl -X PATCH "https://api.orbit.devotel.io/api/v1/brand-identity/impersonation/cases/bpcase_9f2c4a…" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "reported", "note": "Filed with registrar abuse desk."}'
```

The lifecycle is a state machine; only these transitions are legal:

| From             | Allowed next statuses         |
| ---------------- | ----------------------------- |
| `open`           | `evidence_ready`, `dismissed` |
| `evidence_ready` | `reported`, `dismissed`       |
| `reported`       | `resolved`, `dismissed`       |
| `resolved`       | — (terminal)                  |
| `dismissed`      | `open` (reopen)               |

Setting a case to its current status is an idempotent no-op, so retrying a
transition is safe. The dismissal path is always available from any active
state for false positives or accepted risk, and a dismissed case can be
reopened to `open`. Every status change is written to your audit log
(`brand_identity.impersonation_case.status_changed`).

## 6. Operate

Case storage is bounded: the list is pruned down to its cap as cases
accumulate, dropping the oldest `resolved`/`dismissed` entries first, so
active cases stay until everything closed has been trimmed. Case state — like
the watchlist — lives under your **organization settings**
(`brand_impersonation_cases`), tenant-isolated with no schema migration.

Errors to handle:

| Status | Code                               | Cause                                                                                  |
| ------ | ---------------------------------- | -------------------------------------------------------------------------------------- |
| `400`  | validation error                   | A field over its limit, or an unknown enum value.                                      |
| `404`  | `TAKEDOWN_CASE_NOT_FOUND`          | PATCH or GET by an unknown case id.                                                    |
| `409`  | `TAKEDOWN_CASE_INVALID_TRANSITION` | A jump the state machine forbids (`open` → `resolved`, or anything out of `resolved`). |

Writes (watchlist PUT, case create, case PATCH) need the **owner**,
**admin**, or **developer** role; reads also allow **viewer**.

## 7. See also

* [Brand impersonation watchlist, scan & takedown cases](/compliance/brand-impersonation-takedown)
  — the reference page: detection signals, band thresholds, and the matching
  rules behind the scores.
* [Brand identity trust score](/concepts/brand-identity-trust-score) — the
  inward-facing identity model: how your registered brand identity builds
  trust, versus this module's outward-facing watch for third parties
  impersonating it.
* [Brand identity API reference](/api-reference/brand-identity) — the
  endpoint contracts, including the impersonation routes.
* [Sender ID registration](/compliance/sender-id-registration) — register
  the legitimate sender IDs this module protects.
