# IP address lookup API

Bewary's IP API returns the network that owns an address (ASN and name), the registry country of the address block, what kind of network it is (datacenter, CDN, residential, mobile or business) and its reverse DNS name. Use it as facts for your own rules: an address alone rarely decides a scam. A call costs 1 credit, a free key gets 1,000 credits a month, no card, and it answers in median 260 ms.

Updated 2026-10-30.

## At a glance

- Endpoint: GET https://api.bewary.app/v1/ip/{ip}
- Price: 1 credit
- Free key: 1,000 credits a month, no card
- Speed: Median 260 ms, measured
- Returns: verdict, p_scam, entities with typed facts, reason codes
- MCP tool: lookup_ip

## Used for

- Telling datacenter traffic from home users.
- Checking where a link or sender is hosted.
- Enriching logs with the network owner.

## Call it

curl:

```bash
curl "https://api.bewary.app/v1/ip/1.1.1.1"
```

Python:

```python
import requests

r = requests.get("https://api.bewary.app/v1/ip/1.1.1.1", 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/ip/1.1.1.1")
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 is_datacenter(ip):
    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/ip/{ip}", headers=headers, timeout=10)
    r.raise_for_status()
    return r.json()["entities"][0]["facts"]["hosting_type"] in ("datacenter", "cdn")

print(is_datacenter("8.8.8.8"))   # True
```

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

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

export async function isDatacenter(ip: string): Promise<boolean> {
  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/ip/${ip}`, { headers, signal: AbortSignal.timeout(10_000) })
  if (!res.ok) throw new Error(`Bewary ${res.status}`)
  const e = (await res.json() as Envelope).entities[0]
  return e.type === "ip" && (e.facts.hosting_type === "datacenter" || e.facts.hosting_type === "cdn")
}
```

## The fields you will use

- `entities[0].facts.hosting_type`: datacenter, cdn, residential, mobile, business or unknown.
- `entities[0].facts.as_org`: The network owner.
- `entities[0].facts.asn`: The autonomous system number.
- `entities[0].facts.country`: Registry country of the address block.

## Example response: unknown

Request: `GET /v1/ip/1.1.1.1`. Captured from the live API on 2026-10-31.

```json
{
  "schema_version": "1.7.0",
  "request_id": "req_01M69WHH116VSWFG4NPD6WSKBG",
  "checked_at": "2026-10-31T21:06:10Z",
  "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": "ip",
      "value": "1.1.1.1",
      "normalized": "1.1.1.1",
      "role": "mentioned",
      "verdict": "unknown",
      "p_scam": 0.2,
      "risk_score": 20,
      "reason_codes": [],
      "facts": {
        "version": 4,
        "asn": 13335,
        "as_org": "CLOUDFLARENET - Cloudflare, Inc., US",
        "country": "AU",
        "hosting_type": "cdn",
        "reverse_dns": "one.one.one.one",
        "listed": null
      },
      "related": [],
      "datasets": [
        {
          "dataset": "ip_network",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "threat_feeds",
          "coverage": "WW",
          "in_coverage": true
        },
        {
          "dataset": "community_reports",
          "coverage": "WW",
          "in_coverage": true
        }
      ]
    }
  ],
  "signals": [],
  "degraded": false,
  "degraded_reasons": [],
  "cached": false,
  "explanation": null
}
```

## IP address facts

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

| Key | Type | Meaning |
|---|---|---|
| `version` | 4 \| 6 or null | IP version. |
| `asn` | integer or null | The autonomous system number. |
| `as_org` | string or null | The network owner. |
| `country` | ISO code or null | The registry country of the address block. |
| `hosting_type` | datacenter \| cdn \| residential \| mobile \| business \| unknown or null | What kind of network it is, estimated from the network owner. |
| `reverse_dns` | string or null | The PTR name. |
| `listed` | boolean or null | IPs are not put on the scam list, because many sites share one address. |

## Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `ip` | path | string | yes | An IPv4 or IPv6 address. |

## Questions

### Can I tell if an IP is a datacenter or a home connection?

Yes. facts.hosting_type is datacenter, cdn, residential, mobile, business or unknown, estimated from the network owner's name.

### Does it detect VPNs and proxies?

No. It reports the network and its type, not whether a VPN or proxy is in use.

### Why does 1.1.1.1 show country AU?

country is the registry country of the address block, which is not always where the server stands. 1.1.1.1 was allocated through the Asia-Pacific registry.

## Reason codes it can return

`LISTED_BLOCKLIST`, `IP_LISTED`, `IP_DATACENTER_FOR_SENDER`, `LISTED_CAUTION`, `PLATFORM_TAKEDOWN`, `PLATFORM_WARNING`
