# IP Geolocation API: Bulk IP Lookup, Country, City & ASN (`solidfetch/ip-geolocation`) Actor

IP geolocation API and GeoIP lookup in bulk: IP to country, city, region, location (lat/long), ASN and ASN organization for IPv4 and IPv6. Paste a list or a server log. Offline IP database (DB-IP Lite), no IP sent to a third party. $0.50 per 1,000. Private, invalid, repeated IPs free.

- **URL**: https://apify.com/solidfetch/ip-geolocation.md
- **Developed by:** [Solid Fetch](https://apify.com/solidfetch) (community)
- **Categories:** Agents, Developer tools, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.30 / 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 API: Bulk IP Lookup, Country, City & ASN

IP geolocation for a list of IP addresses, for example 500 IPs from your server logs: a bulk IP lookup that gives the country, city and network owner (ASN) of each address, one row per IP. It reads IPv4 and IPv6, so you can do IP to country and IP to city for a firewall log or an analytics CSV. $0.50 per 1,000 located addresses; invalid, private and repeated IPs are free. **No IP addresses are sent to third parties: lookups run offline on the DB-IP Lite database.** Location data by DB-IP (CC BY 4.0, see the attribution below).

```json
{ "ips": ["8.8.8.8", "1.1.1.1", "2001:4860:4860::8888", "10.0.0.1"] }
```

One row of the result (shortened; `region` and `city` are filled when the database has them, see [Output](#output)):

```json
{ "input": "8.8.8.8", "ip": "8.8.8.8", "valid": true, "found": true, "countryCode": "US", "country": "United States", "asn": 15169, "asOrganization": "Google LLC", "isHosting": true, "reason": null }
```

Look up thousands of IP addresses in one run for **$0.50 per 1,000 located addresses** on the free plan ($0.30 per 1,000 on Gold and above). For every IPv4 or IPv6 address you get the country, region, city, approximate latitude and longitude, the timezone, the ASN and the AS organisation (who runs the network), and a hosting / cloud provider flag. Invalid text, private addresses, reserved ranges, repeats and addresses missing from the database are free.

The lookup is **offline**. The actor downloads the free DB-IP Lite databases at the start of a run and answers every address from memory. No address you give it is sent to any third-party service, and there is no rate limit or API key to manage.

### What people use it for

- **Enriching a list of IPs** from a log, a CRM export, a form-submissions table or a security report: add country, city and network owner to each one.
- **IP to country** for fraud rules, geo-blocking checks and traffic reports.
- **IP to ASN**: find out which network or hosting provider an address belongs to. `asOrganization` is the name the network registered, for example `Google LLC` or `Cloudflare, Inc.`.
- **Cleaning a column of mixed data.** Private ranges (10.x, 192.168.x), loopback, documentation addresses and typos are marked, not guessed at.
- **Giving an AI agent a lookup it can call** without an API key: one run, a list in, a table out.

### How to use it

A list:

```json
{ "ips": ["8.8.8.8", "1.1.1.1", "2001:4860:4860::8888", "10.0.0.1"] }
```

Or paste a log, a spreadsheet column or any text, and every address in it is found:

```json
{ "text": "Failed password from 203.0.113.9 port 22\nclient [2001:4860:4860::8888]:443 -> 1.1.1.1:53" }
```

Use `ips` when you want every typo reported as a row. Use `text` when the input is messy and you only want the addresses. Both can be used together. Up to 100,000 entries per run.

Entries may carry a port (`8.8.8.8:443`), brackets (`[2001:db8::1]`) or an IPv6 zone (`fe80::1%eth0`); these are removed. An IPv4 address written in IPv6 form (`::ffff:8.8.8.8`) is read as the IPv4 address. IPv6 addresses are returned in their short lowercase form.

### Output

One row per input, in input order. Fields:

| Field | Meaning |
|---|---|
| `input` | The text as entered |
| `ip`, `version` | The address in standard form, and 4 or 6 |
| `valid` | False when the text is not an IP address |
| `isPrivate`, `isReserved` | Private networks, loopback and link-local are private; documentation ranges, multicast and other set-aside ranges are reserved. Neither is looked up |
| `found` | True when the database has an entry for the address |
| `continentCode`, `countryCode`, `country`, `isEu` | Country level data |
| `region`, `city` | State or province name and city name, when the database has them |
| `latitude`, `longitude` | **Approximate** position, see below |
| `timezone` | IANA timezone (`America/Los_Angeles`) of the approximate position, found offline from timezone boundaries. Empty when the row has no position |
| `asn`, `asOrganization` | Autonomous system number and the organisation that owns it |
| `isHosting`, `cloudProvider`, `networkType` | `networkType` is `hosting`, `isp` or `unknown`. `cloudProvider` names the company (Amazon Web Services, Google Cloud, Cloudflare, DigitalOcean, Hetzner, OVHcloud, Azure and about twenty more) when the ASN is a known one. Empty when the row has no ASN. A heuristic, see below |
| `reason`, `explanation` | Empty for a charged row. Otherwise `invalid-ip`, `private-address`, `reserved-address`, `duplicate` or `not-in-database`, with a sentence |
| `duplicateOf` | For a repeat, the first input that had the same address |
| `source` | The DB-IP attribution, see below |
| `databaseMonth` | The month of the database files used, such as `2026-10` |
| `checkedAt` | Time of the run |

The run summary (record `SUMMARY`) counts rows by outcome and by country.

### Location is approximate

IP geolocation places an address at the centre of a city or region, usually where the network operator is registered or routes traffic, not where a device is. DB-IP's free Lite data has reduced coverage and accuracy compared with their paid data: country level is reliable for most addresses, city level is often wrong or empty, and mobile, VPN, satellite and hosting addresses can be far from the user. Do not use it to find a person or a street. It is also a snapshot: the databases are updated monthly.

The Lite data has no timezone field: the `timezone` column is computed from the latitude and longitude with the MIT-licensed `geo-tz` boundary data, so it is as approximate as the position, and empty when the position is empty. The only personal data in an IP lookup is the address you provided. The database has no subscriber, household or ISP customer information.

### How hosting detection works (a heuristic)

`isHosting`, `cloudProvider` and `networkType` come from the ASN and the AS organisation name, not from a list of IP ranges. First the ASN is looked up in a built-in list of well-known cloud and hosting networks; then the organisation name is checked for words such as hosting, datacenter, VPS or cloud (hosting) and telecom, broadband, mobile or cable (isp). Everything else is `unknown` with `isHosting` false. The list is built into the actor, so nothing is fetched at run time and no address leaves the run. Expect misses (small hosts with a neutral name) and false positives (a hosting ASN that also serves consumers, such as Google's `AS15169`, which is marked as hosting). It is a screening signal, not proof that an address is a VPN, proxy or bot.

### Pricing

Pay per event: one `ip-looked-up` event per public address that the database knows and that is not a repeat. Free rows: text that is not an IP address, private and reserved addresses, repeats of an earlier address in the same run, and public addresses with no database entry. A run that reaches your cost limit stops and says how many inputs were not looked up.

### Limits

- 100,000 entries per run. Split bigger lists over several runs.
- Needs 1 GB of memory (set by default): the city database is about 127 MB once unpacked and is held in memory.
- A run downloads about 65 MB at the start. The files are cached per month in a named key-value store (`ip-geolocation-db-cache`) in your account, so later runs in the same month skip the download. DB-IP publishes new files on the 1st of each month; before they appear, the previous month's files are used and `databaseMonth` says which.

### Data source, licence and attribution

IP geolocation data by [DB-IP](https://db-ip.com): the free IP to City Lite and IP to ASN Lite databases, licensed under the Creative Commons Attribution 4.0 International License (https://creativecommons.org/licenses/by/4.0/). Attribution to DB-IP.com is required, so every output row carries it in the `source` field:

`IP Geolocation by DB-IP (https://db-ip.com), IP to City Lite and IP to ASN Lite, CC BY 4.0`

If you publish results from this data on a web page or in an app, keep a link back to DB-IP.com next to them:

<a href='https://db-ip.com'>IP Geolocation by DB-IP</a>

The databases are read with the MIT-licensed `maxmind` Node.js library. This actor is not affiliated with or endorsed by DB-IP.

Did this help? A review on this page, or an issue saying what is missing, helps other people find it.

# Changelog

This Actor's version history is a separate document: https://apify.com/solidfetch/ip-geolocation/changelog.md

# Actor input Schema

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

One IPv4 or IPv6 address per entry. An entry may also hold several addresses separated by commas, semicolons, spaces or line breaks. A port (8.8.8.8:443), brackets (\[::1]) and an IPv6 zone (fe80::1%eth0) are removed. Repeats are dropped (the repeat gets a free row). Use this or the text box below, or both. Up to 100,000 entries per run.

## `text` (type: `string`):

Paste a column from a spreadsheet, a server or firewall log, or any text. Every IPv4 and IPv6 address in it is found and looked up; the other words are ignored. Use the list above if you want every typo reported as a row.

## Actor input object example

```json
{
  "ips": [
    "8.8.8.8",
    "1.1.1.1",
    "9.9.9.9",
    "2001:4860:4860::8888",
    "80.67.169.12",
    "10.0.0.1",
    "not-an-ip"
  ],
  "text": "Failed password from 203.0.113.9 port 22\nclient [2001:4860:4860::8888]:443 -> 1.1.1.1:53"
}
```

# Actor output Schema

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

Dataset view with one row per IP entry: country, region, city, approximate position, timezone, ASN, AS organisation, hosting flags and the reason when a row is free. Open it for the main output.

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

JSON record SUMMARY in the key-value store: counts of rows by outcome (looked up, invalid, private, reserved, duplicate, not in database) and by country.

# 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",
        "2001:4860:4860::8888",
        "80.67.169.12",
        "10.0.0.1",
        "not-an-ip"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("solidfetch/ip-geolocation").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",
        "2001:4860:4860::8888",
        "80.67.169.12",
        "10.0.0.1",
        "not-an-ip",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("solidfetch/ip-geolocation").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",
    "2001:4860:4860::8888",
    "80.67.169.12",
    "10.0.0.1",
    "not-an-ip"
  ]
}' |
apify call solidfetch/ip-geolocation --silent --output-dataset

```

## MCP server setup

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

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/8iLCDcbeK5BLiPJkt/builds/o062uL7FoJZ9h1bYL/openapi.json
