# Overview (https://docs.ipalmanac.com/) > Who else is on this IP, and since when. Reverse IP, DNS history and network context from one REST API and an MCP server. IP Almanac answers questions about public IPs and domains from DNS answers we record ourselves, plus the IP ranges providers publish and an imported ASN mapping. Look up an IP for the domains seen on it, its PTR name and the network that routes it. Look up a domain for everywhere it has pointed and the networks it moved through. Every DNS answer is dated: when we first observed it, when we last confirmed it, and when it ended. > **Private preview:** To ask for an invitation, email [support@ipalmanac.com](mailto:support@ipalmanac.com). New workspaces start on the > Free plan; Starter and Pro are not on sale yet. ```bash curl https://api.ipalmanac.com/v1/ip/54.10.20.30 \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` ```json title="Response (a made-up example, trimmed)" { "ip": "54.10.20.30", "asn": { "asn": 64500, "descr": "EXAMPLE-CLOUD - Example Cloud, Inc.", "country": "US", "source": "IPtoASN" }, "is_datacenter": true, "is_cdn": null, "provider": "examplecloud", "prefix": "54.10.20.0/24", "ptr": { "names": ["host-54-10-20-30.examplecloud.example"], "status": "positive" }, "domains": { "current_count": 2, "sample": ["alpha.example", "gamma.example"], "first_observed": "2026-10-01T03:10:00+00:00", "last_confirmed": "2026-10-06T01:40:00+00:00" }, "release": { "id": "20261006T020000Z", "built_at": "2026-10-06T02:00:00+00:00" } } ``` - [Quickstart](https://docs.ipalmanac.com/quickstart): Create a key and make your first lookup. - [API reference](https://docs.ipalmanac.com/api-reference): Each REST operation, with its parameters, responses and errors. - [Read a response](https://docs.ipalmanac.com/reading-answers): What null, empty, stale and the dates mean. - [MCP and AI agents](https://docs.ipalmanac.com/mcp): Give an agent the same lookups as tools. ## What you can ask | Question | Operation | | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | What is this IP: its network, cloud, hosting or CDN range, PTR name, and how many domains are on it? | [`GET /v1/ip/{ip}`](https://docs.ipalmanac.com/api-reference/lookup-ip) | | Which domains were seen on this IP, and when? | [`GET /v1/ip/{ip}/domains`](https://docs.ipalmanac.com/api-reference/ip-domains) | | What has this domain resolved to over time? | [`GET /v1/domain/{domain}/history`](https://docs.ipalmanac.com/api-reference/domain-history) | | Which networks has this domain moved through? | [`GET /v1/domain/{domain}/hosting`](https://docs.ipalmanac.com/api-reference/domain-hosting) | | What does this network (AS number) route, and what kind of network is it? | [`GET /v1/asn/{asn}`](https://docs.ipalmanac.com/api-reference/lookup-asn) | The same five lookups are [MCP tools](https://docs.ipalmanac.com/mcp) for AI agents. ## Basics * **Base URL:** `https://api.ipalmanac.com`. HTTPS only, and no trailing slash on paths. * **Authentication:** an API key in the `Authorization: Bearer` header. See [Authentication](https://docs.ipalmanac.com/authentication). * **Format:** every lookup is a `GET` that returns JSON. Times are UTC, in ISO 8601. * **Limits:** a monthly quota and a per-minute rate per workspace. See [Rate limits and quotas](https://docs.ipalmanac.com/rate-limits). * **Spec:** the OpenAPI document is at [`https://api.ipalmanac.com/v1/openapi.json`](https://api.ipalmanac.com/v1/openapi.json). ## What it is not We report what we observe, what operators publish, and a classification of the network that routes an IP: ours where we have one and ipverse's where we don't, labeled with its source. We don't offer geolocation, abuse or reputation scores, WHOIS or company ownership, and we don't guess: unknown stays `null`. [Data and coverage](https://docs.ipalmanac.com/data-and-coverage) says where the data is strong and where it is thin. # Quickstart (https://docs.ipalmanac.com/quickstart) > Create an API key, check it, and look up your first IP. ### Create an API key 1. Sign in at [app.ipalmanac.com](https://app.ipalmanac.com) with your email. We send a sign-in link; there is no password. During the private preview only invited emails can sign in; ask at [support@ipalmanac.com](mailto:support@ipalmanac.com). 2. The first time, agree to the Terms. Your workspace and a project named **Default** are created for you. 3. Open [API keys](https://app.ipalmanac.com/dashboard/api-keys), choose **Create Key** and give it a label. 4. Copy the key. It is shown once. Keep the key out of your code by putting it in an environment variable: ```bash export IPALMANAC_API_KEY="" ``` ### Check the key [`GET /v1/ping`](https://docs.ipalmanac.com/api-reference/ping) answers with the server's time when the key works. ```bash curl https://api.ipalmanac.com/v1/ping \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` ```python import os import requests response = requests.get( "https://api.ipalmanac.com/v1/ping", headers={"Authorization": f"Bearer {os.environ['IPALMANAC_API_KEY']}"}, timeout=30, ) print(response.status_code, response.json()) ``` ```js const response = await fetch("https://api.ipalmanac.com/v1/ping", { headers: { Authorization: `Bearer ${process.env.IPALMANAC_API_KEY}` }, }); console.log(response.status, await response.json()); ``` ```json { "ok": true, "timestamp": "2026-10-08T21:54:16.482913+00:00" } ``` A `401` means the key is missing, mistyped, revoked or expired. A ping counts as one request. ### Look up an IP [`GET /v1/ip/{ip}`](https://docs.ipalmanac.com/api-reference/lookup-ip) says what was observed about one public IP. ```bash curl https://api.ipalmanac.com/v1/ip/8.8.8.8 \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` ```python import os import requests response = requests.get( "https://api.ipalmanac.com/v1/ip/8.8.8.8", headers={"Authorization": f"Bearer {os.environ['IPALMANAC_API_KEY']}"}, timeout=30, ) response.raise_for_status() answer = response.json() print(answer["asn"], answer["ptr"], answer["domains"]["current_count"]) ``` ```js const response = await fetch("https://api.ipalmanac.com/v1/ip/8.8.8.8", { headers: { Authorization: `Bearer ${process.env.IPALMANAC_API_KEY}` }, }); if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`); const answer = await response.json(); console.log(answer.asn, answer.ptr, answer.domains.current_count); ``` The answer has these parts: | Field | What it says | | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `asn` | The network that routes the IP: number, name, registry country, and what kind of network it is (`category`). | | `is_datacenter`, `is_cdn`, `provider`, `prefix` | Whether the IP is in a range a cloud or hosting provider (`is_datacenter`) or a CDN (`is_cdn`) publishes, and whose. `null` means not in one we have, never "no". | | `provider_ranges` | Every published range that holds the IP, including Tor exit, iCloud Private Relay and crawler lists. | | `ptr` | The IP's reverse DNS name, when we have checked it. | | `domains` | How many domains' latest observed answer includes the IP, a sample of up to 10, and when we first and last saw one. | | `coverage`, `attribution`, `release` | What the answer is based on, the sources to credit, and the data snapshot it came from. | The [reference](https://docs.ipalmanac.com/api-reference/lookup-ip) shows an example response with every part. ### List the domains on it [`GET /v1/ip/{ip}/domains`](https://docs.ipalmanac.com/api-reference/ip-domains) lists the domains on the IP now, with dates. Add `include_ended=true` to include domains that have since moved away. ```bash curl "https://api.ipalmanac.com/v1/ip/8.8.8.8/domains?include_ended=true" \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` A long list comes in pages: pass the answer's `next_cursor` back as `cursor`. See [Pagination](https://docs.ipalmanac.com/pagination). ## Next * [Read a response](https://docs.ipalmanac.com/reading-answers): what `null`, empty lists, `stale` and the dates mean. * [Use cases](https://docs.ipalmanac.com/use-cases/shared-hosting): worked investigations. * [MCP and AI agents](https://docs.ipalmanac.com/mcp): give an agent the same lookups. To try requests without writing code, open a project's **Playground** page in the console. It runs these lookups and shows each one as cURL, JavaScript and Python. Owners and admins can run requests there, and each run counts like any other request. # Authentication (https://docs.ipalmanac.com/authentication) > Send an API key in the Authorization header. Keys belong to a project and share the workspace's limits. Requests need an API key in the `Authorization` header, as a bearer token: ```http GET /v1/ip/8.8.8.8 HTTP/1.1 Host: api.ipalmanac.com Authorization: Bearer ``` A few calls need no key: [`GET /health`](https://docs.ipalmanac.com/api-reference/health), the OpenAPI spec at `/v1/openapi.json`, and listing the [MCP](https://docs.ipalmanac.com/mcp) server's tools. ## Create a key Keys are made in the console, under a project's [API keys](https://app.ipalmanac.com/dashboard/api-keys). * **Label:** required, so you can tell keys apart later (for example "Production server"). * **Expiry:** optional. A key never expires unless you set a date. * **Shown once:** copy the key when it appears. Afterwards the console shows only its first and last characters. * **Confirm it's you:** if you signed in more than ten minutes ago, the console first asks for a code we email you (or your authenticator app's code, if you turned two-factor sign-in on). Workspace owners and admins can create and revoke keys; members can see them and their usage. A workspace can have at most 25 active keys. Revoked and expired keys don't count. The console's **Playground** page makes its own key in the project the first time someone runs a request there. It is labeled **Playground**, never leaves the console, and counts toward the 25 like any other key. It expires after 90 days and is replaced on the next run; you can revoke it from the keys list. ## Keep keys secret A key starts with `sk_`. Treat it like a password: * **Never put it in a URL.** A request with a key in a query parameter (`api_key`, `apikey`, `key`, `token` or `authorization`) is refused with `401`, so the key doesn't end up in logs and browser history. * **Call the API from your server, not a web page.** The API accepts browser requests only from the console, and a key in front-end code is public. * **Use one key per app or environment,** so you can revoke one without breaking the others. ## Rotate or revoke a key To rotate, create a new key, deploy it, then revoke the old one. Revoking takes effect immediately and can't be undone. ## When a key is refused | Response | Why | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `401` `unauthorized` | The header is missing, the key is in the URL, or the key is mistyped, revoked or expired. A mistyped, revoked or expired key gets the same message, so the response doesn't tell anyone which keys exist. | | `429` `rate_limited` | More than 100 refused or missing keys from your address in one minute (per /64 for IPv6). Wait the `Retry-After` seconds. | The key is checked before the parameters, so a bad key gets `401` even when the request has other problems. See [Errors](https://docs.ipalmanac.com/errors). ## Projects and workspaces A key belongs to one project, and a project belongs to a workspace. All keys in a workspace share its plan's monthly quota and per-minute rate. A project can also have its own monthly cap, set in its settings, to stop one app from using the whole workspace's quota. See [Rate limits and quotas](https://docs.ipalmanac.com/rate-limits). # MCP and AI agents (https://docs.ipalmanac.com/mcp) > Connect an AI agent to the MCP server, or point it at the OpenAPI spec and the Markdown guides. The MCP server gives an agent the same five lookups as tools, with the same key, limits and data as the REST API. * **URL:** `https://api.ipalmanac.com/mcp` * **Transport:** Streamable HTTP, with JSON responses * **Authentication:** the `Authorization: Bearer ` header ## Connect With Claude Code, add it from the command line: ```bash claude mcp add --transport http ip-almanac https://api.ipalmanac.com/mcp \ --header "Authorization: Bearer $IPALMANAC_API_KEY" ``` Or put it in a project's `.mcp.json`: ```json { "mcpServers": { "ip-almanac": { "type": "http", "url": "https://api.ipalmanac.com/mcp", "headers": { "Authorization": "Bearer " } } } } ``` Other clients take the same two settings, the URL and the `Authorization` header, in their own config format. Run the client on your machine or server: the server refuses requests from web pages other than the console. ## Tools | Tool | Same as | | ---------------- | ------------------------------------------------------------------ | | `lookup_ip` | [`GET /v1/ip/{ip}`](https://docs.ipalmanac.com/api-reference/lookup-ip) | | `ip_domains` | [`GET /v1/ip/{ip}/domains`](https://docs.ipalmanac.com/api-reference/ip-domains) | | `domain_history` | [`GET /v1/domain/{domain}/history`](https://docs.ipalmanac.com/api-reference/domain-history) | | `domain_hosting` | [`GET /v1/domain/{domain}/hosting`](https://docs.ipalmanac.com/api-reference/domain-hosting) | | `lookup_asn` | [`GET /v1/asn/{asn}`](https://docs.ipalmanac.com/api-reference/lookup-asn) | Each tool returns the same JSON as its endpoint, as structured content and as text. Listing the tools needs no key. Calling one checks the key and counts against the same rate and quota as the REST API (see [Rate limits and quotas](https://docs.ipalmanac.com/rate-limits)). A refused call comes back as a tool error whose text carries the [error code](https://docs.ipalmanac.com/errors) after the tool name: ```text Error executing tool lookup_ip: invalid_ip: Expected an IPv4 or IPv6 address. No request was counted. ``` ## Help an agent read the data The data is easy to misread: `null` is unknown rather than false, and an empty list means not observed rather than absent. Give your agent the reading rules along with the tools: * [`https://api.ipalmanac.com/llms-full.txt`](https://api.ipalmanac.com/llms-full.txt): the API's own guide for agents, covering every tool and how to read its results. * [`/llms-full.txt`](https://docs.ipalmanac.com/llms-full.txt) on this site: all of these docs as one Markdown file. [`/llms.txt`](https://docs.ipalmanac.com/llms.txt) is the index, and every page's **Copy Markdown** button copies that page. * [`https://api.ipalmanac.com/v1/openapi.json`](https://api.ipalmanac.com/v1/openapi.json): the OpenAPI spec, for agents and code generators that call the REST API. # Who else is on an IP (https://docs.ipalmanac.com/use-cases/shared-hosting) > List the domains that share an IP, with when each arrived and left. On shared-hosting platforms, website builders and CDNs, one IP can carry thousands of domains. This is where our data is strongest. ## 1. Look up the IP [`lookup_ip`](https://docs.ipalmanac.com/api-reference/lookup-ip) is one request and tells you how big the list is before you fetch it. ```bash curl https://api.ipalmanac.com/v1/ip/ \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` Look at: * `domains.current_count` and `domains.sample`: how many domains resolve to the IP in our latest observation, and up to 10 of them. * `is_cdn`, `is_datacenter`, `provider` and `prefix`: whether the IP is in a CDN's, cloud's or hosting provider's published range. A CDN edge serves many unrelated customers. * `asn.category`: what kind of network routes it, such as `hosting` or `isp`. * `ptr.names`: the IP's reverse DNS name, when we have checked it. ## 2. List the domains [`ip_domains`](https://docs.ipalmanac.com/api-reference/ip-domains) lists every domain seen on the IP, one item per stretch of time it resolved there, oldest first. Add `include_ended=true` to see domains that have since left. ```bash curl "https://api.ipalmanac.com/v1/ip//domains?include_ended=true&limit=1000" \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` Each page counts one request, so a list of 5,000 items costs 5 requests at `limit=1000`, or 50 on the Free plan, whose pages hold at most 100. See [Pagination](https://docs.ipalmanac.com/pagination). ## 3. Read the list * `first_observed` is when we first saw the domain on the IP; `ended_at` is when we saw it gone. It left somewhere between its `last_confirmed` and `ended_at`. * Domains on the same IP are neighbors, not the same owner, especially on a CDN edge or a big shared host. * An empty list means we haven't seen any of the domains we resolve on the IP, not that nothing is hosted there. Residential, ISP and small dedicated-server addresses are often empty. # Investigate a suspicious domain (https://docs.ipalmanac.com/use-cases/suspicious-domain) > For a phishing or look-alike domain, check the network behind its addresses and what else shares them. A new phishing or brand-abuse domain is usually not in our history: we resolve popular domains, and these are new and long-tail. Start from its addresses instead. ## 1. Resolve it yourself ```bash dig +short A suspicious.example dig +short AAAA suspicious.example ``` ## 2. Look up each address ```bash IP=203.0.113.7 # one of the addresses from step 1 curl "https://api.ipalmanac.com/v1/ip/$IP" \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` [`lookup_ip`](https://docs.ipalmanac.com/api-reference/lookup-ip) tells you: * **The network:** `asn` names the network that routes the address, its registry country, and what kind of network it is (`asn.category`: our classification, or ipverse's where we have none). * **The provider range:** `provider` and `prefix` when the address is in a range a cloud, hosting or CDN provider publishes. Behind a CDN, you are looking at the CDN's edge, not the site's own server. * **The PTR name**, when we have checked it. * **The neighbors:** `domains` lists established sites seen on the same address. Sharing an address says nothing about sharing an owner. ## 3. Check the domain's history, if we have it ```bash curl https://api.ipalmanac.com/v1/domain/suspicious.example/history \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` `observed: false` means we don't resolve that name, so there is no history to show: report it as not covered, not as clean. For a domain we do resolve, [`domain_history`](https://docs.ipalmanac.com/api-reference/domain-history) and [`domain_hosting`](https://docs.ipalmanac.com/api-reference/domain-hosting) show where it pointed and when it moved. > **What this doesn't tell you:** We report what we observe and what operators publish. We don't score domains or IPs, and nothing in an answer says > whether a site is malicious. # Map a domain's infrastructure (https://docs.ipalmanac.com/use-cases/infrastructure) > Follow where a popular domain pointed, the networks it moved through, and what those networks route. ## 1. Where it points, and where it pointed [`domain_history`](https://docs.ipalmanac.com/api-reference/domain-history) gives a domain's current A and AAAA state, with the CNAME chain, and every answer it gave, oldest first. ```bash curl https://api.ipalmanac.com/v1/domain/example.com/history \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` * `current` holds the latest state per record type: the `values`, the `cnames` chain, and whether it is `stale`. * `intervals` holds each address with `first_observed`, `last_confirmed` and `ended_at`. ## 2. The networks it moved through [`domain_hosting`](https://docs.ipalmanac.com/api-reference/domain-hosting) turns that history into runs: one per stretch of time the domain answered on one network (routing ASN and cloud, hosting or CDN provider). ```bash curl https://api.ipalmanac.com/v1/domain/example.com/hosting \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` A move happened between one run's `last_confirmed` and its `ended_at`, never at an exact instant. A domain that leaves a network and comes back gets a new run. Each run's network is today's ASN mapping and published ranges applied to the domain's addresses, not how they were routed at the time. So a run changes when the domain's addresses change, not when a network's ranges do. ## 3. What each network routes [`lookup_asn`](https://docs.ipalmanac.com/api-reference/lookup-asn) describes one network: its name and registry country, what kind of network it is, the IP ranges the mapping assigns to it, the published cloud, hosting and CDN ranges inside them, and how many IPs and domains we observed there. ```bash curl https://api.ipalmanac.com/v1/asn/AS13335 \ -H "Authorization: Bearer $IPALMANAC_API_KEY" ``` Its `ranges` are paged like any list; see [Pagination](https://docs.ipalmanac.com/pagination). ## Things to keep in mind * We resolve from one vantage point. A domain behind GeoDNS or anycast may answer differently where you are. * The routing ASN comes from the IPtoASN mapping, not our own view of BGP, and it is not necessarily the holder of the addresses. * History starts on October 1, 2026. # Read a response (https://docs.ipalmanac.com/reading-answers) > What null, empty lists, stale states and the dates in a response mean, and what they don't. Every response separates what we observed from what we don't know. These rules hold across the API. ## Null means unknown, never false `is_datacenter` is `true` only when the IP sits inside a range a cloud or hosting provider publishes, and `is_cdn` only inside a range a CDN publishes (an edge server, not the customer's origin). Outside every range we have, they are `null`, which does not mean the IP isn't hosted: many hosting companies publish no range list. The same goes for every other field that can be `null`: we don't know, so we don't say. ## Empty means not observed here, not absent An IP with no domains means none of the domains we resolve were seen on it, not that no website is hosted there. A domain we don't resolve answers `observed: false` from [`domain_history`](https://docs.ipalmanac.com/api-reference/domain-history): say it isn't covered, not that it has no DNS. See [Data and coverage](https://docs.ipalmanac.com/data-and-coverage) for what we resolve. ## The dates All times are UTC, in ISO 8601. | Field | Meaning | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `first_observed` | When we first saw this answer. The record may be older; this is when we saw it. | | `last_confirmed` | When we last saw the same answer. | | `ended_at` | When we saw that the answer had changed. The change happened between `last_confirmed` and `ended_at`, not at either instant. `null` while it is still current. | | `current` | `true` while the answer is part of the domain's latest state. | ## DNS outcomes A domain's current state (in [`domain_history`](https://docs.ipalmanac.com/api-reference/domain-history)) says how the last query went: | `outcome_class` | `outcome` | Meaning | | --------------- | ---------------------------------- | ------------------------------------------------------ | | `positive` | `positive` | It answered with addresses. | | `negative` | `nxdomain`, `nodata` | The name doesn't exist, or has no record of this type. | | `transient` | `timeout`, `servfail`, `malformed` | The query failed. The earlier answer is kept. | A failure never erases an answer, and a change in TTL or answer order is not a change of address. ## Staleness A current state carries `last_confirmed`, `age_seconds` and `stale`. It is `stale: true`, with a `stale_reason`, when: * `transient_failure`: the last query failed, so the answer shown is the last good one; * `never_confirmed`: no query for it has succeeded yet; * `older_than_seven_days`: it was last confirmed more than seven days ago. Check `stale` and `age_seconds` before you rely on a current answer. ## Networks * `asn` comes from the IPtoASN mapping, not from our own view of BGP. The network that routes an IP is not necessarily the holder of the address or the company using it. * `asn.category` (`isp`, `hosting`, `cdn`, `business`, `banking`, `education_research`, `government_admin`) is what the routing network mainly is, by our own classification where we have one (`category_source: "own"`) and ipverse's where we don't (`"ipverse"`). It describes the network, not the IP's tenant, and it can be wrong. It never sets `is_datacenter`. * `asn.network_role` (`tier1_transit`, `major_transit`, `midsize_transit`, `access_provider`, `content_network`, `stub`) is the network's place in routing, from ipverse as-metadata. * A CDN edge is not the customer's origin server, and domains that share an IP don't share an owner. ## Usage ranges `provider_ranges` can also hold ranges that say how an IP is used, each from the operator's own published list: `tor_exit` (Tor exits), `relay` (iCloud Private Relay egress) and `crawler` (Google and DuckDuckGo crawlers). They never name who hosts the IP. An IP on none of these lists is simply not listed; that is not a "no". ## What every response carries | Field | What it is | | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `coverage` | What the response is based on: the observation window, the seed list and derived names, the resolver, PTR counts, and `semantics`, these reading rules in short. | | `attribution` | The sources to credit if you republish the data. | | `release` | The data snapshot that answered: its `id` and `built_at`. | # Data and coverage (https://docs.ipalmanac.com/data-and-coverage) > Where the data comes from, where it is strong and thin, and how to tell how fresh an answer is. ## Sources | Data | Source | Kind | | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | Domains on an IP, DNS history, hosting moves | Our own resolver, querying the seed list below | Measured by us | | PTR names | Our own resolver, for IPs we have observed | Measured by us | | Network category (`asn.category`) | Our own classification; ipverse as-metadata where we have none | Classified by us, or imported from ipverse (`category_source` says which) | | Network role (`asn.network_role`) | ipverse as-metadata, from each network's BGP neighbors | Imported | | Cloud, hosting and CDN ranges | The lists AWS, Google Cloud, Microsoft Azure, Oracle Cloud, Linode, DigitalOcean, Vultr, Cloudflare and Fastly publish | Published by the providers | | Tor exits, iCloud Private Relay egress, crawler ranges | The Tor Project's exit list, Apple's egress ranges, and Google's and DuckDuckGo's crawler ranges | Published by the operators | | Routing ASN, name and country | The IPtoASN mapping | Imported | ## How we collect * **Seed list:** we resolve the domains in the Majestic Million, a list of 1,000,000 popular domains, and the `www.` name of each. `coverage.seed` in each response says when the list in use was retrieved, and `coverage.derived` counts the `www.` names. * **One resolver, one vantage point.** Answers that differ by location, such as GeoDNS and some CDNs, are recorded as our resolver sees them, so your own lookup of the same name may return other addresses. * **Record types:** A and AAAA, with the CNAME chain that led to them. PTR names only for IPs we have observed. No MX, NS, TXT or other types. * **History** starts with our first observation, on October 1, 2026. `coverage.observation_window` in each response gives the span it is based on. * **Networks** come from the current snapshot's ASN mapping and published ranges, applied to every address in the history. A hosting run in [`domain_hosting`](https://docs.ipalmanac.com/api-reference/domain-hosting) shows which network an address belongs to today, not how it was routed at the time. ## Where it is strong, and where it isn't - **Strong**: Shared-hosting platforms and website builders. CDN and cloud addresses serving popular sites. Popular registered domains and where they moved. - **Thin**: Residential and ISP addresses (usually the ASN only). Brand-new and long-tail domains. Subdomains other than `www.`. - **Not offered**: History before October 1, 2026, when collection started. Geolocation. Abuse or reputation scores. WHOIS and company ownership. Guesses: unknown stays `null`. Many hosting companies publish no range list, so many hosting IPs read `is_datacenter: null`. PTR names are checked for a share of observed IPs; `coverage.ptr` in each response gives the counts. ## Freshness The API serves snapshots of the collected data. Each response says how fresh it is, so you don't have to take it on trust: * `release.id` and `release.built_at`: the snapshot that answered, and when it was built. * `last_confirmed` and `age_seconds`: when we last saw each answer. * `stale` and `stale_reason`: set when an answer was last confirmed more than seven days ago, when the last query failed, or when it was never confirmed. See [Read an answer](https://docs.ipalmanac.com/reading-answers#staleness). * `first_seen` and `last_seen` on each published range: when we first and last saw it on that list. ## Using the data Every response lists its sources in `attribution`. Credit them when you republish data from the API; the Majestic Million is licensed [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/). Using answers inside your own product, research or investigations is fine; reselling or redistributing the data in bulk as a dataset needs our written permission. The [Terms](https://app.ipalmanac.com/terms) have the details. # Pagination (https://docs.ipalmanac.com/pagination) > Long lists come in pages. Pass next_cursor back as cursor until it is null. [`ip_domains`](https://docs.ipalmanac.com/api-reference/ip-domains), [`domain_history`](https://docs.ipalmanac.com/api-reference/domain-history) and [`lookup_asn`](https://docs.ipalmanac.com/api-reference/lookup-asn) (its ranges) return their lists in pages. | Parameter | Meaning | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `limit` | Items per page, 1 to 1000; 100 when left out. On the Free plan a page holds at most 100: a larger `limit` is lowered, and `next_cursor` carries on. | | `cursor` | The previous page's `next_cursor`, unchanged. Leave it out for the first page. | Each page has `next_cursor`, which is `null` on the last page. Each page counts one request. ```python import os import requests API = "https://api.ipalmanac.com" HEADERS = {"Authorization": f"Bearer {os.environ['IPALMANAC_API_KEY']}"} def domains_on(ip: str) -> list[dict]: """Every domain interval seen on the IP, ended ones included.""" items, cursor = [], None while True: params = {"include_ended": "true", "limit": 1000} if cursor: params["cursor"] = cursor response = requests.get(f"{API}/v1/ip/{ip}/domains", headers=HEADERS, params=params, timeout=30) response.raise_for_status() page = response.json() items += page["items"] cursor = page["next_cursor"] if cursor is None: return items ``` ```js const API = "https://api.ipalmanac.com"; const headers = { Authorization: `Bearer ${process.env.IPALMANAC_API_KEY}` }; // Every domain interval seen on the IP, ended ones included. async function domainsOn(ip) { const items = []; let cursor = null; do { const url = new URL(`/v1/ip/${ip}/domains`, API); url.searchParams.set("include_ended", "true"); url.searchParams.set("limit", "1000"); if (cursor) url.searchParams.set("cursor", cursor); const response = await fetch(url, { headers }); if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`); const page = await response.json(); items.push(...page.items); cursor = page.next_cursor; } while (cursor); return items; } ``` ## When a cursor stops working A cursor belongs to the query it came from and to the data snapshot that answered it. * Sent with a different query, it gets `400` `invalid_cursor`. * Once the data is updated, it gets `410` `cursor_expired`: start again without a cursor. Each page's `release.id` names its snapshot, so pages with the same `release.id` belong together. ## Not paged [`domain_hosting`](https://docs.ipalmanac.com/api-reference/domain-hosting) returns everything in one response. For a domain with more than 5,000 answer intervals it covers the most recent ones and sets `truncated: true`. # Errors (https://docs.ipalmanac.com/errors) > The error codes the API returns, what each means, and whether to retry. Errors are JSON with a stable `error` code and a message for people: ```json { "error": "invalid_ip", "message": "Expected one IPv4 or IPv6 address, not a CIDR range. No request was counted." } ``` Branch on `error`, not on `message`; messages may be reworded. A refused request never counts against your monthly quota. ## Error codes | Error | What happened | What to do | | ---------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `400` `invalid_cursor` | The `cursor` was not issued for this query. | Send the same query the cursor came from, or start without one. | | `401` `unauthorized` | The API key is missing, mistyped, revoked or expired, or was sent in the URL. | Check the `Authorization` header. See [Authentication](https://docs.ipalmanac.com/authentication). | | `402` `quota_exceeded` | The workspace has used its monthly quota. | Wait for the quota to reset at the start of the next month. See [Rate limits and quotas](https://docs.ipalmanac.com/rate-limits). | | `402` `project_cap_exceeded` | The key's project has reached its own monthly cap. | Raise or remove the cap in the project's settings. | | `410` `cursor_expired` | The data was updated since the cursor was issued. | Start again without a cursor. | | `413` `payload_too_large` | The request body is over 1 MiB. Only `POST /mcp` takes a body. | Send a smaller request. | | `422` `invalid_ip` | Not one IPv4 or IPv6 address, for example a CIDR range. | Send a single address. | | `422` `non_public_ip` | A private, reserved or other special-purpose address, on a call that needs a public one. | [`lookup_ip`](https://docs.ipalmanac.com/api-reference/lookup-ip) answers these with `is_bogon: true` instead. | | `422` `invalid_domain` | Not a hostname with at least two labels. | Send a name such as `example.com`, with no scheme or path. | | `422` `invalid_asn` | Not an AS number from 1 to 4294967295. | Send `64500` or `AS64500`. | | `429` `rate_limited` | Over your plan's requests per minute, or too many refused keys from your address. | Wait the `Retry-After` header's seconds, then retry. | | `500` `internal_error` | Something failed on our side. | Retry once; if it persists, tell us. | | `503` `release_unavailable` | No lookup data is loaded right now. | Retry later. | | `503` `service_unavailable` | The API is briefly overloaded. | Wait the `Retry-After` seconds, then retry. | ## Other shapes A few responses come from the web framework rather than the API, and have a `detail` field instead of `error`: * `422` with `{"detail": [...]}`: a query parameter is out of range, for example `limit=5000` or a `cursor` over 1024 characters. * `404` with `{"detail": "Not Found"}`: no such path. Check for a typo. * `405` with `{"detail": "Method Not Allowed"}`: every lookup is a `GET`. Paths have no trailing slash. With one, the API redirects to the path without it, and most HTTP clients drop the `Authorization` header when they follow that redirect, so the request ends in `401`. Send the path as the reference shows it. The [MCP server](https://docs.ipalmanac.com/mcp) refuses calls from web pages other than the console with `403`, and answers `405` to anything but `POST`. ## Retrying Retry only `429`, `500` and `503`, and wait the `Retry-After` seconds when the header is there. Every other error will come back the same until the request changes. ## Request IDs Every response except a `500` has an `X-Request-ID` header. Include it when you contact [support@ipalmanac.com](mailto:support@ipalmanac.com) about a request. # Rate limits and quotas (https://docs.ipalmanac.com/rate-limits) > Each workspace has a monthly quota and a per-minute rate, set by its plan. Response headers show where you stand. Limits belong to the workspace, not the key: every key in a workspace shares them. | Plan | Requests a month | Requests a minute | | ------- | ---------------- | ----------------- | | Free | 10,000 | 30 | | Starter | 600,000 | 60 | | Pro | 6,000,000 | 300 | Paid plans are not on sale yet, so new workspaces are on Free. ## Monthly quota Each answered lookup counts one request against the quota, and so does each page of a paged list. | Counted | Not counted | | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | | A response with data, including an empty list | A refused request (any `4xx` or `5xx`) | | Each page of [`ip_domains`](https://docs.ipalmanac.com/api-reference/ip-domains), [`domain_history`](https://docs.ipalmanac.com/api-reference/domain-history) and [`lookup_asn`](https://docs.ipalmanac.com/api-reference/lookup-asn) | A [`lookup_ip`](https://docs.ipalmanac.com/api-reference/lookup-ip) answer for a private or reserved address (`is_bogon: true`) | | [`GET /v1/ping`](https://docs.ipalmanac.com/api-reference/ping) | [`GET /health`](https://docs.ipalmanac.com/api-reference/health), and listing the [MCP](https://docs.ipalmanac.com/mcp) server's tools | | An MCP tool call that answers | | The Free plan's quota resets at the start of each calendar month, UTC. When the quota is used up, lookups answer `402` `quota_exceeded` until it resets. A project can have its own monthly cap, set in the project's settings. It can only lower what the project may use of the workspace's quota; when it is reached, that project's keys get `402` `project_cap_exceeded`. ## Requests per minute The rate is counted per UTC clock minute. Every request made with a valid key counts toward it, including refused ones. Over the rate, requests get `429` `rate_limited` with a `Retry-After` header: wait that many seconds and retry. A separate limit protects keys: more than 100 refused or missing keys from one address in a minute also get `429`. IPv6 addresses count per /64. ## Headers Responses carry where you stand: | Header | Meaning | | ---------------------------------------------- | --------------------------------------------------------- | | `X-RateLimit-Limit` | Requests allowed per minute. | | `X-RateLimit-Remaining` | Requests left this minute. | | `X-RateLimit-Reset` | When this minute's count resets, as Unix time in seconds. | | `X-Quota-Limit` | Requests allowed this month. | | `X-Quota-Used` | Requests used this month. | | `X-Quota-Remaining` | Requests left this month. | | `X-Project-Quota-Limit`, `-Used`, `-Remaining` | The same for the key's project, when it has a cap. | The quota headers come on counted responses. When you get a `429`, go by its `Retry-After`. # Overview (https://docs.ipalmanac.com/api-reference) > The REST API's operations, generated from its OpenAPI spec. These pages are generated from a copy of the API's [OpenAPI spec](https://api.ipalmanac.com/v1/openapi.json) that the API's tests keep in step with its code. The [MCP server](https://docs.ipalmanac.com/mcp) has the same lookups as tools. ## Base URL ```text https://api.ipalmanac.com ``` Every `/v1` operation needs an API key in the `Authorization: Bearer` header ([Authentication](https://docs.ipalmanac.com/authentication)) and answers JSON. Errors are `{"error": "", "message": ""}` ([Errors](https://docs.ipalmanac.com/errors)). ## Operations | Operation | Returns | | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- | | [`GET /v1/ip/{ip}`](https://docs.ipalmanac.com/api-reference/lookup-ip) | One public IP: its network, published ranges, PTR name and the domains on it. | | [`GET /v1/ip/{ip}/domains`](https://docs.ipalmanac.com/api-reference/ip-domains) | Reverse IP: every domain seen on an IP, with dates. Paged. | | [`GET /v1/domain/{domain}/history`](https://docs.ipalmanac.com/api-reference/domain-history) | DNS history: a domain's current A and AAAA state and every answer it gave. Paged. | | [`GET /v1/domain/{domain}/hosting`](https://docs.ipalmanac.com/api-reference/domain-hosting) | Hosting history: the networks a domain moved through. | | [`GET /v1/asn/{asn}`](https://docs.ipalmanac.com/api-reference/lookup-asn) | One network: its ranges, the published ranges inside them, and what we observed there. Paged. | | [`GET /v1/ping`](https://docs.ipalmanac.com/api-reference/ping) | Check that your API key works. | | [`GET /health`](https://docs.ipalmanac.com/api-reference/health) | Whether the service is up, and the data snapshot it serves. No key needed. | To try an operation without writing code, use the **Playground** page of a project in the [console](https://app.ipalmanac.com) (owners and admins can run requests). These pages have no "try it" button, because the API accepts browser requests only from the console. ## About the examples The example responses on these pages are made up, with documentation addresses and `.example` domains, to show the shape of each one. Live responses have real values and the full `coverage` and `attribution` blocks. # Look up a public IP (https://docs.ipalmanac.com/api-reference/lookup-ip) `GET https://api.ipalmanac.com/v1/ip/{ip}` What was observed about one public IP: its mapped ASN, provider ranges (cloud, hosting, CDN), PTR name and the domains seen resolving to it. Start here for who else is on an IP and which network hosts it. Counts one request. Unknown is null, never false; an empty domain list means none were observed here, not that none exist. provider_ranges also says whether the IP is listed as one of Tor exits, iCloud Private Relay egress and DuckDuckGo and Google crawlers (tor_exit, relay and crawler): how the IP is used, from the operators' own published lists, never who hosts it. Needs an API key: `Authorization: Bearer $IPALMANAC_API_KEY`. **Parameters** - `ip` (path, string, required): A public IPv4 or IPv6 address. A private, reserved, loopback, link-local, documentation, CGNAT or multicast address answers is_bogon true and nothing else, not counted. **Responses** - `200`: Successful Response - `401`: `unauthorized`: the API key is missing, malformed, revoked or expired. - `402`: `quota_exceeded` or `project_cap_exceeded`. - `422`: `invalid_ip`. Not counted. - `429`: `rate_limited`: over the plan's requests per minute, or more than 100 refused keys from your address in a minute. Retry after the `Retry-After` header's seconds. - `503`: `release_unavailable` (no lookup data loaded) or `service_unavailable`. Not counted. The response schemas are in the [OpenAPI document](https://api.ipalmanac.com/v1/openapi.json). # List the domains seen on an IP (https://docs.ipalmanac.com/api-reference/ip-domains) `GET https://api.ipalmanac.com/v1/ip/{ip}/domains` Reverse IP: the domains observed resolving to an IP (who else is on it), one item per answer interval, oldest first. Ended intervals are included with `include_ended=true`. Pass `next_cursor` back as `cursor` for the next page; each page counts one request. Needs an API key: `Authorization: Bearer $IPALMANAC_API_KEY`. **Parameters** - `ip` (path, string, required): A public IPv4 or IPv6 address. Private, reserved, loopback, link-local, documentation, CGNAT and multicast addresses are refused. - `include_ended` (query, boolean): Also list intervals that ended (the domain stopped resolving to the IP). - `limit` (query, integer): Items per page, 1 to 1000. Free keys get at most 100 a page; a larger limit is lowered and `next_cursor` continues. - `cursor` (query, string): The previous page's `next_cursor`, unchanged. Omit it for the first page. **Responses** - `200`: Successful Response - `400`: `invalid_cursor`: the cursor was not issued for this query. Not counted. - `401`: `unauthorized`: the API key is missing, malformed, revoked or expired. - `402`: `quota_exceeded` or `project_cap_exceeded`. - `410`: `cursor_expired`: the data was updated since the cursor was issued; start again without a cursor. Not counted. - `422`: `invalid_ip` or `non_public_ip`; an out-of-range `limit` or too long `cursor` gets FastAPI's `detail` list. Not counted. - `429`: `rate_limited`: over the plan's requests per minute, or more than 100 refused keys from your address in a minute. Retry after the `Retry-After` header's seconds. - `503`: `release_unavailable` (no lookup data loaded) or `service_unavailable`. Not counted. The response schemas are in the [OpenAPI document](https://api.ipalmanac.com/v1/openapi.json). # Show a domain's DNS history (https://docs.ipalmanac.com/api-reference/domain-history) `GET https://api.ipalmanac.com/v1/domain/{domain}/history` A domain's current A/AAAA state, with staleness, and its answer intervals, oldest first. Covers popular registered domains; a new or long-tail domain is usually empty. Each page counts one request. Needs an API key: `Authorization: Bearer $IPALMANAC_API_KEY`. **Parameters** - `domain` (path, string, required): A hostname such as example.com. It is lowercased, a trailing dot is dropped and Unicode labels are punycoded. - `limit` (query, integer): Items per page, 1 to 1000. Free keys get at most 100 a page; a larger limit is lowered and `next_cursor` continues. - `cursor` (query, string): The previous page's `next_cursor`, unchanged. Omit it for the first page. **Responses** - `200`: Successful Response - `400`: `invalid_cursor`: the cursor was not issued for this query. Not counted. - `401`: `unauthorized`: the API key is missing, malformed, revoked or expired. - `402`: `quota_exceeded` or `project_cap_exceeded`. - `410`: `cursor_expired`: the data was updated since the cursor was issued; start again without a cursor. Not counted. - `422`: `invalid_domain`; an out-of-range `limit` or too long `cursor` gets FastAPI's `detail` list. Not counted. - `429`: `rate_limited`: over the plan's requests per minute, or more than 100 refused keys from your address in a minute. Retry after the `Retry-After` header's seconds. - `503`: `release_unavailable` (no lookup data loaded) or `service_unavailable`. Not counted. The response schemas are in the [OpenAPI document](https://api.ipalmanac.com/v1/openapi.json). # Show the networks a domain moved through (https://docs.ipalmanac.com/api-reference/domain-hosting) `GET https://api.ipalmanac.com/v1/domain/{domain}/hosting` Hosting history: the networks a domain's A/AAAA answers moved through, one run per stretch on one network (routing ASN and cloud, hosting or CDN provider), oldest first, with when it was first and last seen there and when it left. A move happened between one run's `last_confirmed` and its `ended_at`, never at an instant. Covers popular registered domains; history starts with the first observation (`coverage.observation_window`). Each run's network is the current release's mapping and ranges applied to its addresses, not the routing at the time. Counts one request. Needs an API key: `Authorization: Bearer $IPALMANAC_API_KEY`. **Parameters** - `domain` (path, string, required): A hostname such as example.com. It is lowercased, a trailing dot is dropped and Unicode labels are punycoded. **Responses** - `200`: Successful Response - `401`: `unauthorized`: the API key is missing, malformed, revoked or expired. - `402`: `quota_exceeded` or `project_cap_exceeded`. - `422`: `invalid_domain`. Not counted. - `429`: `rate_limited`: over the plan's requests per minute, or more than 100 refused keys from your address in a minute. Retry after the `Retry-After` header's seconds. - `503`: `release_unavailable` (no lookup data loaded) or `service_unavailable`. Not counted. The response schemas are in the [OpenAPI document](https://api.ipalmanac.com/v1/openapi.json). # Look up an ASN (https://docs.ipalmanac.com/api-reference/lookup-asn) `GET https://api.ipalmanac.com/v1/asn/{asn}` One network: the IP ranges the IPtoASN mapping assigns to the ASN, its name and registry country, the published cloud, hosting and CDN ranges inside them, and how many IPs and domains were observed there. Ranges are paged: pass `next_cursor` back as `cursor`; each page counts one request. An ASN the mapping does not list answers with no ranges. Needs an API key: `Authorization: Bearer $IPALMANAC_API_KEY`. **Parameters** - `asn` (path, string, required): An AS number, as 64500 or AS64500. - `limit` (query, integer): Items per page, 1 to 1000. Free keys get at most 100 a page; a larger limit is lowered and `next_cursor` continues. - `cursor` (query, string): The previous page's `next_cursor`, unchanged. Omit it for the first page. **Responses** - `200`: Successful Response - `400`: `invalid_cursor`: the cursor was not issued for this query. Not counted. - `401`: `unauthorized`: the API key is missing, malformed, revoked or expired. - `402`: `quota_exceeded` or `project_cap_exceeded`. - `410`: `cursor_expired`: the data was updated since the cursor was issued; start again without a cursor. Not counted. - `422`: `invalid_asn`: not an AS number from 1 to 4294967295; an out-of-range `limit` or too long `cursor` gets FastAPI's `detail` list. Not counted. - `429`: `rate_limited`: over the plan's requests per minute, or more than 100 refused keys from your address in a minute. Retry after the `Retry-After` header's seconds. - `503`: `release_unavailable` (no lookup data loaded) or `service_unavailable`. Not counted. The response schemas are in the [OpenAPI document](https://api.ipalmanac.com/v1/openapi.json). # Check an API key (https://docs.ipalmanac.com/api-reference/ping) `GET https://api.ipalmanac.com/v1/ping` Check that your API key works. Answers with the server's time. Counts one request against the rate limit and the monthly quota, like a lookup, and carries the same limit headers. Needs an API key: `Authorization: Bearer $IPALMANAC_API_KEY`. **Responses** - `200`: Successful Response - `401`: `unauthorized`: the API key is missing, malformed, revoked or expired. - `402`: `quota_exceeded` or `project_cap_exceeded`. - `429`: `rate_limited`: over the plan's requests per minute, or more than 100 refused keys from your address in a minute. Retry after the `Retry-After` header's seconds. - `503`: `release_unavailable` (no lookup data loaded) or `service_unavailable`. Not counted. The response schemas are in the [OpenAPI document](https://api.ipalmanac.com/v1/openapi.json). # Check service health (https://docs.ipalmanac.com/api-reference/health) `GET https://api.ipalmanac.com/health` Whether the service is up, and the data release it serves. Needs no key and counts nothing. Answers 503 with `status: unhealthy` when the database is unreachable. `release` names the served data release (`id`, `built_at`, `age_seconds`). Without one only the lookups fail (503, not counted), so the service stays healthy. **Responses** - `200`: Successful Response - `503`: The database is unreachable. The response schemas are in the [OpenAPI document](https://api.ipalmanac.com/v1/openapi.json).