API Reference
Simple, consistent endpoints. CORS enabled. No authentication required. Fully multilingual — every field, key and value, translates into 10+ languages.
Base URL
Quick start
Terminal shortcut: curl ipto.to or curl ipto.to/8.8.8.8 (no https:// or /v1/ip/ needed) works too when the request comes from curl, Wget, or HTTPie — detected by User-Agent. It returns a shorter, ipinfo.io-style field set (ip, country, city, asn, org, ...), not the full schema below — use the /v1/ip paths directly for the complete response documented on this page.
Just the IP: curl ipto.to/ip (or open it in a browser) returns nothing but your own IP address as plain text — 73.9.114.155, no JSON, no page chrome. Direct equivalent of ipinfo.io/ip. See GET /ip below.
Multilingual by default: Set ?lang= to any supported code and every JSON key and value in the response comes back translated — not just country/continent. See Localization below for the full language list and the &localize=0 opt-out.
Lookup Lab
Build any ipto request, copy it as ready-to-run code in your language, or send it right here and read the live response. No key is needed for the free tier.
MCP Server
ipto is also reachable as an MCP (Model Context Protocol) tool server — point Claude Desktop, Cursor, or any MCP-capable client at the endpoint below and it exposes 14 callable tools: ip_lookup, bulk_ip_lookup (Pro/Enterprise), asn_lookup, country_lookup, reverse_dns_lookup, user_agent_parse, phone_lookup, email_lookup, fraud_score, language_detect, currency_convert, weather_lookup, trademark_lookup, and patent_lookup. JSON-RPC 2.0 over Streamable HTTP — POST only, no session/SSE required. Same auth as the REST API: omit for the free tier (1,000 req/day by IP), or append ?api_key= to the endpoint URL.
For stdio-only clients, bridge it: npx mcp-remote https://ipto.to/mcp
Endpoints
Returns nothing but the requesting client's own public IP address, as plain text (Content-Type: text/plain) — e.g. 73.9.114.155. Direct equivalent of ipinfo.io/ip: works identically from curl, Wget, HTTPie, or a plain browser visit (no User-Agent detection, unlike the curl ipto.to shortcut above), and renders as just the bare IP with no page chrome when opened in a browser.
No parameters, no query string. For the full geolocation/threat-intelligence object, use GET /v1/ip below instead. This route reads the connecting IP directly (no D1/KV lookup) and is not metered against the per-day request budget — see Rate limits.
Returns geolocation and threat intelligence for the requesting IP, including city-level data via the CF edge object.
No required parameters. Accepts ?lang=, ?fields=, ?format= — see Localization and Output formats below.
Look up any IPv4 address. City-level fields (city, state, postal_code, latitude, longitude, timezone) are populated when the IP falls within either of two merged geo datasets, IP2Location or MaxMind — check geo_precision in the response to see whether you got city or country-level data. timezone's format depends on which source matched (a UTC-offset string from IP2Location, an IANA name from MaxMind). IPv6 lookups currently return country-level data only.
| Parameter | In | Description |
|---|---|---|
ip | path | Required. Any valid IPv4 or IPv6 address. |
lang | query | Optional. Localizes country_name and continent: en (default), pt, fr, de, zh-TW, ko, zh-CN, ja-JP, es-ES, ar-SA, hi-IN. |
fields | query | Optional. Comma-separated list of fields to include. E.g. ?fields=ip,country_code,is_vpn |
format | query | Optional. Response format: json (default), csv, text, xml, yaml. |
The unified search endpoint behind the homepage search bar. Detects whether :query is an IP, an ASN (AS15169 or 15169), a 2-letter country code, an email, or a phone number, then aggregates every relevant check into one response. For an IP query this fans out — in parallel, server-side — to the primary IP lookup plus RPKI, IP-to-company, mobile-carrier, hosted-domains, and places, so a single search only draws one request against your daily rate-limit budget rather than one per feature.
| Parameter | In | Description |
|---|---|---|
query | path | Required. An IP, ASN, country code, email, or phone number. |
| Field | Type | Description |
|---|---|---|
query | string | The decoded input as received. |
detected_type | string | "ip", "asn", "country", "email", or "phone". |
ip | object|null | Present when detected_type is "ip" — same shape as GET /v1/ip/:ip. |
rpki | object|null | Present for IP queries — same shape as GET /v1/ip/:ip/rpki. |
company | object|null | Present for IP queries — same shape as GET /v1/ip/:ip/company. |
whois | object|null | Present for IP queries — same shape as GET /v1/ip/:ip/whois. |
carrier | object|null | Present for IP queries — same shape as GET /v1/ip/:ip/carrier. |
asn / country / email / phone | object|null | Present instead of the six fields above when detected_type matches — same shape as that type's own dedicated endpoint. |
Every sub-field degrades independently — a slow or failed upstream (RIPEstat, an RDAP server, crt.sh) shows up as that one field being null, never as the whole search failing. Only an invalid primary query (e.g. a malformed IP) returns an error status for the request as a whole.
Reverse DNS — a single live PTR lookup via DNS-over-HTTPS, not a domain-enumeration database. Returns null if the IP has no PTR record.
| Parameter | In | Description |
|---|---|---|
ip | path | Required. Any valid IPv4 or IPv6 address. |
RPKI route-origin validation for an IP's currently-announced route, resolved live via RIPEstat (no ROA database ingested or kept in sync locally). status is only ever "valid", "invalid", or "unknown" — never synthesized from any other signal; any RIPEstat failure or timeout returns "unknown" with a reason string, not an error.
| Parameter | In | Description |
|---|---|---|
ip | path | Required. Any valid IPv4 or IPv6 address. |
| Field | Type | Description |
|---|---|---|
asn | integer|null | The currently-announcing ASN, resolved live — not derived from this site's own ASN dataset. |
prefix | string|null | The currently-announced CIDR prefix. |
status | string | "valid", "invalid", or "unknown". |
validating_roas | array | The specific ROA(s) RIPEstat matched, each with origin/prefix/max_length/validity. |
validator | string|null | Which RPKI validator RIPEstat used (e.g. "routinator"). |
reason | string|null | Explains an "unknown" result — e.g. no covering ROA exists, or RIPEstat was unreachable. |
Explicit (ASN, prefix) RPKI validation — for when you already know the exact route you want checked, rather than the IP-derived convenience above.
| Parameter | In | Description |
|---|---|---|
asn | query | Required. ASN number, with or without the AS prefix. E.g. 15169 or AS15169. |
prefix | query | Required. CIDR prefix, e.g. 8.8.8.0/24. |
Identifies the organization an IP is registered to by combining RDAP (structured WHOIS) registrant data with the ASN's own organization name into a categorical confidence, never a numeric score.
| Parameter | In | Description |
|---|---|---|
ip | path | Required. Any valid IPv4 or IPv6 address. |
| Field | Type | Description |
|---|---|---|
company_name | string|null | The resolved company name — null when confidence is "low" (the two sources disagree, so neither is presented as authoritative) or "none". |
rdap_org_name | string|null | The RDAP registrant name, independent of company_name. |
asn_org_name | string|null | The ASN's own organization name, independent of company_name. |
confidence | string | "high" (sources agree), "medium" (only one source available), "low" (sources disagree — both shown), or "none". |
reason | string | Plain-English explanation of the confidence tier. |
The underlying RDAP registration record itself — network block, registration/last-changed dates, and every contact RDAP has on file. Distinct from /company above, which reconciles this same registrant data against the ASN's org name into one confidence-scored answer; this endpoint returns the raw record as-is, closer to classic WHOIS output. IPv6 addresses are supported too.
| Parameter | In | Description |
|---|---|---|
ip | path | Required. Any valid IPv4 or IPv6 address. |
| Field | Type | Description |
|---|---|---|
available | boolean | false if no RDAP record could be resolved — see reason. |
reason | string|null | Explains an available: false result. |
startAddress / endAddress / cidr | string|null | The registered network block. |
netName / netHandle / parentHandle | string|null | RIR-assigned identifiers for the network object. |
country | string|null | Registrant country, per RDAP. |
allocationType | string|null | e.g. "direct allocation", "reassigned". |
registeredDate / lastChangedDate | string|null | ISO timestamps from the RDAP record. |
rir | string|null | Which regional registry (ARIN, RIPE, APNIC, LACNIC, AFRINIC) served the record. |
contacts | array | Every contact RDAP has on file, each with roles, name, email, and handle — not just the first match. |
Best-effort mobile-carrier identification for IPs classified as mobile networks, cross-referenced against an MCC/MNC operator directory. Deliberately has no single carrier field — see matched_carrier_entries and note below — since a bare carrier name would read as a live per-subscriber claim, which this data can't honestly support.
| Parameter | In | Description |
|---|---|---|
ip | path | Required. Any valid IPv4 or IPv6 address. |
| Field | Type | Description |
|---|---|---|
is_mobile_network | boolean | false stops here with an empty directory — no guess for a non-mobile ASN. |
asn_organization | string|null | The ASN's own organization name. |
matched_carrier_entries | array | MCC/MNC directory rows whose carrier name fuzzy-matches asn_organization, scoped to the IP's own country. |
note | string | Explains what this field can and can't claim. |
Finds domains associated with an IP via its reverse-DNS (PTR) hostname and certificate-transparency logs (crt.sh) — not a general reverse-IP lookup. The response's own limitations field spells out exactly what this can and can't find.
| Parameter | In | Description |
|---|---|---|
ip | path | Required. Any valid IPv4 or IPv6 address. |
| Field | Type | Description |
|---|---|---|
ptr_hostname | string|null | The IP's reverse-DNS hostname, or null if it has no PTR record. |
root_domain | string|null | The registrable domain derived from ptr_hostname. |
subdomains_seen | array|null | Subdomains found via certificate transparency. null means the crt.sh search itself couldn't be completed — distinct from an empty array, which means it ran and found nothing. |
limitations | string | Explains what this feature structurally can't detect (no PTR, shared hosting, uncertified domains). |
Batch lookup — up to 100 IPs per request, Pro/Enterprise only. Returns a keyed object with each IP as a key. Faster than sequential calls due to parallel cache and database reads. Each IP in the batch counts against your daily request budget, same as looking it up individually. Free-tier requests get a 403 — see pricing.
| Parameter | In | Description |
|---|---|---|
body | body (JSON) | JSON array of IP strings. E.g. ["1.1.1.1","8.8.8.8"] |
Look up an Autonomous System by number. Returns the org name, country, continent, IP range count, classification type, exchange rate, and locale reference data (languages, tld, capital, borders).
| Parameter | In | Description |
|---|---|---|
asn | path | ASN number without the AS prefix. E.g. 15169 for Google. |
lang | query | Optional. Localizes country_name and continent: en (default), pt, fr, de, zh-TW, ko, zh-CN, ja-JP, es-ES, ar-SA, hi-IN. |
format | query | Optional. Response format: json (default), csv, text, xml, yaml. |
Reverse-geocodes an IP's existing city-level location, then returns nearby points of interest (restaurants, parks, schools, transit, notable landmarks), local crime data where a source exists (UK and US only), and an optional on-demand driving commute estimate.
| Parameter | In | Description |
|---|---|---|
ip | path | Required. Any valid IPv4 address. IPv6 is not yet supported. |
dest_lat | query | Optional. Destination latitude for a commute estimate — pair with dest_lon. |
dest_lon | query | Optional. Destination longitude for a commute estimate — pair with dest_lat. |
dest_address | query | Optional. Free-text destination address for a commute estimate, forward-geocoded automatically. Ignored if dest_lat/dest_lon are also supplied. |
| Field | Type | Description |
|---|---|---|
geocode | object|null | country, countryCode, region, city, neighbourhood, displayName — null if the IP has no city-level location to geocode from. |
locality.restaurants / .parks / .schools / .transit | array | Up to 4 nearest entries per category, each with name and distanceMeters. |
locality.notable | array | Notable nearby landmarks with a short description, via Wikidata. |
locality.usCrime | object|null | Nearest-agency violent/property crime totals — US only, requires an FBI_CRIME_API_KEY on the deployment. null everywhere else. |
locality.ukCrime | object|null | Street-level incident summary within 1 mile — GB only. |
locality.caCrime | null | Always null — no Canada-wide crime source is wired up (see docs/places.md). |
commute | object|null | durationMinutes and distanceKm, only when a destination was supplied and OpenRouteService returned a route. null otherwise — never computed speculatively. |
Country-level aggregate statistics plus locale reference data (currency, exchange rate, languages, tld, capital, borders).
| Parameter | In | Description |
|---|---|---|
code | path | ISO 3166-1 alpha-2 country code. E.g. US, DE, JP. |
lang | query | Optional. Localizes country_name and continent: en (default), pt, fr, de, zh-TW, ko, zh-CN, ja-JP, es-ES, ar-SA, hi-IN. |
format | query | Optional. Response format: json (default), csv, text, xml, yaml. |
Parse a User-Agent string. Returns browser, OS, device type, and bot detection. Reads the caller's User-Agent header by default, or pass any string via ?ua=.
| Parameter | In | Description |
|---|---|---|
ua | query | Optional. URL-encoded User-Agent string to parse. Defaults to the request User-Agent header. |
Validates and formats a phone number for free, always: region, line type, and E.164/national/international formatting via a self-hosted parser, no vendor call. When a per-IP/key monthly premium budget is available (100/month on Free, more on paid tiers — see pricing), the same request additionally returns a live carrier name, porting status, and a provider-classified phone type from a real-time carrier-lookup service — see source and the fields below. A localized country_name plus lang/lang_source are always included, regardless of tier — see Localization. Exhausting the premium budget, or any upstream issue, falls back seamlessly to the free fields above; the request never errors because of it. For a country-level network-operator reference instead of a per-number lookup, see GET /v1/carriers.
| Parameter | In | Description |
|---|---|---|
number | path | Required. Any phone number, ideally in E.164 format (e.g. +14155552671). URL-encode the leading +. |
country | query | Optional. 2-letter country code hint for parsing ambiguous national-format numbers, e.g. ?country=US. |
mode | query | Optional. ?mode=current requests the number's real-time (rather than SIM-issued) carrier where the premium tier is available. Ignored when the request falls back to the free tier. |
| Field | Type | Description |
|---|---|---|
valid | boolean | true if the number matches a valid numbering-plan pattern for its region. |
country_code | string|null | ISO 3166-1 alpha-2 region the number belongs to. |
national_format | string|null | Formatted in the region's own national convention. |
international_format | string|null | Formatted with country code and spacing, e.g. "+1 415 555 2671". |
e164 | string|null | Canonical E.164 form, e.g. "+14155552671". |
line_type | string|null | FIXED_LINE, MOBILE, FIXED_LINE_OR_MOBILE, VOIP, TOLL_FREE, PREMIUM_RATE, or similar. Many numbering plans (including North America's) can't distinguish fixed from mobile from the number alone. |
source | string | "premium" if a live carrier-lookup budget was available for this request, "local" if it fell back to the free parser above. carrier, phone_type, original_carrier, is_ported, phone_region, and country_prefix below are only populated when source is "premium" — country_name and lang/lang_source further down are always populated, regardless of tier. |
phone_type | string|null | One of the live carrier-lookup provider's own categories: mobile, fixed_line, voip, toll_free, premium_rate, shared_cost, personal_number, pager, uan, voicemail, or unknown — not just the first four, which are only the most common examples. null when source is "local" — distinct from line_type above, which comes from the local parser and uses a different set of categories. |
carrier | string|null | The number's current network operator name. null when source is "local". |
original_carrier | string|null | The network operator the number was originally issued on, if it has since been ported. null when source is "local" or the number hasn't been ported. |
is_ported | boolean|null | true if the number has been ported away from its original carrier. null when source is "local". |
phone_region | string|null | Locality/state-level detail for the number (e.g. "California"), finer-grained than country_code above. null when source is "local". |
country_prefix | string|null | The number's calling code without a leading "+" (e.g. "49" for Germany). null when source is "local". |
country_name | string|null | Full country display name (e.g. "Germany") — country_code above is the ISO alpha-2 form only ("DE"), not a display name. Unlike the premium-only fields above, this is always populated (whenever country_code is) and is localized via ?lang=/Accept-Language the same way every other endpoint's country_name field already is — see lang/lang_source below. |
lang | string | The language actually applied to country_name above — the resolved ?lang= value, or "en" if none was requested or the requested one isn't supported. |
lang_source | string | "param" if ?lang= was present on the request, "header" if Accept-Language was used instead, or "default" if neither was and English was used. Same meaning as the identically-named field on GET /v1/asn/:asn and GET /v1/country/:code. |
Batch validation. Every tier gets bulk access (unlike /v1/ip/bulk) — the per-tier batch cap is what keeps Free bounded, not a 403. Free: 10/request, 100/day. Pro: 100/request, 1,000/day. Enterprise: 1,000/request, 10,000/day. This budget is separate from your general daily request limit — see Rate limits.
| Parameter | In | Description |
|---|---|---|
body | body (JSON) | JSON array of phone number strings. |
Reference lookup: which mobile network operators (MCC/MNC) are known for a given country — not tied to any specific phone number. Sourced from a community-maintained MCC/MNC compilation (MIT-licensed, last synced 2026 — see docs repo for details); a static reference table, not a live feed, since numbering-plan assignments don't change often enough to warrant one.
| Parameter | In | Description |
|---|---|---|
country | query | Required. 2-letter country code, e.g. ?country=US. |
Email syntax validation, a live MX-record check via DNS-over-HTTPS, and disposable/temporary-domain detection against a community-maintained blocklist (CC0-licensed, synced daily). Does not verify the mailbox itself exists — that requires an SMTP handshake this API doesn't perform (see the field reference below for exactly what has_mx does and doesn't prove).
| Parameter | In | Description |
|---|---|---|
email | path | Required. Any email address, URL-encoded (the @ must be encoded as %40). |
| Field | Type | Description |
|---|---|---|
valid_syntax | boolean | true if the address matches a standard email syntax pattern. Does not verify the mailbox exists. |
domain | string|null | The domain portion, lowercased. null if valid_syntax is false. |
has_mx | boolean|null | true if the domain has at least one MX record right now. null if the syntax was invalid or the DNS query itself failed/timed out — treat null as unknown, not as false. |
disposable | boolean|null | true if the domain matches a known disposable/temporary-email provider. null under the same conditions as has_mx. |
source | "premium"|"local" | "premium" if a live verification pass ran for this request, "local" if it did not (rate limit reached, or the feature isn't enabled) — the fields above (valid_syntax through disposable) are always populated either way. |
is_reachable | string|null | The overall verdict from an optional live verification pass: "safe" (deliverable), "invalid" (rejected), "risky" (uncertain), or "unknown" (inconclusive — try again). null when the live pass did not run for this request (see source below). |
is_catch_all | boolean|null | true if the domain accepts mail for any address, meaning this specific mailbox's existence could not be confirmed. null when the live pass did not run. |
mailbox_full | boolean|null | true if the live pass found the mailbox reporting a full inbox. null when the live pass did not run. |
is_role_account | boolean|null | true if the address looks like a role account (e.g. support@, admin@) rather than an individual's mailbox. null when the live pass did not run. |
is_disabled | boolean|null | true if the live pass found the mailbox explicitly disabled. null when the live pass did not run. |
Combines whichever of IP, email, phone, and User-Agent you provide into one 0–100 score — the one thing none of ip-api.com, ipapi.co, ipgeolocation.io, ipify.org, ipregistry.co, ip2location.io, AbstractAPI, IPLocate.io, or BigDataCloud offer as a single fused signal (checked directly). All four inputs are optional and independent — pass any subset. ip defaults to your own IP and ua to your own User-Agent header if omitted, same convention as GET /v1/ip and GET /v1/ua. A signal you don't pass is simply absent from the response, not scored as clean — its weight is redistributed across whichever signals you did provide.
| Parameter | In | Description |
|---|---|---|
ip | query | Optional. Defaults to your own IP. IPv6 not yet scored (same limitation as the rest of the threat-data API). |
email | query | Optional. Any email address. |
phone | query | Optional. Any phone number. |
ua | query | Optional. Defaults to your own User-Agent header. |
| Field | Type | Description |
|---|---|---|
score | integer | 0-100 weighted composite across whichever signals were provided. |
risk_tier | string | "low" (<20), "medium" (20-44), "high" (45-69), or "critical" (70+). |
signals | object | Per-input breakdown — only keys for inputs you actually provided are present: ip, email, phone, user_agent. Each includes its own component score plus the same fields GET /v1/ip/:ip, /v1/email/:email, /v1/phone/:number, and /v1/ua already return for that input. |
Renders a URL in a real headless Chrome instance (Cloudflare Browser Rendering, not a third-party screenshot vendor) and returns a PNG. Internal/private targets (localhost, RFC 1918 ranges, link-local/cloud-metadata addresses) are rejected before the browser ever launches. Subject to Browser Rendering's own platform-level limits, which are much tighter than the rest of this API — expect this to throttle well before your regular daily request budget does on lower Cloudflare plans.
| Parameter | In | Description |
|---|---|---|
url | query | Required. Must be http or https. |
Identifies the language of a piece of text via local trigram-frequency analysis — no external vendor call, no AI-model inference, no logging. Input under 10 characters returns a null language; under ~30 characters can still return a best guess but reliable will be false.
| Parameter | In | Description |
|---|---|---|
text | query | Required. Up to 5,000 characters — longer input is truncated, not rejected. |
| Field | Type | Description |
|---|---|---|
language | object|null | code (ISO 639-1), iso639_3, name, and local_name for the best match. null if the text is too short or unrecognized ("und"). |
reliable | boolean | false for short input — treat the result as a guess, not a confident match. |
candidates | array | Up to 5 runner-up languages with relative scores, for closely-related language pairs. |
Converts an amount between two currencies (154 supported) using the same daily reference-rate feed that already powers currency_code/exchange_rate_usd on every IP, ASN, and country lookup — exposed here as a standalone conversion endpoint instead of nested inside a geolocation response.
| Parameter | In | Description |
|---|---|---|
from | query | Required. 3-letter ISO 4217 currency code. |
to | query | Required. 3-letter ISO 4217 currency code. |
amount | query | Optional. Defaults to 1. |
| Field | Type | Description |
|---|---|---|
result | number | Converted amount. |
rate | number | Units of to per 1 from. |
updated | string | ISO timestamp of the reference-rate sync this conversion used. |
Full exchange-rate table (154 currencies) rebased against any supported currency, not just USD.
| Parameter | In | Description |
|---|---|---|
base | query | Optional. Defaults to USD. |
| Field | Type | Description |
|---|---|---|
rates | object | Map of currency code → rate against base. |
Daily reference-rate history for one currency pair, up to 90 days back — the data behind the rate-trend chart on the currency converter tool. Backfilled once, then appended to daily by the same cron that updates GET /v1/currency/convert. This is a single daily reference rate over time, not a bid/ask trading spread.
| Parameter | In | Description |
|---|---|---|
target | query | Required. 3-letter ISO 4217 currency code to chart. |
base | query | Optional. Defaults to USD. |
days | query | Optional. One of 30, 60, 90. Defaults to 30. |
| Field | Type | Description |
|---|---|---|
series | array | Ascending-date array of { date, rate } points. |
stats | object | min, avg, max, and current across the returned series. |
Current conditions — and, with detail=1, an hourly outlook and multi-day forecast — for a place name, postal code or "lat,lon" pair, or for lat + lon or an ip. US places are answered by the National Weather Service, everywhere else by MET Norway. Add include= for the full document with units, up to 9 days, sun and moon, official alerts and more — see the include card below. The weather-lookup tool additionally auto-detects the caller's own location client-side before ever calling this endpoint explicitly.
| Parameter | In | Description |
|---|---|---|
q | query | City name, postal code, or "lat,lon" (max 100 characters). Or pass lat and lon, or ip, instead. One of the three is required. |
detail | query | Optional. "1" adds pressure_mb/visibility_km/dewpoint_c/sunrise/sunset/etc. to weather, plus a forecast object (hourly + daily). Omitted by default. |
| Field | Type | Description |
|---|---|---|
condition | string|null | Short text description. |
temp_c / temp_f | number|null | Current temperature. |
feels_like_c | number|null | "Feels like" temperature in Celsius. |
humidity_pct | number|null | Relative humidity. |
wind_kmph / wind_dir | number|null / string|null | Wind speed and 16-point compass direction. |
uv_index | number|null | UV index. |
location | object|null | Resolved name, region, country, and lat/lon for the queried place — not just the raw input. |
forecast.hourly[] | array | detail=1 only. Next 24h in 3-hour steps: hour_label, temp_c/temp_f, weather_code, chance_of_rain_pct. |
forecast.daily[] | array | detail=1 only. 3-day outlook: date, day_label, weather_code, condition, chance_of_rain_pct, max/min temp_c/temp_f. |
Live proxy to USPTO's Trademark Status & Document Retrieval (TSDR) system for case status by 8-digit serial number — a direct per-request call, not a cached or resold copy. Case-status data is a U.S. government work and public domain per USPTO's own terms of use, so every response carries a source/attribution field rather than caching or reselling it silently. Trademark logo/image data is deliberately out of scope — USPTO's terms carve image licensing out separately from the case-status text data this endpoint returns. Registration-number lookup isn't supported yet; only the serial-number path is documented by USPTO today. Try it on the Trademark Lookup tool.
| Parameter | In | Description |
|---|---|---|
serial | path | Required. Exactly 8 digits, e.g. 98267877. |
| Field | Type | Description |
|---|---|---|
source | string | Always "USPTO TSDR". |
attribution | string | Required attribution line per USPTO's terms of use. |
data | object | Raw case-status payload as returned by USPTO, passed through rather than reshaped — confirmed live 2026-08-12. Under data.trademarks[0]: status (filing/registration dates, markElement, descOfMark, tm5StatusDesc), parties.ownerGroups (owner name/address by role), gsList[] (goods/services description and classes), and prosecutionHistory[] (full case timeline). Field presence varies by application status — e.g. a pending application has no usRegistrationNumber yet. |
Live proxy to USPTO's Patent File Wrapper API (Open Data Portal) for grant status by 7-8 digit utility patent number — a direct per-request call, not a cached or resold copy. The originally-targeted source (PatentsView's Search API) was confirmed shut down 2026-03-20; this proxies its live successor instead. Patent File Wrapper data is a U.S. government work and public domain per USPTO's own terms of use, so every response carries a source/attribution field. Scope is application/prosecution-centric — no citation graph, patent family, legal-status field, or images the way Lens.org, Espacenet, or Google Patents show. Coverage starts at applications filed in 2001. Design, plant, and reissue patents aren't supported yet; only utility patents by granted patent number. Try it on the Patent Lookup tool.
| Parameter | In | Description |
|---|---|---|
number | path | Required. 7-8 digits, e.g. 12393227. Granted utility patent number only, not an application number. |
| Field | Type | Description |
|---|---|---|
source | string | Always "USPTO Patent File Wrapper". |
attribution | string | Required attribution line per USPTO's terms of use. |
data | object | Raw prosecution-record payload as returned by USPTO, passed through rather than reshaped — confirmed live 2026-08-12 against real patents (12393227, 9935534). Key fields: applicationMetaData (inventionTitle, filingDate, applicationStatusDescriptionText, applicationStatusDate, patentNumber, inventorBag[]), recordAttorney.attorneyBag[], applicationNumberText, and eventDataBag[] (full prosecution timeline). Returns 404 if the number doesn't exist, is a design/plant/reissue patent, or predates 2001 coverage. |
Paginated VPN, Tor, and threat intelligence feed. 200 entries per page. Each entry includes IP/CIDR, provider attribution, ISP, country, and classification type.
| Parameter | In | Description |
|---|---|---|
page | query | 0-indexed page number. Default: 0. |
type | query | Filter by type: VPN, Tor Exit Node, Tor Node, High-Risk Threat Intel. |
q | query | Filter by IP or CIDR substring. |
Metadata only — total entry count, per-source counts, and last sync time. Does not return IP data.
Paginated abusive network ranges. Scored 1-10 by cross-feed corroboration. Risk tiers: critical (8-10), high (5-7), medium (3-4).
| Parameter | In | Description |
|---|---|---|
page | query | 0-indexed page number. Default: 0. |
tier | query | Filter by risk tier: critical, high, medium. |
q | query | Filter by network range substring. |
source | query | Optional. Filter by source feed name exactly as returned in source_feed, e.g. "Spamhaus DROP" or "IPsum Threat Intel". |
Metadata only — total network count, per-source counts, risk tier breakdown, and last sync time.
Downloadable CSV export of the open datasets the API is already built on — same data, no new license. asn and ranges are always available; geo-city returns 503 until that source's dataset is active on a given deployment. geo-city exports one geo source's raw rows at a time, not a merged view — pass ?source=primary (default) or ?source=secondary.
| Parameter | In | Description |
|---|---|---|
kind | path | One of: asn, ranges, geo-city. |
source | query | geo-city only. primary (default) or secondary. |
Issues an API key instantly — no email, no signup. Every key starts on the Free tier (1,000 requests/day; bulk lookup not included). Pro and Enterprise keys unlock higher daily limits and bulk lookup (up to 100 IPs/request) — see pricing. Store the key — it can't be retrieved later.
Check how many requests a key has made. Pass the key on any /v1/* request via the X-API-Key header or ?api_key= to have it counted.
| Parameter | In | Description |
|---|---|---|
key | query | Required. A key returned by POST /v1/keys. |
Abuse-report contact for the network that owns an IP, from RDAP: the RIR, the network handle, the covering CIDR and the abuse mailbox(es). Use it to know where to send a report.
| Parameter | In | Description |
|---|---|---|
ip | path | IPv4 or IPv6 address. |
available is false (with a failure reason) when the registry could not be reached.
The actual IP ranges announced by an ASN, paginated, each with its country and the hosting / mobile / residential / bot / blacklist flags.
| Parameter | In | Description |
|---|---|---|
asn | path | ASN number without the AS prefix, e.g. 15169. |
limit | query | Ranges per page. Default 50, capped at 200. |
offset | query | Ranges to skip. Default 0. |
Registry (RDAP) record for an ASN: handle, name, country, allocation type, registration dates, the RIR and its contacts.
| Parameter | In | Description |
|---|---|---|
asn | path | ASN number without the AS prefix, e.g. 15169. |
available is false with a reason when the registry is unreachable.
Where an ASN actually sits: city-level points geolocated from a sample of its largest ranges, plus the most common cities. Powers the distribution map on the ASN page.
| Parameter | In | Description |
|---|---|---|
asn | path | ASN number without the AS prefix, e.g. 15169. |
A sample, not the full range list. Use /v1/asn/:asn/ranges for every range.
Domains hosted in an ASN, found by reverse-DNS (PTR) on a sample of its addresses. Each entry has the root domain, the PTR hostname and the sampled IP.
| Parameter | In | Description |
|---|---|---|
asn | path | ASN number without the AS prefix, e.g. 15169. |
Sampled, so it is a hint about what the ASN hosts, not a complete list. For one IP use /v1/ip/:ip/hosted-domains.
BGP relationships for an ASN: peers, upstream providers and downstream customers, with counts.
| Parameter | In | Description |
|---|---|---|
asn | path | ASN number without the AS prefix, e.g. 15169. |
available is false when the BGP data source could not be reached. Empty arrays with available: true mean none were reported for that ASN.
Directory of the IPv4 space: one entry per /8 block with how many ranges, countries and ASNs it holds and its top country. Start here, then drill down with /v1/ip-ranges/:a and /v1/ip-ranges/:a/:b.
The /16 blocks inside one /8 (for example 8.0.0.0/8), with the same per-block range, country and ASN counts.
| Parameter | In | Description |
|---|---|---|
a | path | First octet, 0-255. |
The assigned ranges inside one /16 (for example 8.8.0.0/16), each with the ASN it belongs to, plus the per-block counts.
| Parameter | In | Description |
|---|---|---|
a | path | First octet, 0-255. |
b | path | Second octet, 0-255. |
One slice of the Nearby payload for a coordinate, so a client can render each part as it arrives: reverse geocode, points of interest, or local crime statistics.
| Parameter | In | Description |
|---|---|---|
lat | query | Latitude, required. |
lon | query | Longitude, required. |
part | query | Which slice: geocode, poi or crime. An unknown value returns 400. |
country | query | part=crime only: ISO country code. Crime data exists for US and GB. |
region | query | part=crime only: state or region name, e.g. Utah. |
city | query | part=crime only: city name, e.g. Orem. |
lang | query | Optional. Localizes place labels (same codes as the localization table above). |
Driving time and distance between two points. A fast path: it skips the reverse geocoding and points-of-interest lookups that /v1/places/:ip does.
| Parameter | In | Description |
|---|---|---|
from_lat | query | Origin latitude, required. |
from_lon | query | Origin longitude, required. |
dest_lat | query | Destination latitude. Use with dest_lon, or pass dest_address instead. |
dest_lon | query | Destination longitude. |
dest_address | query | Destination as free text, geocoded for you. Used when dest_lat/dest_lon are absent. |
When no destination can be resolved the response has to: null, commute: null and a reason string.
The /v1/places/:ip payload anchored on a typed street address instead of an IP: geocode, locality, and an optional commute estimate.
| Parameter | In | Description |
|---|---|---|
q | query | Free-text address, required. |
dest_lat | query | Optional destination latitude, to include a commute estimate (with dest_lon). |
dest_lon | query | Optional destination longitude. |
dest_address | query | Optional destination as free text, instead of dest_lat/dest_lon. |
part | query | part=geo returns only the label and coordinates, for a faster first paint. |
lang | query | Optional. Localizes place labels (same codes as the localization table above). |
found is false when the address could not be geocoded.
Address autocomplete: a handful of matching places for a partial address, optionally biased toward a point.
| Parameter | In | Description |
|---|---|---|
q | query | Partial address text, required. |
near_lat | query | Optional latitude to bias results toward. |
near_lon | query | Optional longitude to bias results toward. |
The full weather document, for a place name, postal code, coordinates or IP. Pick the blocks you need with include: current, hourly, daily, astro (sun and moon), solar, indicators, alerts, marine, air_quality, nowcast, or all. US places come from the National Weather Service, everywhere else from MET Norway; meta.source says which. Without include you get the simple { query, weather } shape documented above.
| Parameter | In | Description |
|---|---|---|
q / lat+lon / ip | query | Where: q (city, postal code or "lat,lon"), or lat and lon, or ip. One of the three is required. |
country | query | Optional. ISO code or country name to settle an ambiguous q, for example country=FR for postal code 75001. |
include | query | Required for this shape. Comma-separated: current, hourly, daily, astro, solar, indicators, alerts, marine, air_quality, nowcast, or all. |
units | query | metric (default) or imperial. Field names follow the unit: temp_c becomes temp_f, wind_kmph becomes wind_mph, precip_mm becomes precip_in. |
wind_unit | query | kmh, ms, mph or kn. Changes the wind unit only. |
hours | query | Hourly entries to return, 1-240. Default 48. Near-term entries are hourly and later ones 6-hourly; step_hours says which. |
days | query | Daily entries to return, 1-9. Default 7. US data covers 7 days. |
meta.coverage says which fields exist for the place: rain probability, gusts and thunder probability are US and Nordic only and are null (never zero) elsewhere. alerts are official warnings for the US and Norway; marine, air_quality and nowcast cover Norway and nearby waters only and say available: false elsewhere. Anything in meta.derived is an ipto.to estimate, and indicators are derived flags, not official warnings. Keep meta.attribution if you display the data (MET Norway data is CC BY 4.0).
ipto.to's own daily record for places that have been looked up: the highest high, lowest low and largest precipitation seen across the day's forecast snapshots. Only places already requested have data, and it builds up over time.
| Parameter | In | Description |
|---|---|---|
q / lat+lon / ip | query | Where: q (city, postal code or "lat,lon"), or lat and lon, or ip. One of the three is required. |
days | query | Recent days to return, 1-400. Default 30. |
These are forecast snapshots, not station observations or reanalysis, so do not use them as observed history. 404 means no snapshots have been recorded for that place yet.
The available radar animation frames, oldest first, each with a Unix timestamp and the frame id to use in the radar tile URL.
A radar map tile (PNG) for one frame at a standard web-map z/x/y position. Proxied same-origin so a map can load it without a third-party request.
| Parameter | In | Description |
|---|---|---|
frame | path | Frame id from /v1/weather/radar/frames (lowercase hex). |
z | path | Zoom level. |
x | path | Tile column. |
y | path | Tile row. |
A satellite imagery map tile for a standard web-map z/x/y position, proxied same-origin.
| Parameter | In | Description |
|---|---|---|
z | path | Zoom level. |
x | path | Tile column. |
y | path | Tile row. |
Machine-readable OpenAPI 3.1 contract for /v1/email/:email, written so LLM and MCP agents can call the email endpoint without reading these docs.
IP lookup field reference
| Field | Type | Description |
|---|---|---|
ip | string | The queried IP address. |
version | string | "v4" or "v6". |
bogon | boolean | true if this is a private, reserved, or non-routable address. |
bogon_type | string|null | Reason code if bogon: "loopback", "private", "link-local", "multicast", "reserved", "documentation", "unspecified". |
asn | integer|null | Autonomous System Number that announces this IP. |
asn_name | string|null | Short name of the ASN (e.g. "GOOGLE"). |
asn_org | string|null | Organisation registered to the ASN (e.g. "Google LLC"). |
asn_type | string|null | ASN category: "hosting", "isp", "business", "education", "government", "mobile", "residential". |
continent | string|null | Full continent name (e.g. "North America"). |
continent_code | string|null | Two-letter continent code: AF AS EU NA SA OC AN. |
country_code | string|null | ISO 3166-1 alpha-2 country code (e.g. "US") of the country the IP is located in, from the same geolocation data as the city and latitude/longitude. When no geolocation record matches, it falls back to the ASN registry country (asn_country_code). country_name, flag, currency etc. are derived from it. |
country_name | string|null | Full English country name (e.g. "United States"). |
country_flag | string|null | Country emoji flag (e.g. "🇺🇸"). |
is_eu | boolean | true if the country is an EU member state (27 members). |
city | string|null | City name. Populated on self-lookup, and on explicit IPv4 lookups where either merged geo source (IP2Location or MaxMind) has a match — check geo_precision. Both geo sources are IPv4-only, so explicit IPv6 lookups never populate this (geo_precision caps at "country"). |
state | string|null | Administrative region/subdivision. Same availability as city. |
postal_code | string|null | Postal/ZIP code. Populated on self-lookup, and on explicit lookups where IP2Location (preferred) or MaxMind has a match — same availability as city. |
latitude | number|null | Decimal latitude. Same availability as city. |
longitude | number|null | Decimal longitude. Same availability as city. |
timezone | string|null | Timezone — same availability as city. An IANA identifier (e.g. "America/New_York") on self-lookup. On explicit lookups the format depends on which source matched: a UTC-offset string like "-08:00" from IP2Location, or an IANA name from MaxMind. |
geo_precision | string | How precise the location data is: "city", "country" (city-level lookup had no match), or "none". |
geo_country_code | string|null | Country the IP is located in, from the geolocation data (ISO 3166-1 alpha-2). Equals country_code whenever it is set. null if there is no geolocation match. |
asn_country_code | string|null | Registry country of the ASN holder (ISO 3166-1 alpha-2). Can differ from country_code for CDN, VPN, hosting or anycast networks announced from a different country than the holder is registered in. null if the IP has no ASN row. |
currency_code | string|null | ISO 4217 currency code for the country (e.g. "USD"). |
exchange_rate_usd | number|null | Units of local currency per 1 USD, from a daily-synced reference-rate feed covering 154 currencies. null for currencies outside that set. |
calling_code | string|null | Country calling code including + prefix (e.g. "+1"). |
languages | string[] | Official/recognized languages of the country, in English. |
tld | string[] | Country-code top-level domain(s), e.g. [".us"]. |
capital | string|null | Capital city name. |
borders | string[] | ISO alpha-2 codes of bordering countries. |
is_datacenter | boolean | true if the IP belongs to a hosting or cloud provider. |
is_mobile | boolean | true if the IP is registered to a mobile carrier. |
is_residential | boolean | true if the IP is classified as residential broadband. |
connection_type | string | "mobile", "datacenter", or "unknown" — derived from the same classification as is_mobile/is_datacenter. Deliberately does not attempt a residential-vs-corporate distinction: that isn't reliably derivable from ASN org-name text alone, so an ASN that isn't confidently classified as mobile or datacenter stays "unknown" rather than guessed. |
is_bot | boolean | true if the IP is associated with known bot infrastructure. |
is_proxy | boolean | true if the IP is a known proxy exit point. |
is_vpn | boolean | true if the IP falls within a known VPN server range from the threat feed. Threat feeds are IPv4-only today, so this is always false for an explicit IPv6 lookup rather than a real "not VPN" determination. |
is_tor | boolean | true if the IP is a confirmed Tor exit node. Same IPv4-only caveat as is_vpn. |
is_abuser | boolean | true if the IP falls within a network range flagged by multiple abuse intel feeds. Same IPv4-only caveat as is_vpn. |
abuse_score | integer|null | 1-10 severity score when is_abuser is true, from cross-feed corroboration. null otherwise. |
risk_tier | string|null | "critical" (8-10), "high" (5-7), or "medium" (3-4) — derived from abuse_score. null if not flagged. |
threat_type | string|null | The single most actionable matched category when is_tor/is_vpn/is_abuser overlap: "tor_exit" > "abuser" > "vpn". null if none match. |
flags | object | Backward-compatible object mirroring is_* fields. Keys: hosting, mobile, residential, bot, blacklisted, vpn, tor, abuser. |
meta.data_version | string | Dataset date used for the lookup (YYYY-MM-DD). "cf-edge" for self-lookup. "bogon" for private ranges. |
meta.cached | boolean | true if the response was served from the cache layer. |
meta.lang | string | The language actually used for this response — matches ?lang= when given, or the value resolved from Accept-Language, or "en". |
meta.lang_source | "param"|"header"|"default" | How lang was determined: "param" (?lang= was passed), "header" (resolved from Accept-Language), or "default" (neither was present). |
Output formats
IP, ASN, and country lookups all support ?format=.
| Format | Content-Type | Usage |
|---|---|---|
json | application/json | Default. Full structured response. |
csv | text/csv | Flat key,value pairs. Nested objects omitted. |
text | text/plain | key: value lines. One field per line. |
xml | application/xml | Full nested structure, arrays repeat the tag. |
yaml | application/x-yaml | Full nested structure. |
Localization
Pass ?lang= on /v1/ip, /v1/asn/:asn, or /v1/country/:code to localize country_name and continent. Everything else (ASN org names, city names, currency codes) stays in its original form — those are identifiers and proper nouns, not UI text. Unsupported codes fall back to English rather than erroring.
| Code | Language |
|---|---|
en | English (default) |
pt | Português |
fr | Français |
de | Deutsch |
zh-TW | 繁體中文 |
ko | 한국어 |
zh-CN | 简体中文 |
ja-JP | 日本語 |
es-ES | Español |
ar-SA | العربية |
hi-IN | हिन्दी |
No ?lang=? Send an Accept-Language header instead — the same endpoints read it too, so a plain curl -H "Accept-Language: es" https://ipto.to/v1/ip/8.8.8.8 localizes the response with no query string needed. An explicit ?lang= always wins when both are present. Note: the LANG/LC_ALL shell environment variable (e.g. LANG=es_ES.UTF-8 curl ...) only sets curl's own locale — curl never reads it or turns it into a header, so it has no effect on the response language. Accept-Language or ?lang= are the only two things that actually work.
Every /v1/ip, /v1/asn/:asn, and /v1/country/:code response includes lang and lang_source (nested under meta on /v1/ip; top-level on the other two) so you can always see exactly which language was applied and how it was chosen — "param", "header", or "default" — without needing to guess from the request you sent.
Full key+value localization is the default whenever you set a non-English lang — every field, including the nested flags and meta keys, comes back renamed into the target language, and the fixed enum values (risk_tier, threat_type, connection_type, geo_precision, bogon_type, asn_type) are translated too. Need the legacy English-keys shape instead? Add &localize=0 to /v1/ip to opt back out.
Code examples
JavaScript
Python
PHP
Go
Rate limits
Limits are enforced per day (UTC), counted by IP address on the free tier or by key on Pro/Enterprise. Exceeding your limit returns HTTP 429 with a Retry-After header. For production workloads, see pricing.
Applies to every /v1/* endpoint, the /mcp server, and the curl ipto.to / curl ipto.to/:ip shortcuts. GET /ip is the one exception — a plain header echo with no lookup cost, so it draws from no budget at all.
| Tier | Requests / day | Auth |
|---|---|---|
| Free | 1,000 | None |
| Pro | 100,000 | API key (header) |
| Enterprise | Unlimited | API key + dedicated endpoints |
Error codes
| Status | Meaning |
|---|---|
200 | OK. Response includes the data. |
202 | Feed data is syncing. Retry in 45 seconds. |
400 | Bad request. Invalid IP, ASN, or country code. |
404 | ASN or country not found in the dataset. |
503 | Dataset not yet available. Initial ingest may be running. |