openapi: 3.1.0

info:
  title: IpQuery.Info IP Intelligence API
  version: "1.0"
  summary: Geo, network, and threat intelligence for any IPv4 or IPv6 address.
  description: |
    Single endpoint that enriches an IP address with geolocation, ASN, mobile/satellite
    detection, VPN/proxy/Tor classification, cloud provider, crawler detection, spam lists,
    anycast/multicast flags, and RIPE network data.

    **Authentication** — every request requires an `apiKey` query parameter.

    **IP selection** — provide `ip` (single), `ipList` (comma-separated batch), send a
    `POST` body with a JSON/plain-text list, or omit selectors to query the caller's own
    IP address.

    **Batch limits** — bulk lookups are capped at 1,000 IP addresses per request and
    consume one request unit per address.

    **Response shaping** — pass `fields` as a comma-separated list to return only the
    fields your workflow needs. `query_ip_address` is always returned.

    **Rate limiting** — determined by the service tier associated with the API key.

    **Plan-based fields** — some response fields depend on the plan attached to the API
    key. Fields your plan does not include are **omitted from the response object**. They
    are never returned as `null`, and requesting one never raises an error: a `null` would
    coerce to `0` in most languages, which for a field such as `risk_score` would read as
    "perfectly safe" and silently break a fraud rule. Test for presence, not for value.

    Every response carries an `X-IpQuery-Plan` header naming the resolved plan. Any result
    with withheld fields also carries `gated_fields` (the names) and `gated_fields_info`
    (a link). Each property below is annotated with `x-ipquery-plan`; see
    https://ipquery.info/pricing#fields.

    **Compatibility** — `is_vpn` means *commercial VPN* and excludes privacy relays such as
    iCloud Private Relay; use `is_proxy` (unchanged) for "arrived via any intermediary", or
    `privacy_network_type` for the specific classification. Pass `compat=v1` to restore the
    pre-2026-08 `is_vpn` / `is_proxy` / `proxy_type` behaviour; that parameter is supported
    until 1 February 2027.

    **Key restrictions** — API keys may optionally restrict by allowed `Origin`,
    `Referer`, or client IP. Origin and referrer allowlists apply only when the
    caller sends those headers (typical of browser `fetch`/XHR). Server-side
    callers without those headers are not blocked by origin/referrer rules; use
    client-IP allowlists to lock keys to your backend egress addresses.

servers:
  - url: https://ipquery.info
    description: Production

paths:
  /api:
    get:
      operationId: queryIp
      summary: Query one or more IP addresses
      description: |
        Returns an array of result objects — one per queried IP.  Each object contains all
        enrichment fields that could be resolved for that address.

        When the API key has the `statsOnly` option enabled the response is a plain
        acknowledgement object instead of the results array.
      parameters:
        - name: apiKey
          in: query
          required: true
          description: API key issued by IpQuery.Info.
          schema:
            type: string
            example: "abc123xyz"

        - name: ip
          in: query
          required: false
          description: |
            Single IPv4 or IPv6 address to query. Omit all IP selectors to query the
            caller's own IP address.
          schema:
            type: string
            example: "8.8.8.8"

        - name: ipList
          in: query
          required: false
          description: |
            Comma-separated list of IPv4/IPv6 addresses for a bulk query (whitespace around
            commas is stripped). Bulk lookups are capped at 1,000 addresses and statistics
            are not recorded for bulk queries.
          schema:
            type: string
            example: "8.8.8.8,1.1.1.1,2606:4700:4700::1111"

        - name: fields
          in: query
          required: false
          description: |
            Comma-separated response fields to include, for example
            `country_iso_code,is_vpn,risk_score`. `query_ip_address` is always returned.
          schema:
            type: string
            example: "country_iso_code,is_vpn,risk_score"

        - name: tag
          in: query
          required: false
          description: |
            Arbitrary label (max 20 characters) recorded with single-address queries in
            statistics. Useful for correlating API calls to campaigns or user segments.
          schema:
            type: string
            maxLength: 20
            example: "signup-flow"

        - name: ua
          in: query
          required: false
          description: |
            User-Agent string to use for crawler detection instead of the request's
            `User-Agent` header.  Only meaningful when `ip` is also provided.
          schema:
            type: string
            example: "Googlebot/2.1 (+http://www.google.com/bot.html)"

        - name: reverse
          in: query
          required: false
          description: |
            When `true`, performs a reverse-DNS (PTR) lookup and adds a `hostname`
            field.  The lookup is bounded (~150 ms) and cached; on a slow resolve the
            field is omitted for that call and filled from cache on the next identical
            query.  Off by default so normal lookups are not slowed.
          schema:
            type: boolean
            default: false
            example: true

        - name: compat
          in: query
          required: false
          description: |
            Pass `v1` to restore the pre-2026-08 `is_vpn` / `is_proxy` / `proxy_type`
            semantics, in which iCloud Private Relay egress counted as VPN. Provided so
            existing integrations are not broken by the narrowing. Supported until
            1 February 2027; see https://ipquery.info/changelog.
          schema:
            type: string
            enum: [v1]
            example: "v1"

      responses:
        "200":
          description: |
            Successful query.  Returns a JSON array with one result object per queried IP.
            If the API key has `statsOnly` mode enabled, returns a plain acknowledgement
            object instead.
          headers:
            X-IpQuery-Plan:
              description: |
                The plan this request resolved to — `free`, `trial`, `basic`, `advanced`,
                `enterprise`, or `unrestricted`. Always present.
              schema:
                type: string
                example: "basic"
            X-IpQuery-Gated-Fields:
              description: |
                Count of field values withheld across the whole response by the caller's
                plan. Present only when at least one field was withheld.
              schema:
                type: integer
                example: 3
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/IpResultArray"
                  - $ref: "#/components/schemas/StatsOnlyResponse"
              examples:
                single_ip:
                  summary: Single IP — residential broadband
                  value:
                    - query_ip_address: "93.184.216.34"
                      city: "Norwell"
                      continent: "North America"
                      country: "United States"
                      country_iso_code: "US"
                      subdivision: "Massachusetts"
                      subdivision_iso_code: "MA"
                      latitude: "42.1596"
                      longitude: "-70.8217"
                      time_zone: "America/New_York"
                      registered_country: "United States"
                      query_language: "en"
                      currency: "USD"
                      gmt_offset: -18000
                      is_eu_member: false
                      asn: "AS15133"
                      asn_name: "Edgecast Inc."
                      asn_country: "US"
                      is_tor_exit_node: false
                      is_tor_full_node: false
                      is_spam: false
                      is_spam_drop: false
                      is_mobile: false
                      is_satellite: false
                      is_cloud: false
                      is_anycast: false
                      is_multicast: false
                      is_vpn: false
                      is_proxy: false
                      is_crawler: false
                      simplemaps:
                        city: "Boston"
                        admin_name: "Massachusetts"
                        country: "United States"
                        iso2: "US"
                        iso3: "USA"
                        capital: ""
                        population: 675647
                stats_only:
                  summary: API key has statsOnly mode enabled
                  value:
                    msg: "Recorded statistics successfully."
                    code: 200

        "401":
          description: Missing API key or invalid `tag` parameter.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              examples:
                missing_key:
                  summary: No apiKey parameter sent
                  value:
                    msg: "Must send api key as `apiKey` parameter."
                    code: 401
                tag_too_long:
                  summary: tag exceeds 20 characters
                  value:
                    msg: "The parameter `tag` must be less than 20 characters long."
                    code: 401

        "400":
          description: Invalid `POST` request body or unsupported bulk request format.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                msg: "JSON POST body must contain `ips`, `ipList`, or `ip`."
                code: 400

        "402":
          description: API key is invalid or has exhausted its monthly request quota.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                msg: "Api key is invalid or has reached request limit for the month."
                code: 402

        "403":
          description: API key restriction rejected the request. Origin/referrer rules apply only when the caller sends those headers; server-side callers are gated by client-IP allowlists instead.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                msg: "API key origin restriction rejected this request."
                code: 403

        "404":
          description: API key not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                msg: "Api key not found."
                code: 404

        "413":
          description: Bulk request exceeds the 1,000-address limit.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                msg: "Batch lookups are limited to 1000 IP addresses per request."
                code: 413

        "429":
          description: >-
            Per-IP rate limit exceeded. Returned only for keys that enable the
            "Rate Limit by IP Address" option, which throttles any single caller
            to one request every five minutes.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
              example:
                msg: "Rate limit exceeded for this IP. Try again in 240 seconds."
                code: 429

    post:
      operationId: queryIpPost
      summary: Query IP addresses with a request body
      description: |
        Accepts the same authentication and shaping query parameters as `GET /api`, plus a
        request body for bulk input. JSON bodies can be an array of IP strings, a string, or
        an object with `ips`, `ipList`, or `ip`. Plain-text bodies can contain comma- or
        newline-separated addresses.
      parameters:
        - name: apiKey
          in: query
          required: true
          description: API key issued by IpQuery.Info.
          schema:
            type: string
            example: "abc123xyz"

        - name: ip
          in: query
          required: false
          description: Single IPv4 or IPv6 address to query.
          schema:
            type: string
            example: "8.8.8.8"

        - name: ipList
          in: query
          required: false
          description: Comma-separated list of IPv4/IPv6 addresses for a bulk query.
          schema:
            type: string
            example: "8.8.8.8,1.1.1.1"

        - name: fields
          in: query
          required: false
          description: Comma-separated response fields to include.
          schema:
            type: string
            example: "country_iso_code,is_vpn,risk_score"

        - name: tag
          in: query
          required: false
          description: Arbitrary label, max 20 characters, recorded for single-address analytics.
          schema:
            type: string
            maxLength: 20
            example: "signup-flow"

        - name: ua
          in: query
          required: false
          description: User-Agent string to use for crawler detection instead of the request header.
          schema:
            type: string
            example: "Googlebot/2.1 (+http://www.google.com/bot.html)"

        - name: reverse
          in: query
          required: false
          description: When `true`, performs a bounded reverse-DNS lookup.
          schema:
            type: boolean
            default: false
            example: true

        - name: compat
          in: query
          required: false
          description: |
            Pass `v1` to restore the pre-2026-08 `is_vpn` / `is_proxy` / `proxy_type`
            semantics, in which iCloud Private Relay egress counted as VPN. Provided so
            existing integrations are not broken by the narrowing. Supported until
            1 February 2027; see https://ipquery.info/changelog.
          schema:
            type: string
            enum: [v1]
            example: "v1"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              oneOf:
                - type: array
                  items:
                    type: string
                - type: string
                - type: object
                  properties:
                    ips:
                      oneOf:
                        - type: array
                          items:
                            type: string
                        - type: string
                    ipList:
                      oneOf:
                        - type: array
                          items:
                            type: string
                        - type: string
                    ip:
                      type: string
            examples:
              array:
                value: ["8.8.8.8", "1.1.1.1"]
              object:
                value:
                  ips: ["8.8.8.8", "1.1.1.1"]
          text/plain:
            schema:
              type: string
              example: "8.8.8.8\n1.1.1.1"
      responses:
        "200":
          description: Successful query. Returns a JSON array with one result object per queried IP.
          headers:
            X-IpQuery-Plan:
              description: |
                The plan this request resolved to — `free`, `trial`, `basic`, `advanced`,
                `enterprise`, or `unrestricted`. Always present.
              schema:
                type: string
                example: "basic"
            X-IpQuery-Gated-Fields:
              description: |
                Count of field values withheld across the whole response by the caller's
                plan. Present only when at least one field was withheld.
              schema:
                type: integer
                example: 3
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/IpResultArray"
                  - $ref: "#/components/schemas/StatsOnlyResponse"
        "400":
          description: Invalid request body or unsupported bulk request format.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Missing API key or invalid `tag` parameter.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "402":
          description: API key is invalid or has exhausted its monthly request quota.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: API key restriction rejected the request. Origin/referrer rules apply only when the caller sends those headers; server-side callers are gated by client-IP allowlists instead.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: API key not found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: Bulk request exceeds the 1,000-address limit.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Per-IP rate limit exceeded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

components:
  schemas:

    # ── Top-level response wrappers ──────────────────────────────────────────

    IpResultArray:
      type: array
      description: One entry per queried IP address.
      items:
        oneOf:
          - $ref: "#/components/schemas/IpResult"
          - $ref: "#/components/schemas/IpNotFoundError"

    StatsOnlyResponse:
      type: object
      description: Returned instead of results when the API key has statsOnly mode enabled.
      required: [msg, code]
      properties:
        msg:
          type: string
          example: "Recorded statistics successfully."
        code:
          type: integer
          example: 200

    ErrorResponse:
      type: object
      required: [msg, code]
      properties:
        msg:
          type: string
        code:
          type: integer

    IpNotFoundError:
      type: object
      description: Returned inside the results array when a specific IP could not be resolved.
      required: [msg, code, query_ip_address]
      properties:
        msg:
          type: string
          example: "Could not find ip address in database."
        code:
          type: integer
          example: 404
        query_ip_address:
          type: string
          example: "192.0.2.1"

    # ── Full IP result object ────────────────────────────────────────────────

    IpResult:
      type: object
      description: All enrichment data for a single IP address.
      properties:

        # — Core geolocation (GeoLite2 / DB-IP MMDB) —
        query_ip_address:
          type: string
          description: The IP address that was queried.
          example: "8.8.8.8"
        query_language:
          type: string
          description: Language code used for place-name localisation.
          example: "en"
        city:
          type: string
          description: City associated with the IP.
          example: "Mountain View"
        continent:
          type: string
          description: Continent name.
          example: "North America"
        country:
          type: string
          description: Full country name.
          example: "United States"
        country_iso_code:
          type: string
          description: ISO 3166-1 alpha-2 country code.
          example: "US"
        registered_country:
          type: string
          description: Country where the IP block is registered (may differ from city country).
          example: "United States"
        subdivision:
          type: string
          description: First-level administrative subdivision (state, province, region).
          example: "California"
        subdivision_iso_code:
          type: string
          description: ISO code for the subdivision.
          example: "CA"
        latitude:
          type: string
          description: Decimal latitude (returned as a string).
          example: "37.3861"
        longitude:
          type: string
          description: Decimal longitude (returned as a string).
          example: "-122.0839"
        time_zone:
          type: string
          description: IANA time zone identifier.
          example: "America/Los_Angeles"

        # — Country metadata (CountryMetaDataProvider) —
        currency:
          type: string
          description: ISO 4217 currency code for the country.
          example: "USD"
        gmt_offset:
          type: integer
          description: Standard UTC offset in seconds (does not account for DST).
          example: -28800
        is_eu_member:
          type: boolean
          description: True if the country is one of the 27 EU member states (as of 2024).
          example: false

        # — ASN / ISP (IpToAsnDataProvider) —
        asn:
          type: string
          description: Autonomous System Number in "ASxxxx" format.
          example: "AS15169"
        asn_name:
          type: string
          description: Name of the ASN operator.
          example: "Google LLC"
        asn_country:
          type: string
          description: Country code registered to the ASN.
          example: "US"

        # — Mobile / satellite (MobileDataProvider) —
        is_mobile:
          type: boolean
          description: True if the IP belongs to a mobile carrier network.
          example: false
        is_satellite:
          type: boolean
          description: True if the IP belongs to a satellite internet provider (Starlink, Viasat, HughesNet, etc.).
          example: false
        connection_type:
          type: string
          description: |
            Coarse connection classification. `mobile`/`satellite` come from the
            carrier detection; `hosting`/`isp` are filled in for everything else.
          enum: [mobile, satellite, hosting, isp]
          example: "mobile"

        # — Tor (TorDataProvider) —
        is_tor_exit_node:
          type: boolean
          description: True if the IP is a known Tor exit node.
          example: false
        is_tor_full_node:
          type: boolean
          description: True if the IP is a known Tor relay/full node.
          example: false

        # — Spam / abuse (SpamhausZenDataProvider) —
        is_spam:
          type: boolean
          description: True if the IP appears on the Spamhaus DROP or eDROP blocklist.
          example: false
        is_spam_drop:
          type: boolean
          description: Alias for `is_spam`.
          example: false
        spam_list:
          type: string
          description: Which Spamhaus list matched. Present only when `is_spam` is true.
          enum: [DROP, eDROP]
          example: "DROP"

        # — VPN / proxy / privacy networks (ProxyVpn + PrivacyNetwork providers) —
        is_vpn:
          type: boolean
          description: |
            True if the IP belongs to a **commercial VPN** operator. Privacy relays
            (iCloud Private Relay, Chrome IP Protection) are deliberately excluded —
            they are consumer privacy features, not VPN services. Use `is_proxy` for
            "arrived via any intermediary", or `privacy_network_type` for the specific
            classification. Pass `compat=v1` for the pre-2026-08 behaviour.
          example: false
        is_proxy:
          type: boolean
          description: |
            True if traffic arrived via an intermediary of any kind: commercial VPN, Tor,
            a privacy relay, or a detected residential proxy. Unchanged in meaning.

            Hosting/datacenter range membership does NOT set this flag — an address
            living in AWS or Google address space is not by itself a proxy. Use
            `is_hosting_range` / `usage_type` for that.
          example: false
        proxy_type:
          type: string
          description: Present only when `is_proxy` is true. Mirrors `privacy_network_type`.
          enum: [vpn, tor, privacy_relay, residential_proxy]
          example: "vpn"

        # — Granular privacy-network detection (PrivacyNetworkDataProvider) —
        privacy_network_type:
          type: string
          x-ipquery-plan: advanced
          description: |
            (Advanced and above) The single field to switch on. Documented precedence,
            first match wins: `tor` > `privacy_relay` > `vpn` > `residential_proxy` >
            `none`.

            Hosting/datacenter ranges are deliberately not a value here — see
            `is_hosting_range`.
          enum: [none, privacy_relay, vpn, tor, residential_proxy]
          example: "privacy_relay"
        privacy_network_name:
          type: string
          x-ipquery-plan: advanced
          description: (Advanced and above) Named operator, when known — e.g. `iCloud Private Relay`, `NordVPN`, `Mullvad`.
          example: "iCloud Private Relay"
        privacy_network_confidence:
          type: integer
          minimum: 0
          maximum: 100
          x-ipquery-plan: advanced
          description: |
            (Advanced and above) Confidence in the classification. The highest base
            confidence of any source that fired, plus 10 (capped at 99) when two
            independent sources agree.
          example: 99
        privacy_network_sources:
          type: array
          x-ipquery-plan: advanced
          description: (Advanced and above) Which detection sources fired, for explainability.
          items:
            type: string
          example: ["apple_relay_egress"]
        is_privacy_relay:
          type: boolean
          x-ipquery-plan: advanced
          description: |
            (Advanced and above) True for consumer privacy relays — Apple iCloud Private
            Relay, and Chrome IP Protection once Google publishes its geofeed. These
            carry ordinary consumer traffic and are capped at `risk_score` 20.
          example: false
        privacy_relay_name:
          type: string
          x-ipquery-plan: advanced
          description: (Advanced and above) Relay operator. Present only when `is_privacy_relay` is true.
          example: "iCloud Private Relay"
        privacy_relay_confidence:
          type: integer
          x-ipquery-plan: advanced
          description: (Advanced and above) Confidence for the relay classification.
          example: 99
        vpn_name:
          type: string
          x-ipquery-plan: advanced
          description: |
            (Advanced and above) VPN operator, when identifiable. Derived from reverse DNS
            (most precise), the operator's ASN, or a named provider feed.
          example: "NordVPN"
        vpn_confidence:
          type: integer
          x-ipquery-plan: advanced
          description: (Advanced and above) Confidence for the VPN classification.
          example: 98
        is_residential_proxy:
          type: boolean
          x-ipquery-plan: advanced
          description: |
            (Advanced and above) Heuristic residential-proxy detection: a residential or
            mobile address, outside cloud and hosting space, that appears on a Spamhaus
            list (XBL covers infected machines and open proxies).

            Stated plainly: there is no free, redistributable residential-proxy feed —
            that address space is the core paid asset of the vendors who sell it. This
            signal is therefore inferred, ships at a confidence deliberately **below** the
            risk-scoring threshold, and publishes its confidence so you can set your own
            bar. Treat it as a hint, not a verdict.
          example: false
        residential_proxy_confidence:
          type: integer
          x-ipquery-plan: advanced
          description: (Advanced and above) Confidence for the residential-proxy heuristic. Scored in `risk_score` only at 70 or above.
          example: 0
        is_hosting_range:
          type: boolean
          description: |
            True if the IP falls inside a published hosting/datacenter provider range.
            Free on every plan: it is a plain fact about the address, and it is what makes
            `usage_type` and `is_datacenter` accurate rather than inferred from the
            network name. Being in hosting space is NOT a proxy signal — see `is_proxy`.
          example: true
        hosting_range_confidence:
          type: integer
          x-ipquery-plan: advanced
          description: (Advanced and above) Confidence for the hosting-range classification.
          example: 70
        is_tor:
          type: boolean
          description: Convenience alias — true when `is_tor_exit_node` or `is_tor_full_node` is true.
          example: false
        tor_confidence:
          type: integer
          x-ipquery-plan: advanced
          description: (Advanced and above) Confidence for the Tor classification.
          example: 95
        is_bot:
          type: boolean
          description: Alias of `is_crawler`, using the term most callers search for.
          example: false
        bot_name:
          type: string
          description: Alias of `crawler_name`. Present only when `is_bot` is true.
          example: "Googlebot"
        bot_confidence:
          type: integer
          x-ipquery-plan: advanced
          description: (Advanced and above) Confidence for the bot/crawler classification.
          example: 90
        cloud_confidence:
          type: integer
          x-ipquery-plan: advanced
          description: (Advanced and above) Confidence for the cloud-provider classification.
          example: 90

        # — Plan gating (present only when the plan withheld something) —
        gated_fields:
          type: array
          description: |
            Names of fields withheld by the caller's plan. Present only when at least one
            field was withheld. When `fields=` is used, lists only the intersection of what
            you requested and what you did not get.
          items:
            type: string
          example: ["risk_score"]
        gated_fields_info:
          type: string
          description: Human-readable pointer to the plan comparison. Accompanies `gated_fields`.
          example: "Some fields are not included on your plan. See https://ipquery.info/pricing#fields"

        # — Cloud provider (CloudDataProvider) —
        is_cloud:
          type: boolean
          description: True if the IP is in a published cloud provider IP range.
          example: false
        cloud_provider:
          type: string
          description: Cloud provider name. Present only when `is_cloud` is true.
          enum: [AWS, GCP, Azure, Cloudflare, DigitalOcean, Oracle, Fastly]
          example: "AWS"

        # — Anycast / multicast (AnycastMulticastDataProvider) —
        is_anycast:
          type: boolean
          description: True if the IP is a known anycast address (root DNS, public resolvers, etc.).
          example: true
        is_multicast:
          type: boolean
          description: True if the IP is in the multicast range (224.0.0.0/4 or ff00::/8).
          example: false

        # — Crawler / bot (CrawlerDataProvider) —
        is_crawler:
          type: boolean
          description: True if the IP or User-Agent matches a known crawler or bot.
          example: false
        crawler_name:
          type: string
          description: |
            Name of the identified crawler. Present only when `is_crawler` is true.
            IP range matches (Googlebot, Bingbot, etc.) take priority over User-Agent matches.
          example: "Googlebot"

        # — RIPE network data (RipeDataProvider — European/MENA/Central Asia IPs only) —
        ripe_country:
          type: string
          description: Country code from the RIPE inetnum record. Present for IPs in the RIPE NCC service region.
          example: "DE"
        netname:
          type: string
          description: Network name from the RIPE database.
          example: "DTAG-DIAL7"
        ripe_org_id:
          type: string
          description: RIPE organisation ID.
          example: "ORG-DTAG1-RIPE"
        org:
          type: string
          description: Organisation name from the RIPE database.
          example: "Deutsche Telekom AG"
        network_status:
          type: string
          description: RIPE allocation/assignment status.
          example: "ALLOCATED PA"

        # — BGP / routing (RouteViewsBGPDataProvider — IPv4 only) —
        bgp_prefix:
          type: string
          x-ipquery-plan: advanced
          description: (Advanced and above) Announced BGP prefix covering the IP (CAIDA RouteViews). Omitted if not found.
          example: "8.8.8.0/24"
        bgp_asn:
          type: integer
          x-ipquery-plan: advanced
          description: (Advanced and above) Origin ASN announcing the prefix. Omitted if not found.
          example: 15169
        bgp_is_moas:
          type: boolean
          x-ipquery-plan: advanced
          description: (Advanced and above) True if the prefix is announced from more than one origin AS (MOAS — a common anycast/hijack signal).
          example: false
        is_bogon:
          type: boolean
          description: True if the IP is in an unroutable/martian range (RFC 1122 / RFC 6890). IPv4 only.
          example: false
        bogon_type:
          type: string
          description: Bogon category. Present only when `is_bogon` is true.
          example: "private"

        # — Reverse DNS (ReverseDnsDataProvider — opt-in via reverse=true) —
        hostname:
          type: string
          x-ipquery-plan: basic
          description: |
            Reverse-DNS (PTR) hostname for the IP. Present only when `reverse=true`
            was passed and a PTR record resolved within the lookup budget.
          example: "dns.google"

        # — Derived signals (DerivedSignalsDataProvider) —
        usage_type:
          type: string
          description: |
            Higher-level classification of how the address is used, derived from the
            ASN, cloud, mobile/satellite, and RIPE signals (first match wins:
            satellite → mobile → hosting → education → business → residential).
          enum: [satellite, mobile, hosting, education, business, residential]
          example: "hosting"
        is_datacenter:
          type: boolean
          description: Convenience flag — true when `usage_type` is `hosting`.
          example: true
        risk_score:
          type: integer
          minimum: 0
          maximum: 100
          x-ipquery-plan: basic
          description: |
            Packaged fraud/abuse risk score (0-100), derived transparently from the
            threat signals in this response. Additive rubric over atomic signals:

            | Signal                | Points |
            |-----------------------|--------|
            | Tor exit node         | +55    |
            | Tor relay (not exit)  | +30    |
            | Spamhaus DROP/eDROP   | +50    |
            | VPN                   | +30    |
            | Cloud / datacenter    | +25 / +20 (max, not both) |
            | Bogon (martian)       | +40    |
            | BGP MOAS              | +5     |
            | Anycast               | +10    |
            | Crawler               | +15    |
            | Residential proxy (confidence >= 70) | +45 |
            | Mobile (mitigation)   | -10    |
            | Residential (mitig.)  | -5     |
            | Privacy relay         | 0 (and suppresses the VPN and cloud/datacenter points) |

            Score is clamped to 0-100; a Tor exit or Spamhaus-DROP hit floors it to 65.

            **Privacy relays are capped at 20.** iCloud Private Relay and Chrome IP
            Protection carry ordinary consumer traffic but egress through Cloudflare and
            Akamai, so scoring them on the VPN and datacenter signals penalises the person
            behind the relay. Relay addresses therefore never exceed `risk_score` 20 —
            *unless* the address is independently known-bad (Tor exit or relay, Spamhaus
            listing, or a bogon), in which case the normal score and the known-bad floor
            both still apply. The cap can never override the floor.

            (Plan: `risk_score` and `risk_level` require Basic or above; `risk_factors`
            requires Advanced or above. The 15 day trial returns all three.)
          example: 20
        risk_level:
          type: string
          x-ipquery-plan: basic
          description: "(Basic and above) Bucketed risk band: low (0-29), medium (30-64), high (65-100)."
          enum: [low, medium, high]
          example: "low"
        risk_factors:
          type: array
          x-ipquery-plan: advanced
          description: (Advanced and above) The signal labels that contributed to `risk_score` (explainability).
          items:
            type: string
          example: ["cloud_aws", "anycast"]

        # — Nearest city (SimpleMapsDataProvider) —
        simplemaps:
          $ref: "#/components/schemas/SimpleMapsResult"

    SimpleMapsResult:
      type: object
      description: Nearest populated place from the SimpleMaps world cities dataset.
      properties:
        city:
          type: string
          example: "Mountain View"
        admin_name:
          type: string
          description: Administrative region name.
          example: "California"
        country:
          type: string
          example: "United States"
        iso2:
          type: string
          description: ISO 3166-1 alpha-2 country code.
          example: "US"
        iso3:
          type: string
          description: ISO 3166-1 alpha-3 country code.
          example: "USA"
        capital:
          type: string
          description: Capital type — "primary", "admin", "minor", or empty string.
          enum: [primary, admin, minor, ""]
          example: ""
        population:
          type: integer
          description: City population. Omitted when zero or unknown.
          example: 82376
