{
  "openapi": "3.1.0",
  "info": {
    "title": "IPLogs VPN and proxy detection API",
    "version": "1.0.0",
    "description": "Free VPN, proxy, Tor, iCloud Private Relay, WARP and datacenter IP detection. No API key and no signup. Every answer lists the signals behind the verdict. Limits per client IP: Requests: 60 a minute (past it: 429 with Retry-After); Full checks: 1,000 a UTC day (past it: Still 200, answered from the fast path with \"mode\": \"fast\"); Bulk: 100 IPs per call; 3 calls in a burst, then 1 every 5 s (past it: 429 with Retry-After); Bad address: – (past it: 400 with {\"error\": \"invalid IP address\"}); Time: 30 s per full check (past it: 504 with {\"error\": \"detection timed out, please retry\"}).",
    "termsOfService": "https://iplogs.com/terms",
    "contact": {
      "name": "IPLogs",
      "email": "admin@iplogs.com",
      "url": "https://iplogs.com/docs"
    }
  },
  "externalDocs": {
    "description": "Guide, examples and signal catalogue",
    "url": "https://iplogs.com/docs"
  },
  "servers": [
    {
      "url": "https://iplogs.com"
    }
  ],
  "paths": {
    "/v1/check": {
      "post": {
        "operationId": "checkIp",
        "summary": "Check one IP address",
        "description": "Full check: published VPN server lists, network intelligence and live protocol handshakes, about 3 seconds for an address not seen recently. Send \"mode\": \"fast\" for an in-memory answer in microseconds.",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ip": {
                    "type": "string",
                    "description": "The IPv4 or IPv6 address to check. Leave it out to check whoever sent the request.",
                    "example": "185.220.101.34"
                  },
                  "mode": {
                    "type": "string",
                    "description": "\"fast\" answers from memory in microseconds: every list and network check, no live probes or lookups.",
                    "enum": [
                      "fast"
                    ]
                  },
                  "timezone": {
                    "type": "string",
                    "description": "The browser's IANA time zone, e.g. America/New_York, compared with the IP's location. Read only when the browser calls with no ip."
                  },
                  "language": {
                    "type": "string",
                    "description": "The browser's language, e.g. en-US, compared with the IP's country. Read only when the browser calls with no ip."
                  },
                  "webrtc_ip": {
                    "type": "string",
                    "description": "An address the browser exposed over WebRTC; a different one is a leak. Read only when the browser calls with no ip."
                  },
                  "user_agent": {
                    "type": "string",
                    "description": "Read from the request header already. Send it only to override that. Read only when the browser calls with no ip."
                  }
                }
              },
              "example": {
                "ip": "185.220.101.34"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verdict and evidence.",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Full-depth checks allowed per UTC day."
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Full-depth checks left today; at 0 answers come from the fast path."
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                },
                "description": "Unix time when the daily count resets."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckResponse"
                }
              }
            }
          },
          "400": {
            "description": "Not a public IP address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Over 60 requests a minute; wait Retry-After seconds.",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "504": {
            "description": "A full check ran past 30 seconds; retry, or use fast mode.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/bulk-check": {
      "post": {
        "operationId": "bulkCheck",
        "summary": "Check up to 100 IP addresses",
        "description": "One compact row per address, no live probes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ips"
                ],
                "properties": {
                  "ips": {
                    "type": "array",
                    "maxItems": 100,
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "example": {
                "ips": [
                  "185.220.101.34",
                  "146.70.116.130",
                  "8.8.8.8",
                  "10.0.0.1"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One row per address, in request order. Flags are omitted when false.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "duration_ms": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "ip": {
                            "type": "string"
                          },
                          "verdict": {
                            "type": "string",
                            "enum": [
                              "clean",
                              "suspicious",
                              "vpn_likely",
                              "vpn_detected"
                            ]
                          },
                          "score": {
                            "type": "number"
                          },
                          "is_vpn": {
                            "type": "boolean"
                          },
                          "is_proxy": {
                            "type": "boolean"
                          },
                          "proxy_type": {
                            "type": "string"
                          },
                          "is_tor": {
                            "type": "boolean"
                          },
                          "is_relay": {
                            "type": "boolean"
                          },
                          "is_hosting": {
                            "type": "boolean"
                          },
                          "is_crawler": {
                            "type": "boolean"
                          },
                          "is_satellite": {
                            "type": "boolean"
                          },
                          "reputation": {
                            "type": "string",
                            "enum": [
                              "allowlist",
                              "greylist",
                              "blocklist"
                            ]
                          },
                          "vpn_provider": {
                            "type": "string"
                          },
                          "country_code": {
                            "type": "string"
                          },
                          "asn": {
                            "type": "string"
                          },
                          "org": {
                            "type": "string"
                          },
                          "error": {
                            "type": "string",
                            "description": "Set instead of a verdict when the address cannot be checked."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Over the bulk rate: 3 calls in a burst, then 1 every 5 seconds.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/ip": {
      "get": {
        "operationId": "myIp",
        "summary": "The caller's own address",
        "description": "ipv4.iplogs.com and ipv6.iplogs.com each answer over one address family.",
        "responses": {
          "200": {
            "description": "The address the request came from.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ip": {
                      "type": "string"
                    },
                    "family": {
                      "type": "string",
                      "enum": [
                        "ipv4",
                        "ipv6"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "operationId": "health",
        "summary": "Service health",
        "responses": {
          "200": {
            "description": "The API is up.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok"
                      ]
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CheckResponse": {
        "type": "object",
        "required": [
          "score",
          "verdict",
          "is_vpn",
          "signals",
          "ip_info",
          "request_id"
        ],
        "properties": {
          "score": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "0 to 1. Higher means more evidence of a VPN, proxy, relay or Tor exit. Not a probability."
          },
          "verdict": {
            "type": "string",
            "enum": [
              "clean",
              "suspicious",
              "vpn_likely",
              "vpn_detected"
            ],
            "description": "clean below 0.30, suspicious below 0.60, vpn_likely below 0.85, then vpn_detected. Tor exits and relays use the same levels; vpn_provider says which."
          },
          "is_vpn": {
            "type": "boolean",
            "description": "True when the score is 0.60 or more."
          },
          "is_proxy": {
            "type": "boolean",
            "description": "The same test, counting only proxy-type evidence."
          },
          "proxy_type": {
            "type": "string",
            "enum": [
              "residential",
              "isp",
              "public",
              "datacenter"
            ],
            "description": "Set when is_proxy is true and the evidence says which kind: residential (exit of a residential proxy network), isp (static ISP proxy), public (open proxy anyone can use) or datacenter (a proxy company's own servers). Omitted for Tor and relays, which have their own flags."
          },
          "is_tor": {
            "type": "boolean",
            "description": "A current Tor exit."
          },
          "is_relay": {
            "type": "boolean",
            "description": "An iCloud Private Relay or Cloudflare WARP exit, or a corporate secure web gateway: relays many sites choose to allow."
          },
          "is_hosting": {
            "type": "boolean",
            "description": "A datacenter, cloud or CDN network rather than a home or mobile connection."
          },
          "is_crawler": {
            "type": "boolean",
            "description": "Inside a crawler's or uptime monitor's published address ranges (Googlebot, Bingbot, Applebot, OpenAI, Perplexity, DuckDuckGo, Common Crawl, Anthropic, Ahrefs, Pingdom, UptimeRobot and others). Verified by address, not by User-Agent."
          },
          "crawler": {
            "type": "object",
            "description": "Present only when is_crawler is true: name (e.g. GPTBot), operator (e.g. OpenAI) and purpose: search, ai_training, ai_search, user_fetch (a person asked an assistant to open the page), seo, monitor, archive, crawler or ai. Lets you allow ChatGPT-User while blocking GPTBot.",
            "properties": {
              "name": {
                "type": "string"
              },
              "operator": {
                "type": "string"
              },
              "purpose": {
                "type": "string",
                "enum": [
                  "search",
                  "ai_training",
                  "ai_search",
                  "user_fetch",
                  "seo",
                  "monitor",
                  "archive",
                  "crawler",
                  "ai"
                ]
              }
            }
          },
          "is_satellite": {
            "type": "boolean",
            "description": "The network serves users over satellite (Starlink, Viasat, HughesNet, OneWeb, inflight and maritime Wi-Fi). Location can be far from the user, and round-trip checks are skipped there, since satellite links split connections the way proxies do."
          },
          "reputation": {
            "type": "string",
            "enum": [
              "allowlist",
              "greylist",
              "blocklist"
            ],
            "description": "allowlist (no abuse, nothing hidden), greylist (no abuse, but hidden or hosted) or blocklist (recent attacks or abuse). Separate from the verdict."
          },
          "confidence": {
            "type": "number"
          },
          "mode": {
            "type": "string",
            "enum": [
              "fast"
            ],
            "description": "\"fast\" when the answer came from the in-memory path. Absent on a full check."
          },
          "signals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Signal"
            },
            "description": "Each check run: type, weight, matched, and a short detail. Server lists, Tor exits and our own sightings also carry last_seen (UTC): when our data last confirmed the address. Only matched signals add to the score."
          },
          "ip_info": {
            "$ref": "#/components/schemas/IPInfo"
          },
          "request_id": {
            "type": "string",
            "description": "Quote it when you report a wrong result."
          }
        }
      },
      "Signal": {
        "type": "object",
        "required": [
          "type",
          "weight",
          "matched",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "Signal type, e.g. tor_exit, vpn_relay_list, active_probe_openvpn. The full catalogue is at https://iplogs.com/docs#sig-h."
          },
          "weight": {
            "type": "number",
            "description": "What this signal adds to the score when matched."
          },
          "matched": {
            "type": "boolean"
          },
          "detail": {
            "type": "string",
            "description": "One line of evidence in plain English."
          },
          "last_seen": {
            "type": "string",
            "format": "date-time",
            "description": "When our data last confirmed the address, for list and sighting signals."
          }
        }
      },
      "IPInfo": {
        "type": "object",
        "description": "ip, asn, org, country, country_code, city, reverse_dns, prefix, and vpn_provider: the operator (Mullvad, Tor, Apple) when one is known.",
        "properties": {
          "ip": {
            "type": "string"
          },
          "asn": {
            "type": "string",
            "example": "AS60729"
          },
          "org": {
            "type": "string"
          },
          "isp": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "country_code": {
            "type": "string",
            "example": "DE"
          },
          "city": {
            "type": "string"
          },
          "lat": {
            "type": "number"
          },
          "lon": {
            "type": "number"
          },
          "type": {
            "type": "string",
            "description": "residential, mobile, datacenter or hosting; empty when unknown."
          },
          "is_vpn": {
            "type": "boolean"
          },
          "is_proxy": {
            "type": "boolean"
          },
          "vpn_provider": {
            "type": "string",
            "description": "The operator (Mullvad, Tor, Apple) when one is known."
          },
          "vpn_provider_sources": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Every kind of source that matched the address."
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        }
      }
    }
  }
}