# Phone number scam lookup API

Bewary's phone API checks numbers against its scam list, which includes numbers from FTC and FCC complaints and Germany's Federal Network Agency (BNetzA) actions. It returns the country and number type from the numbering plan. With `line_type=true`, it adds the live line type from the carrier network. A number named only as caller ID in unwanted-call complaints carries `PHONE_REPORTED_CALLER_ID`, a caution, and is not listed. A call costs 1 credit (10 with line_type=true), a free key gets 1,000 credits a month, no card, and it answers in median 115 ms.

Updated 2026-10-30.

## At a glance

- Endpoint: GET https://api.bewary.app/v1/phone/{number}
- Price: 1 credit, line_type=true 10
- Free key: 1,000 credits a month, no card
- Speed: Median 115 ms, measured
- Returns: verdict, p_scam, entities with typed facts, reason codes
- MCP tool: lookup_phone
- Free tool: Is this number a scam?, https://bewary.app/check/phone

## Used for

- Labelling incoming calls in a dialer, CRM or call center.
- Checking the callback number in a suspicious message.
- Flagging VoIP numbers at sign-up.

## Call it

curl:

```bash
curl "https://api.bewary.app/v1/phone/%2B18008291040"
```

Python:

```python
import requests

r = requests.get("https://api.bewary.app/v1/phone/%2B18008291040", timeout=10)
result = r.json()
print(result["verdict"], result["p_scam"], result["reason_codes"])
```

JavaScript:

```javascript
const res = await fetch("https://api.bewary.app/v1/phone/%2B18008291040")
const result = await res.json()
console.log(result.verdict, result.p_scam, result.reason_codes)
```

## Act on the result

Python:

```python
import os, requests
from urllib.parse import quote

def caller_label(number):
    key = os.environ["BEWARY_API_KEY"]          # a free key: bewary.app/account
    headers = {"Authorization": f"Bearer {key}"}
    r = requests.get("https://api.bewary.app/v1/phone/" + quote(number), headers=headers, timeout=10)
    r.raise_for_status()
    result = r.json()
    if result["entities"][0]["facts"]["listed"]:
        return "Reported for scam calls"
    return None                            # no label: not listed is not proof of safe

print(caller_label("+18008291040"))
```

TypeScript, with the types from https://bewary.app/types/bewary.d.ts:

```typescript
import type { Envelope } from "./bewary"

export async function callerLabel(number: string): Promise<string | null> {
  const key = process.env.BEWARY_API_KEY!     // a free key: bewary.app/account
  const headers: Record<string, string> = { Authorization: `Bearer ${key}` }
  const res = await fetch("https://api.bewary.app/v1/phone/" + encodeURIComponent(number),
    { headers, signal: AbortSignal.timeout(10_000) })
  if (!res.ok) return null
  const e = (await res.json() as Envelope).entities[0]
  return e.type === "phone" && e.facts.listed ? "Reported for scam calls" : null
}
```

## The fields you will use

- `verdict`: scam when the number is on our list, otherwise usually unknown.
- `entities[0].facts.listed`: On Bewary's scam list.
- `entities[0].facts.number_type`: From the numbering plan: toll_free, mobile, landline and more.
- `entities[0].facts.line_type`: With line_type=true: the live line type.

## Example response: unknown

Request: `GET /v1/phone/+18008291040`. Captured from the live API on 2026-10-31.

```json
{
  "schema_version": "1.7.0",
  "request_id": "req_01M69WHKA2ETH25TQA4J813VRP",
  "checked_at": "2026-10-31T21:06:15Z",
  "engine": "bewary-engine/3.0.0",
  "models": {
    "calibration": "interim-band-v0"
  },
  "input": {
    "kind": "lookup",
    "channel": "unknown",
    "language": null,
    "region": null
  },
  "verdict": "unknown",
  "p_scam": 0.2,
  "risk_score": 20,
  "confidence": 0,
  "classification": null,
  "reason_codes": [],
  "entities": [
    {
      "id": "e1",
      "type": "phone",
      "value": "+18008291040",
      "normalized": "+18008291040",
      "role": "mentioned",
      "verdict": "unknown",
      "p_scam": 0.2,
      "risk_score": 20,
      "reason_codes": [],
      "facts": {
        "e164": "+18008291040",
        "country": "US",
        "number_type": "toll_free",
        "original_carrier": null,
        "line_type": null,
        "complaints": {
          "ftc": null,
          "fcc": null,
          "callbacks": null,
          "pushed_by": null
        },
        "honeypot_hits_30d": 0,
        "listed": false,
        "public_listing": null,
        "indexable": false,
        "target_countries": null,
        "list_count": null
      },
      "related": [],
      "datasets": [
        {
          "dataset": "phone_numbering",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "us_numbering",
          "coverage": "US",
          "in_coverage": true
        },
        {
          "dataset": "phone_complaints",
          "coverage": "US",
          "in_coverage": true
        },
        {
          "dataset": "honeypot_lines",
          "coverage": "US",
          "in_coverage": true
        },
        {
          "dataset": "phone_reports_de",
          "coverage": "DE",
          "in_coverage": false
        },
        {
          "dataset": "community_reports",
          "coverage": "WW",
          "in_coverage": true
        }
      ]
    }
  ],
  "signals": [],
  "degraded": false,
  "degraded_reasons": [],
  "cached": false,
  "explanation": null
}
```

## Phone number facts

Every key is in every response, null when unknown. Keys marked coming stay null until their source ships.

| Key | Type | Meaning |
|---|---|---|
| `e164` | string or null | The number in E.164 form. |
| `country` | ISO code or null | Country from the numbering plan. |
| `number_type` | mobile \| landline \| voip \| toll_free \| premium \| shared_cost \| shortcode \| unknown or null | Type from the numbering plan. |
| `line_type` | same values or null | With line_type=true: the live line type from the carrier network. |
| `listed` | boolean or null | On Bewary's scam list. |
| `complaints` | object or null | FTC and FCC complaints from the last 13 months, one entry per source (null when that source has none): count, days, first and last date, subjects, robocall share, the count per day for 90 days and the top five states of the people who complained. |
| `indexable` | boolean or null | Whether the number has a page in the number directory: two independent signals, such as complaints on two or more days, or FTC and FCC complaints. |
| `original_carrier` | string or null | The carrier the number block was first assigned to. |
| `target_countries` | array or null | Countries whose people it is aimed at, from the national lists that name it (PL, TW, DE, US). |
| `list_count` | integer or null | How many lists name it, ours and others'. |
| `honeypot_hits_30d` | integer or null | Calls to our honeypot lines in 30 days. |

## Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `number` | path | string | yes | The number in E.164 form, e.g. +18008291040. A national number needs region. |
| `line_type` | query | boolean | no | Add the live line type: mobile, landline or VoIP (10 credits, API key required). Default `false`. |
| `region` | query | string | no | ISO country code for reading a national number, e.g. US. |

## Questions

### Is there an API that says if a phone number is a scam?

Yes, `GET /v1/phone/{number}` checks Bewary's scam list. Bewary reads FTC Do Not Call and FCC complaints hourly and Germany's Federal Network Agency (BNetzA) actions daily. Numbers named only as caller ID in unwanted-call complaints stay `unknown` unless other evidence names them. Your code must never treat `unknown` as safe.

### Can it detect VoIP numbers?

With line_type=true (10 credits, API key), facts.line_type gives the live line type, VoIP included. The free number_type comes from the numbering plan, which for US numbers often cannot tell mobile from landline.

### Does it return the owner's name?

No. Bewary never returns a person's name for a number.

### Is a listed number always a scammer?

Bewary's scam list includes numbers pushed as callback numbers in calls and numbers named in national lists such as BNetzA's actions against phone numbers. Since contract 1.2.0, a number named only as caller ID carries the caution `PHONE_REPORTED_CALLER_ID` and stays `unknown` without a scam listing.

## Reason codes it can return

`LISTED_BLOCKLIST`, `PHONE_INVALID`, `PHONE_UNPARSEABLE`, `PHONE_VOIP`, `PHONE_RISK_FLAGGED`, `PHONE_PUBLIC_SCAM_REPORTS`, `PHONE_NO_PUBLIC_LISTING`, `PHONE_FTC_COMPLAINTS`, `PHONE_FCC_COMPLAINTS`, `PHONE_HONEYPOT_HIT`, `LISTED_CAUTION`, `PHONE_REPORTED_CALLER_ID`, `PLATFORM_TAKEDOWN`, `PLATFORM_WARNING`
