# IP Geolocation & ASN Lookup: Country, City & ISP in Bulk (`data-done-right/ip-geolocation-lookup`) Actor

Look up IP addresses in bulk, offline: country, region, city, coordinates, ASN and network operator from DB-IP Lite data. IPv4 and IPv6, no external API call. City-level accuracy at best, not a street address. IP Geolocation by DB-IP (CC BY 4.0).

- **URL**: https://apify.com/data-done-right/ip-geolocation-lookup.md
- **Developed by:** [Data Done Right](https://apify.com/data-done-right) (community)
- **Categories:** Developer tools, Agents, Other
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 ip looked ups

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## IP Geolocation & ASN Lookup: Country, City & ISP in Bulk

**IP geolocation in bulk:** paste a list of IPv4 and IPv6 addresses and get the country, region, city, approximate coordinates, ASN and network operator (ISP, hosting company or cloud) for each one. The lookups run offline inside the Actor on the free DB-IP Lite database, so no IP is sent to a third-party API and no API key is needed.

> **What this does NOT do.** It does not give a street address, a postal code or the location of a person or device: the free database is accurate to the country in most cases and to the city only some of the time, and the coordinates are a reference point for the whole IP range. It does not detect VPNs, proxies, Tor or hosting traffic as a risk flag (the ASN operator name often hints at it, nothing more). It does not do reverse DNS, WHOIS or abuse scoring. Mobile and satellite networks, VPNs and anycast addresses (such as 1.1.1.1) show where the range is registered or announced, not where the user is. Private and reserved addresses have no location and are returned as `non-public` rows.

**What you get**

- One row per IP: `status`, `country`, `countryCode`, `region`, `city`, `latitude`, `longitude`, `isEuMember`, `continent`.
- Network data: `asn`, `asnOrg`, `asnLabel` (for example `AS15169 Google LLC`) and the database block (`network`, CIDR).
- Clean handling of bad input: private, loopback and reserved addresses and malformed entries are flagged, not charged, and never stop the run.

Typical use: enrich server logs, signup lists or firewall exports, spot traffic by country or by network operator, and prepare geo reports. Available through the Apify API, CSV/Excel/JSON export, and as a tool for AI agents (MCP).

**Attribution (CC BY 4.0): IP Geolocation by DB-IP** ([db-ip.com](https://db-ip.com)). This Actor uses the DB-IP Lite City and ASN databases, which DB-IP publishes under the Creative Commons Attribution 4.0 licence. Every output row carries `attribution: "IP Geolocation by DB-IP"` and `attributionUrl`. If you show or republish the data, keep the credit. This Actor is not endorsed by or affiliated with DB-IP.

### How to look up the country and city of many IP addresses

1. Open the Actor and paste your IPs in **IP addresses** (one per line; IPv4, IPv6, with or without a port). You can also paste a block of text (a log excerpt works) or point to an Apify dataset. Sources are combined.
2. Set **Maximum IP addresses** (default 1000). It caps the cost: 1,000 IPs cost at most $2.50.
3. Click **Start**. Lookups are local file reads, so even long lists finish quickly.
4. Download the dataset as CSV, Excel or JSON. The **Overview**, **Location**, **Network**, **Not located** and **With attribution** views are built in. The run summary (counts by status, top countries and ASNs) is in the `SUMMARY` record.

### How to find the ASN and network operator of an IP address

Every located row has `asn` (number), `asnOrg` (registered organisation) and `network` (the database block, for example `8.8.8.0/24`). Filter the dataset on `asn` to group traffic by operator, or look for cloud and hosting names in `asnOrg` to separate data-centre traffic from consumer ISPs. The ASN data answers who announces the range, not who uses the address.

### How to clean an IP list before analysis

Leave **Remove duplicates** on. Duplicates (including `::ffff:8.8.8.8` against `8.8.8.8`) are merged. Rows with `status: non-public` are private, loopback, link-local, multicast, documentation or reserved addresses, and `invalid` rows are text that is not an IP. Both are free. Use the **Not located** view to see them with the reason in `error`.

### How to geolocate IPs from a scraper's or log export's dataset

Pick the dataset in **Dataset with IPs** and set **Dataset field** to the field that holds the address (`ip`, or a dotted path such as `client.address`; a field with a list of IPs also works). Output rows carry `source: "dataset"` and the 1-based `position`.

### Input

```json
{
  "ips": ["8.8.8.8", "1.1.1.1", "9.9.9.9", "2606:4700:4700::1111", "193.99.144.80", "10.0.0.1", "not-an-ip"],
  "maxIps": 1000,
  "language": "en"
}
```

| Field | Default | What it does |
|---|---|---|
| `ips` | 7 sample IPs | IPv4 or IPv6 addresses, one per line. Ports, `[v6]:port` and zone ids are stripped. |
| `ipsText` | empty | Pasted text; IPs split on new lines, commas, semicolons, spaces. |
| `datasetId` | empty | Apify dataset to read IPs from. |
| `datasetField` | `ip` | Field holding the IP (dotted path, list values allowed). |
| `maxIps` | 1000 | Maximum unique entries per run (1 to 1,000,000); caps the cost. |
| `deduplicate` | true | Look each address up once; duplicates are not charged. |
| `language` | `en` | Language of country and continent names (`en`, `de`, `es`, `fr`, `ja`, `pt-BR`, `ru`, `zh-CN`). City and region names are always English. |

### Output

Real output of the input above (shortened to three rows, English):

```json
[
  {
    "ip": "8.8.8.8",
    "inputValue": "8.8.8.8",
    "version": 4,
    "status": "found",
    "ipType": "public",
    "country": "United States",
    "countryCode": "US",
    "isEuMember": false,
    "continent": "North America",
    "continentCode": "NA",
    "region": "California",
    "city": "Mountain View",
    "latitude": 37.422,
    "longitude": -122.085,
    "asn": 15169,
    "asnOrg": "Google LLC",
    "asnLabel": "AS15169 Google LLC",
    "network": "8.8.8.0/24",
    "error": null,
    "databaseDate": "2026-11-01",
    "attribution": "IP Geolocation by DB-IP",
    "attributionUrl": "https://db-ip.com",
    "source": "list",
    "position": 1
  },
  {
    "ip": "193.99.144.80",
    "status": "found",
    "country": "Germany",
    "countryCode": "DE",
    "isEuMember": true,
    "region": "Lower Saxony",
    "city": "Hanover (Buchholz-Kleefeld)",
    "asn": 12306,
    "asnOrg": "Plus.line AG",
    "network": "193.99.144.0/24",
    "attribution": "IP Geolocation by DB-IP"
  },
  {
    "ip": "10.0.0.1",
    "status": "non-public",
    "ipType": "private",
    "country": null,
    "error": "private address: not on the public internet, so it has no location",
    "attribution": "IP Geolocation by DB-IP"
  }
]
```

Every row has all fields (null when unknown). `checkedAt` is the UTC time of the lookup. Note the 1.1.1.1 row: the database says Sydney, Australia, because that is where the range is registered, although the address is served from servers worldwide (anycast).

#### Statuses

| `status` | Meaning | Charged |
|---|---|---|
| `found` | The city or ASN database has a record for this public IP. | yes |
| `not-found` | Valid public IP that neither database covers (unallocated or unrouted ranges). | yes |
| `non-public` | Private, loopback, link-local, multicast, unspecified, shared (100.64.0.0/10), documentation or reserved address. `ipType` says which. | no |
| `invalid` | The entry is not an IPv4 or IPv6 address. `error` says why. | no |

A `found` row can be partial: some ranges have only a country (no city or coordinates), some only an ASN.

### Data source

The Docker image downloads `dbip-city-lite` and `dbip-asn-lite` (monthly `.mmdb` files) from `download.db-ip.com` when the Actor is built, trying the current month first and the previous month second. The `databaseDate` field in every row and in `SUMMARY` is the build date of the database actually used. The database is refreshed when the Actor is rebuilt, so a long-lived build can be a few weeks old; check `databaseDate` if freshness matters. Licence: CC BY 4.0, see [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php).

### How much does it cost

One event, **`ip-looked-up`: $0.0025 per public IP address looked up** ($2.50 per 1,000). Not charged: `non-public` and `invalid` rows and removed duplicates.

| IP addresses | Cost (all public) |
|---|---|
| 1,000 | $2.50 |
| 10,000 | $25.00 |
| 100,000 | $250.00 |

If 5% of a 10,000-row list is private or malformed you pay for 9,500 rows ($23.75). Set `maxIps` or a spending limit on the run to cap the bill; the Actor stops cleanly when the limit is reached.

### Use cases

- Add country and city to web server or application logs before analysis.
- Segment signups, trials or form submissions by country and flag mismatches with the billing country.
- Group traffic by ASN to separate cloud and hosting networks from consumer ISPs.
- Build a per-country or per-operator report from a firewall or intrusion log export.
- Pre-filter an IP list: drop private and malformed entries and merge duplicates before a paid, deeper check.

### FAQ

**How accurate is it?** Country level is right for most addresses. City level is right only some of the time, often within tens of kilometres and sometimes much further. The free DB-IP Lite data is not a street-level product, and coordinates are a reference point of the whole range. Do not use it for anything that needs a precise location.

**Does it detect VPNs, proxies or Tor?** No. There is no proxy, VPN or Tor flag. The operator name in `asnOrg` can hint at a hosting provider, nothing more.

**Are my IPs sent to a third party?** No. The lookups run inside the Actor against a local database file. Only the usual Apify run storage holds your input and output.

**How current is the data?** `databaseDate` in each row shows the build date of the database (monthly releases). IP allocations change constantly, so recent moves may be wrong.

**Does it support IPv6?** Yes. IPv6 coverage in the free database is smaller than IPv4, so more IPv6 rows come back with only country or ASN.

**What happens with private or invalid IPs?** They get a `non-public` or `invalid` row with the reason in `error`, are not charged, and the run continues.

### Use with the Apify API, MCP, n8n and Make

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("YOUR_USERNAME/ip-geolocation-lookup").call(run_input={"ips": ["8.8.8.8", "2606:4700:4700::1111"]})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["ip"], row["status"], row["countryCode"], row["city"], row["asnLabel"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('YOUR_USERNAME/ip-geolocation-lookup').call({ ips: ['8.8.8.8', '2606:4700:4700::1111'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((r) => [r.ip, r.countryCode, r.city, r.asnLabel]));
```

In n8n or Make use the Apify node/module "Run Actor", pass `ips` (or `datasetId` from a previous step) and read the dataset items. For AI agents the Actor is available through the Apify MCP server; the input is a single `ips` array.

### Running locally

Download `dbip-city-lite-YYYY-MM.mmdb.gz` and `dbip-asn-lite-YYYY-MM.mmdb.gz` from [db-ip.com/db/lite.php](https://db-ip.com/db/lite.php), unzip them as `city.mmdb` and `asn.mmdb` into a folder, and set the `DBIP_DIR` environment variable to that folder (`tools/fetch_dbip.py <folder>` does this for you). Then run `python -m src`.

### Related tools

- Domain Age Checker: Registration Date, Expiry & RDAP Lookup (what a domain is, to pair with where an IP is)
- HTTP Status Code Checker: Bulk URL, Website & Redirect Chains (check the sites behind the IPs)
- Verify Email Addresses in Bulk: Syntax, MX & Disposable (clean the signup list you are geolocating)

IP Geolocation by DB-IP, https://db-ip.com (CC BY 4.0).

# Actor input Schema

## `ips` (type: `array`):

IPv4 or IPv6 addresses, one per line (use Bulk edit to paste a long list). Example: 8.8.8.8 or 2606:4700:4700::1111. A trailing :port or \[v6]:port is accepted.

## `ipsText` (type: `string`):

Alternative to the list: paste a block of text (a log excerpt, a CSV column) with IPs separated by new lines, commas, semicolons or spaces. Example: 8.8.8.8, 1.1.1.1; 9.9.9.9. Tokens that are not IPs show up as invalid rows.

## `datasetId` (type: `string`):

Pick an Apify dataset (for example a scraper's or a log export's output); the field below is read from every item. Example: a dataset ID such as aBcD1234eFgH5678i.

## `datasetField` (type: `string`):

Field of each dataset item that holds the IP (or a list of IPs). Dotted paths work. Example: ip or client.address. Default: ip.

## `maxIps` (type: `integer`):

Upper limit of unique entries processed in this run. This also caps the cost: at $0.0025 per looked-up IP, 1000 IPs cost at most $2.50.

## `deduplicate` (type: `boolean`):

Process each address once (IPv4-mapped IPv6 such as ::ffff:8.8.8.8 counts as 8.8.8.8). Duplicates are counted in the run summary and are not charged. Example: true.

## `language` (type: `string`):

Language of the country and continent names. City and region names are always English (the free database has no translations for them). ISO codes are unaffected. Default: English.

## Actor input object example

```json
{
  "ips": [
    "8.8.8.8",
    "1.1.1.1",
    "9.9.9.9",
    "2606:4700:4700::1111",
    "193.99.144.80",
    "10.0.0.1",
    "not-an-ip"
  ],
  "datasetField": "ip",
  "maxIps": 1000,
  "deduplicate": true,
  "language": "en"
}
```

# Actor output Schema

## `results` (type: `string`):

One row per IP: status, country, region, city, coordinates, ASN, network operator, database date and the DB-IP attribution (all items as JSON, CSV, Excel and more).

## `summary` (type: `string`):

SUMMARY record: counts by status, top countries and ASNs, database date, attribution, duplicates removed, charge count and notes.

## `records` (type: `string`):

Key-value store records of this run.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "ips": [
        "8.8.8.8",
        "1.1.1.1",
        "9.9.9.9",
        "2606:4700:4700::1111",
        "193.99.144.80",
        "10.0.0.1",
        "not-an-ip"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("data-done-right/ip-geolocation-lookup").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "ips": [
        "8.8.8.8",
        "1.1.1.1",
        "9.9.9.9",
        "2606:4700:4700::1111",
        "193.99.144.80",
        "10.0.0.1",
        "not-an-ip",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("data-done-right/ip-geolocation-lookup").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "ips": [
    "8.8.8.8",
    "1.1.1.1",
    "9.9.9.9",
    "2606:4700:4700::1111",
    "193.99.144.80",
    "10.0.0.1",
    "not-an-ip"
  ]
}' |
apify call data-done-right/ip-geolocation-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,data-done-right/ip-geolocation-lookup"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/bjcLbCzrrUmGRtZcO/builds/zJ5PBbybOsX67u8gX/openapi.json
