# Domain age and lookalike domain API

Bewary's domain API returns registration and expiry dates and registrar from RDAP, plus age in days. It checks scam list membership, how often its TLD is used for scams and brand imitation by homoglyph, typo, brand plus extra words or brand plus a scam word. The answer includes `verdict`, `reason_codes` and `site.p_scam_site`, Bewary's verdict on a page its worker read in the last 90 days. We measured first-time domain lookups from Europe at a median of 449 ms on October 23, 2026. A call costs 1 credit (30 with deep=true), a free key gets 1,000 credits a month, no card, and it answers in median 449 ms.

Updated 2026-10-30.

## At a glance

- Endpoint: GET https://api.bewary.app/v1/domain/{host}
- Price: 1 credit, deep=true 30
- Free key: 1,000 credits a month, no card
- Speed: Median 449 ms, measured
- Returns: verdict, p_scam, entities with typed facts, reason codes
- MCP tool: lookup_domain
- Free tool: Domain age checker, https://bewary.app/check/domain-age

## Used for

- Sign-up and checkout checks on the email or website a user gives you.
- Brand protection: spotting lookalike domains that use your name.
- Flagging newly registered domains in a browser extension or security alert.

## Call it

curl:

```bash
curl "https://api.bewary.app/v1/domain/paypal-login-secure.top"
```

Python:

```python
import requests

r = requests.get("https://api.bewary.app/v1/domain/paypal-login-secure.top", 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/domain/paypal-login-secure.top")
const result = await res.json()
console.log(result.verdict, result.p_scam, result.reason_codes)
```

## Act on the result

Python:

```python
import os, requests

def domain_risk(host):
    key = os.environ["BEWARY_API_KEY"]          # a free key: bewary.app/account
    headers = {"Authorization": f"Bearer {key}"}
    r = requests.get(f"https://api.bewary.app/v1/domain/{host}", headers=headers, timeout=10)
    r.raise_for_status()
    result = r.json()
    facts = result["entities"][0]["facts"]
    age = facts["age_days"]                # None when the registry has no date
    return {"verdict": result["verdict"], "new_domain": None if age is None else age < 30,
            "imitates": (facts["lookalike"] or {}).get("brand_id")}

print(domain_risk("paypal-login-secure.top"))
# {'verdict': 'scam', 'new_domain': None, 'imitates': 'paypal'}
```

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

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

export async function domainRisk(host: string) {
  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/domain/${encodeURIComponent(host)}`,
    { headers, signal: AbortSignal.timeout(10_000) })
  if (!res.ok) throw new Error(`Bewary ${res.status}`)
  const r: Envelope = await res.json()
  const e = r.entities[0]
  if (e.type !== "domain") throw new Error("unexpected entity")   // narrows e.facts to DomainFacts
  const age = e.facts.age_days                                  // null when the registry has no date
  return { verdict: r.verdict, newDomain: age === null ? null : age < 30, imitates: e.facts.lookalike?.brand_id ?? null }
}
```

## The fields you will use

- `verdict`: The domain's verdict.
- `entities[0].facts.age_days`: Days since registration. registered_at has the date.
- `entities[0].facts.lookalike`: The brand it imitates and the technique.
- `entities[0].facts.tld_abuse`: How often its TLD is used for scams: low, medium or high.
- `entities[0].facts.listed`: On Bewary's scam list.

## Example response: scam

Request: `GET /v1/domain/paypal-login-secure.top`. Captured from the live API on 2026-10-31.

```json
{
  "schema_version": "1.7.0",
  "request_id": "req_01M69WH7A2GYM3D5XTWA82PZ5B",
  "checked_at": "2026-10-31T21:06:00Z",
  "engine": "bewary-engine/3.0.0",
  "models": {
    "calibration": "interim-band-v0"
  },
  "input": {
    "kind": "lookup",
    "channel": "unknown",
    "language": null,
    "region": null
  },
  "verdict": "scam",
  "p_scam": 0.9116,
  "risk_score": 91,
  "confidence": 1,
  "classification": null,
  "reason_codes": [
    "DOMAIN_BRAND_ON_ABUSED_TLD",
    "DOMAIN_LOOKALIKE_BRAND_WORD"
  ],
  "entities": [
    {
      "id": "e1",
      "type": "domain",
      "value": "paypal-login-secure.top",
      "normalized": "paypal-login-secure.top",
      "role": "link_target",
      "verdict": "scam",
      "p_scam": 0.9116,
      "risk_score": 91,
      "reason_codes": [
        "DOMAIN_BRAND_ON_ABUSED_TLD",
        "DOMAIN_LOOKALIKE_BRAND_WORD"
      ],
      "facts": {
        "registrable_domain": "paypal-login-secure.top",
        "registered_domain": "paypal-login-secure.top",
        "tld": "top",
        "tld_abuse": "high",
        "registered_at": null,
        "expires_at": null,
        "age_days": null,
        "age_bucket": "unknown",
        "registrar": null,
        "dns": {
          "resolves": false,
          "a": null,
          "mx": null,
          "ns": null
        },
        "status": "gone",
        "cert_first_seen": null,
        "lookalike": {
          "brand_id": "paypal",
          "technique": "brand_word"
        },
        "platform": "none",
        "allowlisted": false,
        "listed": false,
        "seen_in_checks_7d": null,
        "country": null,
        "target_countries": null,
        "list_count": null,
        "site": null,
        "platform_status": null,
        "registry_status": null,
        "hold": null,
        "registered": false,
        "registration_checked_at": "2026-10-30T19:18:58Z",
        "archive": {
          "checked_at": "2026-10-30T19:20:08Z",
          "captured": false,
          "first_capture_at": null,
          "last_capture_at": null,
          "captured_before_registration": null,
          "months_before_registration": null,
          "last_capture_before_registration_at": null
        },
        "first_seen_by_bewary": null,
        "first_listed_publicly": null
      },
      "related": [],
      "datasets": [
        {
          "dataset": "registration",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "dns",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "certificates",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "web_archive",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "threat_feeds",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "threat_aggregates",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "brand_registry",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "platform_pages",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "community_reports",
          "coverage": "WW",
          "in_coverage": true
        }
      ]
    }
  ],
  "signals": [
    {
      "code": "DOMAIN_BRAND_ON_ABUSED_TLD",
      "entity": "e1",
      "severity": "high",
      "weight": 55,
      "source_class": "heuristic",
      "p": null,
      "dataset": "brand_registry",
      "coverage": "WW",
      "evidence": {
        "brand_id": "paypal",
        "tld": "top"
      }
    },
    {
      "code": "DOMAIN_LOOKALIKE_BRAND_WORD",
      "entity": "e1",
      "severity": "medium",
      "weight": 35,
      "source_class": "heuristic",
      "p": null,
      "dataset": "brand_registry",
      "coverage": "WW",
      "evidence": {}
    }
  ],
  "degraded": false,
  "degraded_reasons": [],
  "cached": false,
  "explanation": null
}
```

## Example response: safe

Request: `GET /v1/domain/chase.com`. Captured from the live API on 2026-10-31.

```json
{
  "schema_version": "1.7.0",
  "request_id": "req_01M69WH9P6H5FWFNDK0RSH7VWK",
  "checked_at": "2026-10-31T21:06:03Z",
  "engine": "bewary-engine/3.0.0",
  "models": {
    "calibration": "interim-band-v0"
  },
  "input": {
    "kind": "lookup",
    "channel": "unknown",
    "language": null,
    "region": null
  },
  "verdict": "safe",
  "p_scam": 0,
  "risk_score": 0,
  "confidence": 1,
  "classification": null,
  "reason_codes": [
    "DOMAIN_ALLOWLISTED"
  ],
  "entities": [
    {
      "id": "e1",
      "type": "domain",
      "value": "chase.com",
      "normalized": "chase.com",
      "role": "link_target",
      "verdict": "safe",
      "p_scam": 0,
      "risk_score": 0,
      "reason_codes": [
        "DOMAIN_ALLOWLISTED"
      ],
      "facts": {
        "registrable_domain": "chase.com",
        "registered_domain": "chase.com",
        "tld": "com",
        "tld_abuse": "low",
        "registered_at": "1995-10-11T04:00:00Z",
        "expires_at": "2027-10-10T04:00:00Z",
        "age_days": 11342,
        "age_bucket": "gt_1y",
        "registrar": "MarkMonitor Inc.",
        "dns": {
          "resolves": true,
          "a": [
            "146.143.13.57",
            "146.143.141.57",
            "146.143.83.57"
          ],
          "mx": null,
          "ns": [
            "ns0119.secondary.cloudflare.com",
            "ns0140.secondary.cloudflare.com",
            "ns05.jpmorganchase.com",
            "ns06.jpmorganchase.com",
            "ns1.jpmorganchase.com",
            "ns2.jpmorganchase.com"
          ]
        },
        "status": "live",
        "cert_first_seen": null,
        "lookalike": null,
        "platform": "none",
        "allowlisted": true,
        "listed": false,
        "seen_in_checks_7d": null,
        "country": null,
        "target_countries": null,
        "list_count": null,
        "site": null,
        "platform_status": null,
        "registry_status": [
          "client_delete_prohibited",
          "client_transfer_prohibited",
          "client_update_prohibited",
          "server_delete_prohibited",
          "server_transfer_prohibited",
          "server_update_prohibited"
        ],
        "hold": "none",
        "registered": true,
        "registration_checked_at": "2026-10-30T19:19:01Z",
        "archive": {
          "checked_at": "2026-10-30T19:20:21Z",
          "captured": true,
          "first_capture_at": "1996-11-15T03:41:13Z",
          "last_capture_at": "2026-10-30T19:18:26Z",
          "captured_before_registration": false,
          "months_before_registration": null,
          "last_capture_before_registration_at": null
        },
        "first_seen_by_bewary": null,
        "first_listed_publicly": null
      },
      "related": [],
      "datasets": [
        {
          "dataset": "registration",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "dns",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "certificates",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "web_archive",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "threat_feeds",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "threat_aggregates",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "brand_registry",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "platform_pages",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "community_reports",
          "coverage": "WW",
          "in_coverage": true
        }
      ]
    }
  ],
  "signals": [
    {
      "code": "DOMAIN_ALLOWLISTED",
      "entity": "e1",
      "severity": "info",
      "weight": -60,
      "source_class": "first_party",
      "p": null,
      "dataset": "brand_registry",
      "coverage": "WW",
      "evidence": {}
    }
  ],
  "degraded": false,
  "degraded_reasons": [],
  "cached": false,
  "explanation": null
}
```

## Domain facts

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

| Key | Type | Meaning |
|---|---|---|
| `registrable_domain` | string or null | The owner boundary per the Public Suffix List with its private section: x.pages.dev stays whole. Block by this. |
| `registered_domain` | string or null | The name a registry sold: pages.dev for x.pages.dev. The registration dates and registrar describe this name. |
| `status` | live \| gone \| broken \| parked \| held \| unknown or null | What the domain is doing now. held: its registry or registrar holds it out of the DNS. |
| `tld` | string or null | The top-level domain. |
| `tld_abuse` | low \| medium \| high or null | How often this TLD is used for scams. |
| `registered_at` | timestamp or null | Registration date from the public registry (RDAP). |
| `expires_at` | timestamp or null | When the registration expires unless it is renewed (RDAP). |
| `age_days` | integer or null | Days since registration. |
| `age_bucket` | lt_7d \| d7_30 \| d30_90 \| d90_365 \| gt_1y \| unknown or null | The age as a band, for simple rules. |
| `lookalike` | object or null | The brand it imitates (brand_id) and the technique: homoglyph, typo, brand_word or brand_scam_word. subdomain, tld_swap and userinfo are reserved. |
| `platform` | none \| multi_tenant or null | multi_tenant: a site anyone can publish on, so the domain's age vouches for nothing. |
| `allowlisted` | boolean or null | A well-known legitimate domain. |
| `listed` | boolean or null | On Bewary's scam list. |
| `registrar` | string or null | The registrar's name, from RDAP. |
| `registry_status` | array or null | The registry's EPP status codes for registered_domain, such as server_hold or pending_delete. |
| `hold` | none \| registrar \| registry \| both or null | Who holds the name out of the DNS. A hold is how registries and registrars take a domain down. |
| `registered` | boolean or null | false: the registry says no such name is registered, so anyone may register it. |
| `registration_checked_at` | timestamp or null | When the registration record was read. Names on our lists are read again daily. |
| `dns` | object or null | Whether it resolves, its IPs, MX and name servers. |
| `site` | object or null | Our verdict on the site when our worker read its page in the last 90 days, with a calibrated probability (p_scam_site). |
| `platform_status` | object or null | The hosting platform's own answer about the site, such as a takedown page or a phishing warning. |
| `archive` | object or null | The site's history in a public web archive, including captures from before the current registration. |
| `first_seen_by_bewary` | timestamp or null | When our own watch first saw the host, in a new certificate or a registry zone file. |
| `first_listed_publicly` | timestamp or null | When a public list we read first named the host. |
| `country` | ISO code or null | The country its country-code TLD names. null for generic TLDs. |
| `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'. |
| `cert_first_seen` | timestamp or null | When its first TLS certificate was logged. Coming: null for now. |
| `seen_in_checks_7d` | integer or null | How often it appeared in checks this week. Coming: null for now. |

## Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `host` | path | string | yes | The domain or hostname, e.g. paypal-login-secure.top. Subdomains are fine. |
| `deep` | query | boolean | no | Also read the page with a model (30 credits, API key required). Default `false`. |

## Questions

### How do I get a domain's age with an API?

GET /v1/domain/{host}. facts.registered_at, facts.age_days, facts.expires_at and facts.registrar come from the public registration data (RDAP), and age_bucket gives a band such as lt_7d or gt_1y for simple rules. The free domain age checker at bewary.app/check/domain-age makes the same call.

### Can it detect lookalike and typosquatting domains?

Yes, for well-known brands. facts.lookalike names the brand and the technique: homoglyph (paypa1.com), typo, brand_word (paypal-login.top) or brand_scam_word. Subdomain, TLD swap and userinfo tricks are reserved values, added to the contract and not reported yet.

### Is a new domain a scam?

Not on its own. A domain under 30 days old raises the score, but alone it never makes a scam verdict, because real businesses launch new sites too. It needs a second signal, such as a lookalike brand name.

### How fast is it?

Measured median 449 ms for a domain seen for the first time.

## Reason codes it can return

`LISTED_BLOCKLIST`, `DOMAIN_ALLOWLISTED`, `DOMAIN_AGE_GT_1Y`, `DOMAIN_AGE_90D_1Y`, `DOMAIN_AGE_30_90D`, `DOMAIN_AGE_LT_30D`, `DOMAIN_MULTITENANT_PLATFORM`, `DOMAIN_AGE_UNKNOWN`, `DOMAIN_LOOKALIKE_BRAND_WORD`, `DOMAIN_MIXED_SCRIPT`, `DOMAIN_HOMOGLYPH_BRAND`, `DOMAIN_BRAND_ON_ABUSED_TLD`, `DOMAIN_TYPO_BRAND`, `DOMAIN_BRAND_SCAM_WORD`, `TLD_HIGH_ABUSE`, `DOMAIN_DECEPTIVE_STRUCTURE`, `DOMAIN_CERT_LT_48H`, `LISTED_CAUTION`, `PROTECTIVE_DNS_BLOCKED`, `PLATFORM_TAKEDOWN`, `PLATFORM_WARNING`, `DOMAIN_REGISTRY_HOLD`, `DOMAIN_REGISTRAR_HOLD`
