Egress Report

Home › Implementation › IP to Company API Python Example: Single and Bulk Lookups

IP to Company API Python Example: Single and Bulk Lookups

Editorial team · Updated 2026-10-14 · 5 min read

A minimal IP to company integration in Python reads the API key from an environment variable, sends a bearer-authenticated GET for one address, then branches on the verdict field. For many addresses, chunk them into batches of 100 and post to the bulk route. The sketch below follows BusinessMCP’s documented routes.

What the API looks like

Per the IP to company API page:

  • Single lookup: GET /api/hub/v1/ip/{ip}
  • Bulk lookup: POST /api/hub/v1/ip/bulk with {"ips": [...]} of 1 to 100 addresses
  • Auth: bearer API key
  • Verdicts: company, non_business, unidentified
  • Price: $0.02 per lookup, charged only when a result is found, with $5 free credit for 7 days

The page does not list every response field; those are in the API documentation. The code below only relies on verdict and, for named companies, a company object with a domain, as the page describes. Check field names against the live reference before shipping.

Setup

pip install requests
export BUSINESSMCP_API_KEY="your-key"
export IP_API_BASE="https://businessmcp.com"   # confirm the host in the docs

Using a variable for the base URL means you can change it without touching code.

Single lookup

import os
import requests

BASE = os.environ["IP_API_BASE"]
KEY = os.environ["BUSINESSMCP_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}

def lookup(ip: str, timeout: float = 5.0) -> dict | None:
    r = requests.get(f"{BASE}/api/hub/v1/ip/{ip}", headers=HEADERS, timeout=timeout)
    if r.status_code != 200:
        return None            # log it; do not treat as a company
    return r.json()

def company_or_none(data: dict | None):
    if not data or data.get("verdict") != "company":
        return None            # everything else is unknown
    return data.get("company")

The key rule sits in company_or_none: anything other than company is unknown, which is exactly what the product documentation advises.

Bulk lookup with chunking

import time

def chunks(items, n=100):
    for i in range(0, len(items), n):
        yield items[i:i + n]

def bulk(ips, retries=3):
    results = []
    for batch in chunks(sorted(set(ips))):
        for attempt in range(retries):
            try:
                r = requests.post(f"{BASE}/api/hub/v1/ip/bulk",
                                  headers=HEADERS, json={"ips": batch}, timeout=30)
                if r.status_code == 200:
                    results.append(r.json())
                    break
            except requests.RequestException:
                pass
            time.sleep(2 ** attempt)     # simple backoff
    return results

Notes on the design:

  • sorted(set(ips)) deduplicates and gives a stable order.
  • Each batch is retried a few times with backoff, then skipped; log skipped batches so you can re-run them.
  • The response shape for bulk is described as the same fields as a single lookup for each address, but confirm how results are wrapped before parsing.

Key takeaway: the integration is small. The work is in what you do around it: dedupe, filter infrastructure, store every verdict and respect retention.

Wiring it to real traffic

Step Where Detail
Get client IP Edge or app Use the forwarded address only when the request verifiably came through your proxy
Skip private ranges Before the call Use the ipaddress module’s is_private and is_loopback
Deduplicate Job level Per day, per address
Resolve API call Single for live, bulk for batch
Store Database Verdict, class, name, domain, date
Expire Scheduler Re-resolve after a few days

A small helper for the second step:

import ipaddress

def routable(ip: str) -> bool:
    try:
        a = ipaddress.ip_address(ip)
    except ValueError:
        return False
    return not (a.is_private or a.is_loopback or a.is_link_local or a.is_reserved)

Testing the integration

  1. Run it against five addresses you know: your office, a home line, a mobile hotspot, a cloud VM and a known customer’s office if you have one.
  2. Confirm that only the office and customer rows return company.
  3. Compare your balance before and after to see how billing treats the others.
  4. Feed it a malformed string and confirm the code does not crash or bill.

For a broader evaluation across vendors, use the method in how to test an IP to company API. For cost projections see IP to company API pricing.

Production hardening

  • Set explicit timeouts, as shown, so a slow upstream never hangs a request thread.
  • Never block a page render on a lookup; resolve asynchronously after the response.
  • Cache results by IP with a short expiry.
  • Redact the key from logs and error reports.
  • Keep raw addresses only as long as your retention policy allows; see GDPR and IP lookup.
  • Monitor the share of company verdicts over time; a sudden jump or drop signals a bug, such as logging a proxy address.

Once results flow in, you can feed them into lead scoring or traffic-quality reports like those in filtering bot and cloud traffic.

Reading the results into a table

Once you have responses, a small function flattens them into rows you can load into a spreadsheet or database. Adjust the keys to the response format in the API documentation.

def to_row(ip: str, data: dict) -> dict:
    company = data.get("company") or {}
    return {
        "ip": ip,
        "verdict": data.get("verdict"),
        "company_name": company.get("name"),
        "company_domain": company.get("domain"),
    }

The .get() calls matter. A field you expect may be absent for non-company verdicts, and code that indexes directly will crash on the first ISP address. Write defensively, store the raw response alongside the flattened row, and you can re-parse later if the schema changes or you want fields you ignored the first time.

Unit tests worth writing

  • A company response returns a name and domain.
  • A non_business response returns None from company_or_none.
  • An unidentified response returns None.
  • A 500 response is retried and then skipped without raising.
  • A malformed address never reaches the network call.

Mock the HTTP layer with a small fake rather than calling the real API in tests, so your suite stays free and fast. Keep one slow integration test, run manually, that checks your control addresses against the live service. The control-set idea is described in how to test an IP to company API.

Alternatives to hand-rolled code

For a one-off analysis, you may not need a script. A spreadsheet with a column of addresses and a small macro, or the free web lookup for a handful of addresses, is often faster. Write code when the job repeats, when volume passes a few hundred addresses, or when the result must flow into another system. When you do, keep the client in one small module so that swapping vendors later means changing one file and a field mapping rather than rewriting your pipeline.

Frequently asked questions

How do I call an IP to company API from Python?

Send an authenticated HTTP request with the requests library, parse the JSON response and branch on the verdict field. Handle timeouts and non-200 responses explicitly.

Where should I store the API key?

In an environment variable or secrets manager, never in source code. BusinessMCP’s examples read it from BUSINESSMCP_API_KEY.

How do I handle rate limits?

Retry with exponential backoff and a cap. The product page does not state rate limits, so check the documentation and test.

Should the code store the raw IP?

Only as long as needed. Keep the company-level result and drop or truncate the raw address in line with your retention policy.