API Reference

Simple, consistent endpoints. CORS enabled. No authentication required. Fully multilingual — every field, key and value, translates into 10+ languages.

Base URL

https://ipto.to

Quick start

# Detect your own IP curl https://ipto.to/v1/ip # Look up any IP curl https://ipto.to/v1/ip/8.8.8.8 # Bulk lookup (up to 100 IPs, Pro/Enterprise only) curl -X POST https://ipto.to/v1/ip/bulk \ -H "Content-Type: application/json" \ --data '["1.1.1.1","8.8.8.8","9.9.9.9"]'

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.

curl "https://ipto.to/v1/ip/8.8.8.8?lang=de" # "landesname":"Vereinigte Staaten","ist_vpn":false, ... # every key & value comes back translated -- add &localize=0 for the legacy English-keys shape

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.

{ "mcpServers": { "ipto": { "url": "https://ipto.to/mcp" } } }

For stdio-only clients, bridge it: npx mcp-remote https://ipto.to/mcp

curl -X POST https://ipto.to/mcp \ -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"ip_lookup","arguments":{"ip":"8.8.8.8"}}}'

Endpoints

GET/ip

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.

curl https://ipto.to/ip # 203.0.113.7
GET/v1/ip

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.

curl https://ipto.to/v1/ip # the lookup for the calling IP: same shape as /v1/ip/:ip below
GET/v1/ip/:ip

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.

ParameterInDescription
ippathRequired. Any valid IPv4 or IPv6 address.
langqueryOptional. Localizes country_name and continent: en (default), pt, fr, de, zh-TW, ko, zh-CN, ja-JP, es-ES, ar-SA, hi-IN.
fieldsqueryOptional. Comma-separated list of fields to include. E.g. ?fields=ip,country_code,is_vpn
formatqueryOptional. Response format: json (default), csv, text, xml, yaml.
curl https://ipto.to/v1/ip/8.8.8.8 # only the fields you want: curl "https://ipto.to/v1/ip/8.8.8.8?fields=ip,asn,country_code,city" # {"ip":"8.8.8.8","asn":15169,"country_code":"US","city":"Mountain View"}
GET/v1/search/:query

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.

ParameterInDescription
querypathRequired. An IP, ASN, country code, email, or phone number.
FieldTypeDescription
querystringThe decoded input as received.
detected_typestring"ip", "asn", "country", "email", or "phone".
ipobject|nullPresent when detected_type is "ip" — same shape as GET /v1/ip/:ip.
rpkiobject|nullPresent for IP queries — same shape as GET /v1/ip/:ip/rpki.
companyobject|nullPresent for IP queries — same shape as GET /v1/ip/:ip/company.
whoisobject|nullPresent for IP queries — same shape as GET /v1/ip/:ip/whois.
carrierobject|nullPresent for IP queries — same shape as GET /v1/ip/:ip/carrier.
asn / country / email / phoneobject|nullPresent 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.

curl https://ipto.to/v1/search/8.8.8.8
GET/v1/ip/:ip/hostname

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.

ParameterInDescription
ippathRequired. Any valid IPv4 or IPv6 address.
curl https://ipto.to/v1/ip/8.8.8.8/hostname # {"ip":"8.8.8.8","hostname":"dns.google"}
GET/v1/ip/:ip/rpki

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.

ParameterInDescription
ippathRequired. Any valid IPv4 or IPv6 address.
FieldTypeDescription
asninteger|nullThe currently-announcing ASN, resolved live — not derived from this site's own ASN dataset.
prefixstring|nullThe currently-announced CIDR prefix.
statusstring"valid", "invalid", or "unknown".
validating_roasarrayThe specific ROA(s) RIPEstat matched, each with origin/prefix/max_length/validity.
validatorstring|nullWhich RPKI validator RIPEstat used (e.g. "routinator").
reasonstring|nullExplains an "unknown" result — e.g. no covering ROA exists, or RIPEstat was unreachable.
curl https://ipto.to/v1/ip/8.8.8.8/rpki # {"ip":"8.8.8.8","asn":15169,"prefix":"8.8.8.0/24","status":"valid",...}
GET/v1/rpki

Explicit (ASN, prefix) RPKI validation — for when you already know the exact route you want checked, rather than the IP-derived convenience above.

ParameterInDescription
asnqueryRequired. ASN number, with or without the AS prefix. E.g. 15169 or AS15169.
prefixqueryRequired. CIDR prefix, e.g. 8.8.8.0/24.
curl "https://ipto.to/v1/rpki?asn=15169&prefix=8.8.8.0/24"
GET/v1/ip/:ip/company

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.

ParameterInDescription
ippathRequired. Any valid IPv4 or IPv6 address.
FieldTypeDescription
company_namestring|nullThe resolved company name — null when confidence is "low" (the two sources disagree, so neither is presented as authoritative) or "none".
rdap_org_namestring|nullThe RDAP registrant name, independent of company_name.
asn_org_namestring|nullThe ASN's own organization name, independent of company_name.
confidencestring"high" (sources agree), "medium" (only one source available), "low" (sources disagree — both shown), or "none".
reasonstringPlain-English explanation of the confidence tier.
curl https://ipto.to/v1/ip/1.1.1.1/company # {"ip":"1.1.1.1","company_name":null,"rdap_org_name":"APNIC Research and Development","asn_org_name":"Cloudflare, Inc.","confidence":"low",...}
GET/v1/ip/:ip/whois

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.

ParameterInDescription
ippathRequired. Any valid IPv4 or IPv6 address.
FieldTypeDescription
availablebooleanfalse if no RDAP record could be resolved — see reason.
reasonstring|nullExplains an available: false result.
startAddress / endAddress / cidrstring|nullThe registered network block.
netName / netHandle / parentHandlestring|nullRIR-assigned identifiers for the network object.
countrystring|nullRegistrant country, per RDAP.
allocationTypestring|nulle.g. "direct allocation", "reassigned".
registeredDate / lastChangedDatestring|nullISO timestamps from the RDAP record.
rirstring|nullWhich regional registry (ARIN, RIPE, APNIC, LACNIC, AFRINIC) served the record.
contactsarrayEvery contact RDAP has on file, each with roles, name, email, and handle — not just the first match.
curl https://ipto.to/v1/ip/8.8.8.8/whois # {"ip":"8.8.8.8","available":true,"cidr":"8.8.8.0/24","netName":"LVLT-GOGL-8-8-8","rir":"ARIN","contacts":[...],...}
GET/v1/ip/:ip/carrier

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.

ParameterInDescription
ippathRequired. Any valid IPv4 or IPv6 address.
FieldTypeDescription
is_mobile_networkbooleanfalse stops here with an empty directory — no guess for a non-mobile ASN.
asn_organizationstring|nullThe ASN's own organization name.
matched_carrier_entriesarrayMCC/MNC directory rows whose carrier name fuzzy-matches asn_organization, scoped to the IP's own country.
notestringExplains what this field can and can't claim.
curl https://ipto.to/v1/ip/8.8.8.8/carrier # {"ip":"8.8.8.8","is_mobile_network":false,"asn_organization":null,"matched_carrier_entries":[],...}
GET/v1/ip/:ip/hosted-domains

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.

ParameterInDescription
ippathRequired. Any valid IPv4 or IPv6 address.
FieldTypeDescription
ptr_hostnamestring|nullThe IP's reverse-DNS hostname, or null if it has no PTR record.
root_domainstring|nullThe registrable domain derived from ptr_hostname.
subdomains_seenarray|nullSubdomains 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.
limitationsstringExplains what this feature structurally can't detect (no PTR, shared hosting, uncertified domains).
curl https://ipto.to/v1/ip/8.8.8.8/hosted-domains # {"ip":"8.8.8.8","ptr_hostname":"dns.google","root_domain":"dns.google","subdomains_seen":["dns.google","dns.google.com",...],...}
POST/v1/ip/bulk

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.

ParameterInDescription
bodybody (JSON)JSON array of IP strings. E.g. ["1.1.1.1","8.8.8.8"]
curl -X POST https://ipto.to/v1/ip/bulk \ -H "Content-Type: application/json" \ --data '["1.1.1.1","8.8.8.8"]'
GET/v1/asn/:asn

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).

ParameterInDescription
asnpathASN number without the AS prefix. E.g. 15169 for Google.
langqueryOptional. Localizes country_name and continent: en (default), pt, fr, de, zh-TW, ko, zh-CN, ja-JP, es-ES, ar-SA, hi-IN.
formatqueryOptional. Response format: json (default), csv, text, xml, yaml.
curl https://ipto.to/v1/asn/15169
GET/v1/places/:ip

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.

ParameterInDescription
ippathRequired. Any valid IPv4 address. IPv6 is not yet supported.
dest_latqueryOptional. Destination latitude for a commute estimate — pair with dest_lon.
dest_lonqueryOptional. Destination longitude for a commute estimate — pair with dest_lat.
dest_addressqueryOptional. Free-text destination address for a commute estimate, forward-geocoded automatically. Ignored if dest_lat/dest_lon are also supplied.
FieldTypeDescription
geocodeobject|nullcountry, countryCode, region, city, neighbourhood, displayName — null if the IP has no city-level location to geocode from.
locality.restaurants / .parks / .schools / .transitarrayUp to 4 nearest entries per category, each with name and distanceMeters.
locality.notablearrayNotable nearby landmarks with a short description, via Wikidata.
locality.usCrimeobject|nullNearest-agency violent/property crime totals — US only, requires an FBI_CRIME_API_KEY on the deployment. null everywhere else.
locality.ukCrimeobject|nullStreet-level incident summary within 1 mile — GB only.
locality.caCrimenullAlways null — no Canada-wide crime source is wired up (see docs/places.md).
commuteobject|nulldurationMinutes and distanceKm, only when a destination was supplied and OpenRouteService returned a route. null otherwise — never computed speculatively.
curl https://ipto.to/v1/places/8.8.8.8 curl "https://ipto.to/v1/places/8.8.8.8?dest_address=Space+Needle,+Seattle"
GET/v1/country/:code

Country-level aggregate statistics plus locale reference data (currency, exchange rate, languages, tld, capital, borders).

ParameterInDescription
codepathISO 3166-1 alpha-2 country code. E.g. US, DE, JP.
langqueryOptional. Localizes country_name and continent: en (default), pt, fr, de, zh-TW, ko, zh-CN, ja-JP, es-ES, ar-SA, hi-IN.
formatqueryOptional. Response format: json (default), csv, text, xml, yaml.
curl https://ipto.to/v1/country/US # {"country_code":"US","country_name":"United States","continent":"North America","currency_code":"USD", # "calling_code":"+1","languages":["English"],"tld":[".us"],"capital":"Washington D.C.","borders":["CA","MX"], # "is_eu":false,"ip_range_count":151633,"ip_range_count_v6":30329,"asn_count":18243, ...}
GET/v1/ua

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=.

ParameterInDescription
uaqueryOptional. URL-encoded User-Agent string to parse. Defaults to the request User-Agent header.
curl "https://ipto.to/v1/ua?ua=Mozilla%2F5.0+%28iPhone%3B+CPU+iPhone+OS+17_0%29"
GET/v1/phone/:number

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.

ParameterInDescription
numberpathRequired. Any phone number, ideally in E.164 format (e.g. +14155552671). URL-encode the leading +.
countryqueryOptional. 2-letter country code hint for parsing ambiguous national-format numbers, e.g. ?country=US.
modequeryOptional. ?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.
FieldTypeDescription
validbooleantrue if the number matches a valid numbering-plan pattern for its region.
country_codestring|nullISO 3166-1 alpha-2 region the number belongs to.
national_formatstring|nullFormatted in the region's own national convention.
international_formatstring|nullFormatted with country code and spacing, e.g. "+1 415 555 2671".
e164string|nullCanonical E.164 form, e.g. "+14155552671".
line_typestring|nullFIXED_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.
sourcestring"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_typestring|nullOne 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.
carrierstring|nullThe number's current network operator name. null when source is "local".
original_carrierstring|nullThe 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_portedboolean|nulltrue if the number has been ported away from its original carrier. null when source is "local".
phone_regionstring|nullLocality/state-level detail for the number (e.g. "California"), finer-grained than country_code above. null when source is "local".
country_prefixstring|nullThe number's calling code without a leading "+" (e.g. "49" for Germany). null when source is "local".
country_namestring|nullFull 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.
langstringThe 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_sourcestring"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.
curl "https://ipto.to/v1/phone/+14155552671"
POST/v1/phone/bulk

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.

ParameterInDescription
bodybody (JSON)JSON array of phone number strings.
curl -X POST https://ipto.to/v1/phone/bulk \ -H "Content-Type: application/json" \ --data '["+14155552671","+442071838750"]'
GET/v1/carriers

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.

ParameterInDescription
countryqueryRequired. 2-letter country code, e.g. ?country=US.
curl "https://ipto.to/v1/carriers?country=US"
GET/v1/email/:email

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).

ParameterInDescription
emailpathRequired. Any email address, URL-encoded (the @ must be encoded as %40).
FieldTypeDescription
valid_syntaxbooleantrue if the address matches a standard email syntax pattern. Does not verify the mailbox exists.
domainstring|nullThe domain portion, lowercased. null if valid_syntax is false.
has_mxboolean|nulltrue 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.
disposableboolean|nulltrue 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_reachablestring|nullThe 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_allboolean|nulltrue 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_fullboolean|nulltrue if the live pass found the mailbox reporting a full inbox. null when the live pass did not run.
is_role_accountboolean|nulltrue 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_disabledboolean|nulltrue if the live pass found the mailbox explicitly disabled. null when the live pass did not run.
curl "https://ipto.to/v1/email/user%40example.com" # When the live-verification budget allows, the same response also includes the # source/is_reachable/is_catch_all/mailbox_full/is_role_account/is_disabled fields above: # {"email":"user@example.com","valid_syntax":true,"domain":"example.com","has_mx":true, # "disposable":false,"source":"premium","is_reachable":"safe","is_catch_all":false, # "mailbox_full":false,"is_role_account":false,"is_disabled":false}
GET/v1/fraud-score

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.

ParameterInDescription
ipqueryOptional. Defaults to your own IP. IPv6 not yet scored (same limitation as the rest of the threat-data API).
emailqueryOptional. Any email address.
phonequeryOptional. Any phone number.
uaqueryOptional. Defaults to your own User-Agent header.
FieldTypeDescription
scoreinteger0-100 weighted composite across whichever signals were provided.
risk_tierstring"low" (<20), "medium" (20-44), "high" (45-69), or "critical" (70+).
signalsobjectPer-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.
curl "https://ipto.to/v1/fraud-score?ip=1.2.3.4&email=test%40mailinator.com"
GET/v1/screenshot

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.

ParameterInDescription
urlqueryRequired. Must be http or https.
curl "https://ipto.to/v1/screenshot?url=https://example.com" --output screenshot.png
GET/v1/language

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.

ParameterInDescription
textqueryRequired. Up to 5,000 characters — longer input is truncated, not rejected.
FieldTypeDescription
languageobject|nullcode (ISO 639-1), iso639_3, name, and local_name for the best match. null if the text is too short or unrecognized ("und").
reliablebooleanfalse for short input — treat the result as a guess, not a confident match.
candidatesarrayUp to 5 runner-up languages with relative scores, for closely-related language pairs.
curl "https://ipto.to/v1/language?text=Bonjour%2C+comment+allez-vous%3F"
GET/v1/currency/convert

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.

ParameterInDescription
fromqueryRequired. 3-letter ISO 4217 currency code.
toqueryRequired. 3-letter ISO 4217 currency code.
amountqueryOptional. Defaults to 1.
FieldTypeDescription
resultnumberConverted amount.
ratenumberUnits of to per 1 from.
updatedstringISO timestamp of the reference-rate sync this conversion used.
curl "https://ipto.to/v1/currency/convert?from=USD&to=EUR&amount=100"
GET/v1/currency/rates

Full exchange-rate table (154 currencies) rebased against any supported currency, not just USD.

ParameterInDescription
basequeryOptional. Defaults to USD.
FieldTypeDescription
ratesobjectMap of currency code → rate against base.
curl "https://ipto.to/v1/currency/rates?base=EUR"
GET/v1/currency/history

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.

ParameterInDescription
targetqueryRequired. 3-letter ISO 4217 currency code to chart.
basequeryOptional. Defaults to USD.
daysqueryOptional. One of 30, 60, 90. Defaults to 30.
FieldTypeDescription
seriesarrayAscending-date array of { date, rate } points.
statsobjectmin, avg, max, and current across the returned series.
curl "https://ipto.to/v1/currency/history?base=USD&target=EUR&days=90"
GET/v1/weather

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.

ParameterInDescription
qqueryCity name, postal code, or "lat,lon" (max 100 characters). Or pass lat and lon, or ip, instead. One of the three is required.
detailqueryOptional. "1" adds pressure_mb/visibility_km/dewpoint_c/sunrise/sunset/etc. to weather, plus a forecast object (hourly + daily). Omitted by default.
FieldTypeDescription
conditionstring|nullShort text description.
temp_c / temp_fnumber|nullCurrent temperature.
feels_like_cnumber|null"Feels like" temperature in Celsius.
humidity_pctnumber|nullRelative humidity.
wind_kmph / wind_dirnumber|null / string|nullWind speed and 16-point compass direction.
uv_indexnumber|nullUV index.
locationobject|nullResolved name, region, country, and lat/lon for the queried place — not just the raw input.
forecast.hourly[]arraydetail=1 only. Next 24h in 3-hour steps: hour_label, temp_c/temp_f, weather_code, chance_of_rain_pct.
forecast.daily[]arraydetail=1 only. 3-day outlook: date, day_label, weather_code, condition, chance_of_rain_pct, max/min temp_c/temp_f.
curl "https://ipto.to/v1/weather?q=Tokyo"
curl "https://ipto.to/v1/weather?q=Tokyo&detail=1"
GET/v1/trademark/:serial

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.

ParameterInDescription
serialpathRequired. Exactly 8 digits, e.g. 98267877.
FieldTypeDescription
sourcestringAlways "USPTO TSDR".
attributionstringRequired attribution line per USPTO's terms of use.
dataobjectRaw 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.
curl "https://ipto.to/v1/trademark/98267877" # ANTHROPIC, Reg. No. 8296420
GET/v1/patent/:number

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.

ParameterInDescription
numberpathRequired. 7-8 digits, e.g. 12393227. Granted utility patent number only, not an application number.
FieldTypeDescription
sourcestringAlways "USPTO Patent File Wrapper".
attributionstringRequired attribution line per USPTO's terms of use.
dataobjectRaw 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.
curl "https://ipto.to/v1/patent/12393227" # WEARABLE COMPUTING DEVICE, granted 2025
GET/v1/feeds

Paginated VPN, Tor, and threat intelligence feed. 200 entries per page. Each entry includes IP/CIDR, provider attribution, ISP, country, and classification type.

ParameterInDescription
pagequery0-indexed page number. Default: 0.
typequeryFilter by type: VPN, Tor Exit Node, Tor Node, High-Risk Threat Intel.
qqueryFilter by IP or CIDR substring.
curl "https://ipto.to/v1/feeds?page=0&type=VPN" # {"metadata":{...},"pagination":{"page":0,"page_size":200,"total":11200,"total_pages":56,"has_next":true}, # "ranges":[{"ip":"2.26.157.0/24","type":"VPN","provider":"Datacamp Limited","isp":"Datacamp Limited","country":"GB"}, ...]}
GET/v1/feeds/stats

Metadata only — total entry count, per-source counts, and last sync time. Does not return IP data.

curl https://ipto.to/v1/feeds/stats # {"total_tracked_ranges":34561,"last_updated":"2026-10-06T18:00:44.321Z", # "sources":[{"name":"X4BNet VPN Ranges","type":"VPN","count":11200}, ...]}
GET/v1/abuse

Paginated abusive network ranges. Scored 1-10 by cross-feed corroboration. Risk tiers: critical (8-10), high (5-7), medium (3-4).

ParameterInDescription
pagequery0-indexed page number. Default: 0.
tierqueryFilter by risk tier: critical, high, medium.
qqueryFilter by network range substring.
sourcequeryOptional. Filter by source feed name exactly as returned in source_feed, e.g. "Spamhaus DROP" or "IPsum Threat Intel".
curl "https://ipto.to/v1/abuse?page=0&tier=critical&source=Spamhaus%20DROP" # {"metadata":{...},"pagination":{"page":0,"page_size":200,"total":1640,"total_pages":9,"has_next":true}, # "networks":[{"network_range":"1.10.16.0/20","abuse_score":10,"risk_tier":"critical","source_feed":"Spamhaus DROP"}, ...]}
GET/v1/abuse/stats

Metadata only — total network count, per-source counts, risk tier breakdown, and last sync time.

curl https://ipto.to/v1/abuse/stats # {"total_networks_tracked":18300,"last_updated":"2026-10-06T18:00:15.762Z", # "classification":{"ipsum_threat_intel":16660,"spamhaus_drop":1641,"spamhaus_edrop":0}, # "sources":[{"name":"IPsum Threat Intel","count":16660}, ...]}
GET/v1/datasets/:kind

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.

ParameterInDescription
kindpathOne of: asn, ranges, geo-city.
sourcequerygeo-city only. primary (default) or secondary.
curl -OJ https://ipto.to/v1/datasets/asn curl -OJ https://ipto.to/v1/datasets/ranges curl -OJ "https://ipto.to/v1/datasets/geo-city?source=secondary"
POST/v1/keys

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.

curl -X POST https://ipto.to/v1/keys
GET/v1/keys/usage

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.

ParameterInDescription
keyqueryRequired. A key returned by POST /v1/keys.
curl "https://ipto.to/v1/keys/usage?key=ipto_xxxxxxxx"
GET/v1/ip/:ip/abuse

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.

ParameterInDescription
ippathIPv4 or IPv6 address.
curl https://ipto.to/v1/ip/8.8.8.8/abuse # {"available":true,"failure":null,"ip":"8.8.8.8","rir":"arin","network":"GOGL","cidr":"8.8.8.0/24", # "contacts":[{"name":"Abuse","email":"network-abuse@google.com","handle":"ABUSE5250-ARIN"}]}

available is false (with a failure reason) when the registry could not be reached.

GET/v1/asn/:asn/ranges

The actual IP ranges announced by an ASN, paginated, each with its country and the hosting / mobile / residential / bot / blacklist flags.

ParameterInDescription
asnpathASN number without the AS prefix, e.g. 15169.
limitqueryRanges per page. Default 50, capped at 200.
offsetqueryRanges to skip. Default 0.
curl "https://ipto.to/v1/asn/15169/ranges?limit=1&offset=0" # {"asn":15169,"total":153,"limit":1,"offset":0,"ranges":[{"start_ip":"8.8.4.0","end_ip":"8.8.4.255", # "country_code":"US","is_hosting":false,"is_mobile":false,"is_residential":false,"is_bot":false,"is_blacklisted":false}]}
GET/v1/asn/:asn/whois

Registry (RDAP) record for an ASN: handle, name, country, allocation type, registration dates, the RIR and its contacts.

ParameterInDescription
asnpathASN number without the AS prefix, e.g. 15169.
curl https://ipto.to/v1/asn/15169/whois # {"asn":15169,"available":true,"reason":null,"handle":"AS15169","name":"GOOGLE","country":"US", # "allocationType":"...","registeredDate":"...","lastChangedDate":"...","rir":"arin","contacts":[...]}

available is false with a reason when the registry is unreachable.

GET/v1/asn/:asn/geo-sample

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.

ParameterInDescription
asnpathASN number without the AS prefix, e.g. 15169.
curl https://ipto.to/v1/asn/15169/geo-sample # {"asn":15169,"sampled":true,"sample_size":30,"points":[{"city":"Council Bluffs","state":"Iowa", # "country_code":"US","latitude":41.2591,"longitude":-95.8517}, ...],"top_cities":[...]}

A sample, not the full range list. Use /v1/asn/:asn/ranges for every range.

GET/v1/asn/:asn/hosted-domains

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.

ParameterInDescription
asnpathASN number without the AS prefix, e.g. 15169.
curl https://ipto.to/v1/asn/15169/hosted-domains # {"asn":15169,"sampled":true,"sample_size":5,"domains":[{"root_domain":"googleusercontent.com", # "ptr_hostname":"0.0.214.35.bc.googleusercontent.com","sample_ip":"35.214.0.0"}, ...]}

Sampled, so it is a hint about what the ASN hosts, not a complete list. For one IP use /v1/ip/:ip/hosted-domains.

GET/v1/asn/:asn/relationships

BGP relationships for an ASN: peers, upstream providers and downstream customers, with counts.

ParameterInDescription
asnpathASN number without the AS prefix, e.g. 15169.
curl https://ipto.to/v1/asn/15169/relationships # {"asn":15169,"available":true,"peers":[],"upstreams":[],"downstreams":[], # "peer_count":0,"upstream_count":0,"downstream_count":0}

available is false when the BGP data source could not be reached. Empty arrays with available: true mean none were reported for that ASN.

GET/v1/ip-ranges

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.

curl https://ipto.to/v1/ip-ranges # {"blocks":[{"octet":1,"range_count":814,"country_count":15,"asn_count":182,"top_country":"KR", # "top_country_share":35,"top_country_name":"South Korea"}, ...],"data_version":"2026-10-06"}
GET/v1/ip-ranges/:a

The /16 blocks inside one /8 (for example 8.0.0.0/8), with the same per-block range, country and ASN counts.

ParameterInDescription
apathFirst octet, 0-255.
curl https://ipto.to/v1/ip-ranges/8 # {"octet1":8,"cidr":"8.0.0.0/8","blocks":[{"octet":8,"range_count":...,"country_count":...,"asn_count":..., # "top_country":"US", ...}, ...]}
GET/v1/ip-ranges/:a/:b

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.

ParameterInDescription
apathFirst octet, 0-255.
bpathSecond octet, 0-255.
curl https://ipto.to/v1/ip-ranges/8/8 # {"octet1":8,"octet2":8,"cidr":"8.8.0.0/16","blocks":[{"octet":1,"range_count":1,"country_count":1,"asn_count":1, # "top_country":"US", ...}, ...],"ranges_total":30,"ranges":[...]}
GET/v1/nearby

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.

ParameterInDescription
latqueryLatitude, required.
lonqueryLongitude, required.
partqueryWhich slice: geocode, poi or crime. An unknown value returns 400.
countryquerypart=crime only: ISO country code. Crime data exists for US and GB.
regionquerypart=crime only: state or region name, e.g. Utah.
cityquerypart=crime only: city name, e.g. Orem.
langqueryOptional. Localizes place labels (same codes as the localization table above).
curl "https://ipto.to/v1/nearby?lat=40.2969&lon=-111.6948&part=geocode" # {"geocode":{...reverse-geocoded place...}} curl "https://ipto.to/v1/nearby?lat=40.2969&lon=-111.6948&part=poi" # {"locality":{...nearby points of interest...}} curl "https://ipto.to/v1/nearby?lat=40.2969&lon=-111.6948&part=crime&country=US&region=Utah&city=Orem" # {"locality":{"ukCrime":null,"usCrime":{"agencyName":"Orem Police Department","agencyType":"City", # "distanceKm":4.4,"year":2025,"categories":[{"category":"violent-crime","label":"Violent crime","totalOffenses":129}, ...]}}}
GET/v1/commute

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.

ParameterInDescription
from_latqueryOrigin latitude, required.
from_lonqueryOrigin longitude, required.
dest_latqueryDestination latitude. Use with dest_lon, or pass dest_address instead.
dest_lonqueryDestination longitude.
dest_addressqueryDestination as free text, geocoded for you. Used when dest_lat/dest_lon are absent.
curl "https://ipto.to/v1/commute?from_lat=40.2969&from_lon=-111.6948&dest_lat=40.7608&dest_lon=-111.8910" # {"from":{"lat":40.2969,"lon":-111.6948},"to":{"lat":40.7608,"lon":-111.891}, # "commute":{"durationMinutes":44,"distanceKm":63.5}}

When no destination can be resolved the response has to: null, commute: null and a reason string.

GET/v1/address-lookup

The /v1/places/:ip payload anchored on a typed street address instead of an IP: geocode, locality, and an optional commute estimate.

ParameterInDescription
qqueryFree-text address, required.
dest_latqueryOptional destination latitude, to include a commute estimate (with dest_lon).
dest_lonqueryOptional destination longitude.
dest_addressqueryOptional destination as free text, instead of dest_lat/dest_lon.
partquerypart=geo returns only the label and coordinates, for a faster first paint.
langqueryOptional. Localizes place labels (same codes as the localization table above).
curl "https://ipto.to/v1/address-lookup?q=1600+Pennsylvania+Ave+NW+Washington+DC" # {"query":"1600 Pennsylvania Ave NW Washington DC","found":true,"label":"...","latitude":38.89, # "longitude":-77.03,"geocode":{...},"commute":null,"locality":{...}} curl "https://ipto.to/v1/address-lookup?q=1600+Pennsylvania+Ave+NW+Washington+DC&part=geo" # {"query":"...","found":true,"label":"...","latitude":38.89,"longitude":-77.03}

found is false when the address could not be geocoded.

GET/v1/address-suggest

Address autocomplete: a handful of matching places for a partial address, optionally biased toward a point.

ParameterInDescription
qqueryPartial address text, required.
near_latqueryOptional latitude to bias results toward.
near_lonqueryOptional longitude to bias results toward.
curl "https://ipto.to/v1/address-suggest?q=1600+Penn&near_lat=38.9&near_lon=-77.0" # {"suggestions":[{"label":"Penn Quarter, Ward 2, Washington, District of Columbia, United States", # "lat":38.8949563,"lon":-77.0224592}, ...]}
GET/v1/weather?include=

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.

ParameterInDescription
q / lat+lon / ipqueryWhere: q (city, postal code or "lat,lon"), or lat and lon, or ip. One of the three is required.
countryqueryOptional. ISO code or country name to settle an ambiguous q, for example country=FR for postal code 75001.
includequeryRequired for this shape. Comma-separated: current, hourly, daily, astro, solar, indicators, alerts, marine, air_quality, nowcast, or all.
unitsquerymetric (default) or imperial. Field names follow the unit: temp_c becomes temp_f, wind_kmph becomes wind_mph, precip_mm becomes precip_in.
wind_unitquerykmh, ms, mph or kn. Changes the wind unit only.
hoursqueryHourly entries to return, 1-240. Default 48. Near-term entries are hourly and later ones 6-hourly; step_hours says which.
daysqueryDaily entries to return, 1-9. Default 7. US data covers 7 days.
curl "https://ipto.to/v1/weather?lat=38.8977&lon=-77.0365&include=current,daily,alerts&days=1" # {"query":"38.8977,-77.0365","place":{"name":"Washington","region":"DC","country":"United States","country_code":"US", # "lat":38.8977,"lon":-77.0365,"timezone":"America/New_York"}, # "current":{"time":"2026-10-07T12:00-04:00","observed":false,"temp_c":18.9,"feels_like_c":18.9,"humidity_pct":52,"wind_kmph":12.9, # "wind_gust_kmph":24.1,"wind_dir":"S","uv_index":5.4,"condition":"Sunny","weather_code":113,"is_day":true, ...}, # "daily":[{"date":"2026-10-07","tmax_c":22.2,"tmin_c":12.8,"precip_mm":0,"precip_prob_pct":1,"gust_max_kmph":25.9,"uv_index_max":5.4, ...}], # "alerts":{"available":true,"source":"NWS","official":true,"items":[]}, # "meta":{"source":"NOAA National Weather Service","fallback_used":false,"coverage":{"precip_prob":true,"wind_gusts":true, ...}, # "derived":["current.uv_index","current.wet_bulb", ...],"timezone":"America/New_York", ...}} curl "https://ipto.to/v1/weather?q=Oslo&include=marine,air_quality,nowcast&units=imperial" # Norway-only blocks come back with available: true there and available: false everywhere else

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).

GET/v1/weather/history

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.

ParameterInDescription
q / lat+lon / ipqueryWhere: q (city, postal code or "lat,lon"), or lat and lon, or ip. One of the three is required.
daysqueryRecent days to return, 1-400. Default 30.
curl "https://ipto.to/v1/weather/history?q=Tokyo&days=7" # {"query":"Tokyo","place":{"name":"Tokyo","cc":"JP", ...}, # "days":[{"date":"2026-10-06","tmax_c":24.1,"tmin_c":17.8,"precip_mm":1.2,"condition_code":296,"source":"metno","snapshots":4}, ...], # "note":"Daily envelope of day-0 forecast snapshots (highest high, lowest low, largest precipitation seen), not station observations."}

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.

GET/v1/weather/radar/frames

The available radar animation frames, oldest first, each with a Unix timestamp and the frame id to use in the radar tile URL.

curl https://ipto.to/v1/weather/radar/frames # {"frames":[{"time":1791316200,"id":"fdc753dba723"},{"time":1791316800,"id":"2314000028f2"}, ...]}
GET/v1/weather/radar/:frame/:z/:x/:y.png

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.

ParameterInDescription
framepathFrame id from /v1/weather/radar/frames (lowercase hex).
zpathZoom level.
xpathTile column.
ypathTile row.
# 1. pick the latest frame id FRAME=$(curl -s https://ipto.to/v1/weather/radar/frames | python3 -c "import sys,json;print(json.load(sys.stdin)['frames'][-1]['id'])") # 2. fetch a tile (image/png) curl -o radar.png "https://ipto.to/v1/weather/radar/$FRAME/5/6/12.png"
GET/v1/weather/satellite/:z/:x/:y.png

A satellite imagery map tile for a standard web-map z/x/y position, proxied same-origin.

ParameterInDescription
zpathZoom level.
xpathTile column.
ypathTile row.
curl -o satellite.jpg "https://ipto.to/v1/weather/satellite/5/6/12.png" # the response body is the tile image (served as image/jpeg despite the .png path)
GET/v1/openapi/email.json

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.

curl https://ipto.to/v1/openapi/email.json # {"openapi":"3.1.0","info":{...},"servers":[{"url":"https://ipto.to"}],"paths":{"/v1/email/{email}":{...}}}

IP lookup field reference

FieldTypeDescription
ipstringThe queried IP address.
versionstring"v4" or "v6".
bogonbooleantrue if this is a private, reserved, or non-routable address.
bogon_typestring|nullReason code if bogon: "loopback", "private", "link-local", "multicast", "reserved", "documentation", "unspecified".
asninteger|nullAutonomous System Number that announces this IP.
asn_namestring|nullShort name of the ASN (e.g. "GOOGLE").
asn_orgstring|nullOrganisation registered to the ASN (e.g. "Google LLC").
asn_typestring|nullASN category: "hosting", "isp", "business", "education", "government", "mobile", "residential".
continentstring|nullFull continent name (e.g. "North America").
continent_codestring|nullTwo-letter continent code: AF AS EU NA SA OC AN.
country_codestring|nullISO 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_namestring|nullFull English country name (e.g. "United States").
country_flagstring|nullCountry emoji flag (e.g. "🇺🇸").
is_eubooleantrue if the country is an EU member state (27 members).
citystring|nullCity 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").
statestring|nullAdministrative region/subdivision. Same availability as city.
postal_codestring|nullPostal/ZIP code. Populated on self-lookup, and on explicit lookups where IP2Location (preferred) or MaxMind has a match — same availability as city.
latitudenumber|nullDecimal latitude. Same availability as city.
longitudenumber|nullDecimal longitude. Same availability as city.
timezonestring|nullTimezone — 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_precisionstringHow precise the location data is: "city", "country" (city-level lookup had no match), or "none".
geo_country_codestring|nullCountry 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_codestring|nullRegistry 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_codestring|nullISO 4217 currency code for the country (e.g. "USD").
exchange_rate_usdnumber|nullUnits of local currency per 1 USD, from a daily-synced reference-rate feed covering 154 currencies. null for currencies outside that set.
calling_codestring|nullCountry calling code including + prefix (e.g. "+1").
languagesstring[]Official/recognized languages of the country, in English.
tldstring[]Country-code top-level domain(s), e.g. [".us"].
capitalstring|nullCapital city name.
bordersstring[]ISO alpha-2 codes of bordering countries.
is_datacenterbooleantrue if the IP belongs to a hosting or cloud provider.
is_mobilebooleantrue if the IP is registered to a mobile carrier.
is_residentialbooleantrue if the IP is classified as residential broadband.
connection_typestring"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_botbooleantrue if the IP is associated with known bot infrastructure.
is_proxybooleantrue if the IP is a known proxy exit point.
is_vpnbooleantrue 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_torbooleantrue if the IP is a confirmed Tor exit node. Same IPv4-only caveat as is_vpn.
is_abuserbooleantrue if the IP falls within a network range flagged by multiple abuse intel feeds. Same IPv4-only caveat as is_vpn.
abuse_scoreinteger|null1-10 severity score when is_abuser is true, from cross-feed corroboration. null otherwise.
risk_tierstring|null"critical" (8-10), "high" (5-7), or "medium" (3-4) — derived from abuse_score. null if not flagged.
threat_typestring|nullThe single most actionable matched category when is_tor/is_vpn/is_abuser overlap: "tor_exit" > "abuser" > "vpn". null if none match.
flagsobjectBackward-compatible object mirroring is_* fields. Keys: hosting, mobile, residential, bot, blacklisted, vpn, tor, abuser.
meta.data_versionstringDataset date used for the lookup (YYYY-MM-DD). "cf-edge" for self-lookup. "bogon" for private ranges.
meta.cachedbooleantrue if the response was served from the cache layer.
meta.langstringThe 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=.

FormatContent-TypeUsage
jsonapplication/jsonDefault. Full structured response.
csvtext/csvFlat key,value pairs. Nested objects omitted.
texttext/plainkey: value lines. One field per line.
xmlapplication/xmlFull nested structure, arrays repeat the tag.
yamlapplication/x-yamlFull nested structure.
curl "https://ipto.to/v1/ip/8.8.8.8?format=csv" curl "https://ipto.to/v1/ip/8.8.8.8?format=xml" curl "https://ipto.to/v1/ip/8.8.8.8?format=yaml" curl "https://ipto.to/v1/ip/8.8.8.8?fields=ip,country_code,is_vpn,is_tor"

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.

CodeLanguage
enEnglish (default)
ptPortuguês
frFrançais
deDeutsch
zh-TW繁體中文
ko한국어
zh-CN简体中文
ja-JP日本語
es-ESEspañol
ar-SAالعربية
hi-INहिन्दी
curl "https://ipto.to/v1/ip/8.8.8.8?lang=de" # "landesname":"Vereinigte Staaten","kontinent":"Nordamerika","ist_vpn":false, ... # every key is renamed and every enum value translated -- this is the default for any non-English lang

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.

curl -H "Accept-Language: es" https://ipto.to/v1/ip/8.8.8.8 # "nombre_país":"Estados Unidos","continente":"Norteamérica" -- full key+value localization, same as ?lang=es # equivalent, explicit form: curl "https://ipto.to/v1/ip/8.8.8.8?lang=es" # does NOT work -- LANG only sets curl's own locale, never a header: LANG=es_ES.UTF-8 curl https://ipto.to/v1/ip/8.8.8.8

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.

curl -s -H "Accept-Language: es" https://ipto.to/v1/ip/8.8.8.8 | python3 -m json.tool # "meta": { "data_version": "...", "cached": true, "lang": "es-ES", "lang_source": "header" }

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.

curl -s "https://ipto.to/v1/ip/8.8.8.8?lang=de&localize=0" | python3 -m json.tool # opts back out to the legacy English-keys shape: # "country_name": "Vereinigte Staaten", "geo_precision": "country", "is_vpn": false, # "flags": { "hosting": false, "mobile": false, ... }, # "meta": { "lang": "de", "lang_source": "param", ... }

Code examples

JavaScript

const res = await fetch("https://ipto.to/v1/ip/8.8.8.8"); const data = await res.json(); console.log(data.country_name, data.asn_org, data.is_vpn);

Python

import requests r = requests.get("https://ipto.to/v1/ip/8.8.8.8") print(r.json()["country_name"], r.json()["is_vpn"])

PHP

$data = json_decode(file_get_contents("https://ipto.to/v1/ip/8.8.8.8"), true); echo $data["country_name"] . " " . $data["asn_org"];

Go

resp, _ := http.Get("https://ipto.to/v1/ip/8.8.8.8") var data map[string]interface{} json.NewDecoder(resp.Body).Decode(&data)

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.

TierRequests / dayAuth
Free1,000None
Pro100,000API key (header)
EnterpriseUnlimitedAPI key + dedicated endpoints

Error codes

StatusMeaning
200OK. Response includes the data.
202Feed data is syncing. Retry in 45 seconds.
400Bad request. Invalid IP, ASN, or country code.
404ASN or country not found in the dataset.
503Dataset not yet available. Initial ingest may be running.