openapi: 3.1.0
info:
  title: OpenIPApi
  description: |
    IP geolocation, network attribution and threat-intelligence API.

    OpenIPApi combines geolocation data, ASN attribution, Tor exit lists,
    proxy detection and active probing from 60+ probing nodes into a single
    REST API.

    Authentication is by API key passed in the `X-API-Key` header. Get a free
    key (10,000 lookups/month) at https://console.openipapi.com/register.

    **Data is probabilistic** — treat fields like `threat_score`, `is_vpn`,
    `is_proxy`, `is_tor` as risk signals to combine with other factors
    (account, payment, device, behaviour). See
    https://www.openipapi.com/methodology for limitations.
  version: 1.0.0
  contact:
    name: OpenIPApi Support
    email: support@openipapi.com
    url: https://www.openipapi.com
  license:
    name: Commercial — see Terms of Service
    url: https://www.openipapi.com/terms

servers:
  - url: https://api.openipapi.com
    description: Production

security:
  - ApiKeyAuth: []

tags:
  - { name: Lookup,    description: IP address lookup endpoints }
  - { name: Network,   description: ASN and network metadata }
  - { name: Validate,  description: IP validation and classification }
  - { name: Probe,     description: Active probing data }
  - { name: Account,   description: Account and usage metadata }
  - { name: Database,  description: MMDB database downloads }

paths:
  /v1/lookup/{ip}:
    get:
      tags: [Lookup]
      summary: Single IP lookup
      description: Returns full geo + network + threat data for an IPv4 or IPv6 address.
      parameters:
        - $ref: '#/components/parameters/IpPath'
        - in: query
          name: fields
          description: Comma-separated list of top-level fields to include (`geo`, `network`, `threat`, `abuse`).
          schema: { type: string }
          example: geo,network
        - in: query
          name: date
          description: |
            Historical snapshot date in `YYYY-MM-DD`. Up to 365 days back.
            Requires Pro plan or above.
          schema: { type: string, format: date }
      responses:
        '200': { $ref: '#/components/responses/LookupOk' }
        '400': { $ref: '#/components/responses/InvalidIp' }
        '401': { $ref: '#/components/responses/InvalidApiKey' }
        '402': { $ref: '#/components/responses/LimitExceeded' }
        '403': { $ref: '#/components/responses/PlanRequired' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /v1/lookup/batch:
    post:
      tags: [Lookup]
      summary: Batch IP lookup
      description: |
        Look up multiple IPs in one request. Batch size depends on the plan:
        Starter 100, Pro 500, Business 1 000, Enterprise 5 000. Free plan
        cannot use this endpoint.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ips]
              properties:
                ips:
                  type: array
                  items: { type: string }
                  example: ["8.8.8.8", "1.1.1.1", "185.220.101.45"]
      responses:
        '200':
          description: Array of lookup objects in the same order as the request.
          content:
            application/json:
              schema:
                type: object
                properties:
                  results:
                    type: array
                    items: { $ref: '#/components/schemas/LookupResult' }
        '400': { description: Invalid request body }
        '401': { $ref: '#/components/responses/InvalidApiKey' }
        '403': { $ref: '#/components/responses/PlanRequired' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /v1/me:
    get:
      tags: [Lookup]
      summary: Caller IP lookup
      description: Returns geo + threat data for the IP making the request.
      responses:
        '200': { $ref: '#/components/responses/LookupOk' }
        '401': { $ref: '#/components/responses/InvalidApiKey' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /v1/asn/{asn}:
    get:
      tags: [Network]
      summary: ASN detail
      description: Retrieve detailed info about an Autonomous System. Requires Starter plan or above.
      parameters:
        - in: path
          name: asn
          required: true
          schema: { type: string }
          example: "15169"
          description: ASN number (with or without `AS` prefix).
      responses:
        '200':
          description: ASN object
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AsnResult' }
        '403': { $ref: '#/components/responses/PlanRequired' }
        '404': { description: ASN not found }

  /v1/validate/{ip}:
    get:
      tags: [Validate]
      summary: Validate and classify an IP
      description: |
        Validates the syntax of an IP and classifies it. Does **not** consume
        lookup quota. Rate-limited at 60 req/min per caller IP.
      parameters:
        - $ref: '#/components/parameters/IpPath'
      responses:
        '200':
          description: Validation result
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ValidateResult' }
        '400': { $ref: '#/components/responses/InvalidIp' }

  /v1/fraud/{ip}:
    get:
      tags: [Lookup]
      summary: Fraud score
      description: |
        Composite fraud score 0–100 with risk level and recommendation. Combines
        VPN/proxy/Tor/datacentre flags, probing results, and reputation feeds.
        Requires Starter plan or above.

        **Signal, not verdict.** Combine with account, payment, device and
        behaviour data before taking automatic action.
      parameters:
        - $ref: '#/components/parameters/IpPath'
      responses:
        '200':
          description: Fraud score object
          content:
            application/json:
              schema: { $ref: '#/components/schemas/FraudResult' }
        '403': { $ref: '#/components/responses/PlanRequired' }

  /v1/probe/{ip}:
    get:
      tags: [Probe]
      summary: Active probe data
      description: |
        Real-time network probe data: open TCP ports, service banners, reverse
        DNS, full TLS certificate details, detected service categories.
        Requires Pro plan or above. Probe quota is 10% of the monthly lookup
        quota.
      parameters:
        - $ref: '#/components/parameters/IpPath'
      responses:
        '200':
          description: Probe result (with cache age and rescan status)
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProbeResult' }
        '403': { $ref: '#/components/responses/PlanRequired' }

  /v1/proxy-attribution/{ip}:
    get:
      tags: [Network]
      summary: Proxy / VPN provider attribution
      description: |
        Attributes an IP to a specific residential proxy pool, commercial VPN,
        or datacentre range (e.g. `brightdata`, `oxylabs`, `nordvpn`).
        Pro+ plan with Proxy Intelligence add-on.
      parameters:
        - $ref: '#/components/parameters/IpPath'
      responses:
        '200':
          description: Proxy attribution result
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProxyAttributionResult' }
        '403': { $ref: '#/components/responses/PlanRequired' }

  /v1/account:
    get:
      tags: [Account]
      summary: Account information
      description: Returns the authenticated account's plan, limits, and metadata.
      responses:
        '200':
          description: Account info
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Account' }

  /v1/account/usage:
    get:
      tags: [Account]
      summary: Usage statistics
      description: Returns the current billing period's usage counters.
      responses:
        '200':
          description: Usage stats
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Usage' }

  /v1/database/list:
    get:
      tags: [Database]
      summary: List available MMDB sources
      description: |
        Lists the MMDB databases available for the authenticated plan
        (`country`, `asn`, `city-geolite2`, `city-dbip`, `openipapi-full`, etc.).
        Requires Pro plan or above.
      responses:
        '200':
          description: Source list
          content:
            application/json:
              schema:
                type: object
                properties:
                  plan: { type: string }
                  sources:
                    type: array
                    items: { $ref: '#/components/schemas/MmdbSource' }

  /v1/database/download/{source}:
    get:
      tags: [Database]
      summary: Download an MMDB database
      description: |
        Streams the raw `.mmdb` binary. Supports `If-None-Match` (ETag) for
        conditional GET (304 Not Modified when unchanged). Rate limit:
        10 downloads per source per day.
      parameters:
        - in: path
          name: source
          required: true
          schema:
            type: string
            enum: [country, asn, city-geolite2, city-geolite2-ipv6, city-dbip, city-dbip-ipv6, openipapi-full]
      responses:
        '200':
          description: MMDB binary
          content:
            application/octet-stream:
              schema: { type: string, format: binary }
        '304': { description: Not Modified (ETag match) }
        '403': { $ref: '#/components/responses/PlanRequired' }
        '429': { $ref: '#/components/responses/RateLimited' }

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API key with prefix `oip_`. Find it at
        https://console.openipapi.com/api-keys. Never expose it in
        client-side JavaScript or public repositories.

  parameters:
    IpPath:
      in: path
      name: ip
      required: true
      description: IPv4 or IPv6 address.
      schema: { type: string }
      examples:
        ipv4: { value: "8.8.8.8" }
        ipv6: { value: "2001:4860:4860::8888" }

  responses:
    LookupOk:
      description: Lookup result
      content:
        application/json:
          schema: { $ref: '#/components/schemas/LookupResult' }
    InvalidIp:
      description: The IP address is malformed or not a public IP.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    InvalidApiKey:
      description: The API key is missing, malformed or revoked.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    LimitExceeded:
      description: The monthly lookup quota for the current plan is exhausted.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    PlanRequired:
      description: The feature requires a higher plan tier.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    RateLimited:
      description: Too many requests in a short window. Includes `Retry-After` header.
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }

  schemas:
    Error:
      type: object
      properties:
        error:   { type: string, description: Machine-readable error code }
        message: { type: string, description: Human-readable explanation }
        status:  { type: integer, description: HTTP status code }
      example:
        error: "invalid_api_key"
        message: "The provided API key is invalid or has been revoked."
        status: 401

    LookupResult:
      type: object
      properties:
        ip:      { type: string }
        type:    { type: string, enum: [IPv4, IPv6] }
        geo:     { $ref: '#/components/schemas/Geo' }
        network: { $ref: '#/components/schemas/Network' }
        threat:  { $ref: '#/components/schemas/Threat' }
        abuse:   { $ref: '#/components/schemas/Abuse' }
      example:
        ip: "185.220.101.45"
        type: IPv4
        geo:
          country_code: DE
          country: Germany
          region: Hessen
          region_code: HE
          city: Frankfurt am Main
          postal_code: "60313"
          latitude: 50.1109
          longitude: 8.6821
          timezone: Europe/Berlin
          is_sanctioned: false
          sanction_lists: []
        network:
          asn: 205100
          as_name: F3 Netze e.V.
          as_domain: f3netze.de
          isp: F3 Netze
          organization: F3 Netze e.V.
          connection_type: datacenter
        threat:
          is_vpn: true
          is_proxy: false
          is_tor: true
          is_relay: false
          is_hosting: true
          threat_score: 85
          threat_categories: [tor_exit_node, known_abuser]
          bot_type: null
        abuse:
          contact_email: abuse@f3netze.de

    Geo:
      type: object
      properties:
        country_code:   { type: string, description: ISO-3166 alpha-2, e.g. "DE" }
        country:        { type: string }
        region:         { type: string }
        region_code:    { type: string }
        city:           { type: string }
        postal_code:    { type: string }
        latitude:       { type: number, format: float }
        longitude:      { type: number, format: float }
        timezone:       { type: string }
        is_sanctioned:  { type: boolean }
        sanction_lists: { type: array, items: { type: string, enum: [OFAC, EU, UN, UK] } }

    Network:
      type: object
      properties:
        asn:             { type: integer }
        as_name:         { type: string }
        as_domain:       { type: string }
        isp:             { type: string }
        organization:    { type: string }
        connection_type: { type: string, enum: [residential, datacenter, mobile, education, government] }

    Threat:
      type: object
      properties:
        is_vpn:            { type: boolean }
        is_proxy:          { type: boolean }
        is_tor:            { type: boolean }
        is_relay:          { type: boolean }
        is_hosting:        { type: boolean }
        threat_score:      { type: integer, minimum: 0, maximum: 100, description: "Risk signal 0–100. Not a final verdict." }
        threat_categories: { type: array, items: { type: string } }
        bot_type:          { type: string, nullable: true, description: "Verified bot (e.g. googlebot) or null." }

    Abuse:
      type: object
      properties:
        contact_email: { type: string, format: email, nullable: true }

    AsnResult:
      type: object
      properties:
        asn:           { type: integer }
        name:          { type: string }
        domain:        { type: string }
        country_code:  { type: string }
        prefix_count:  { type: integer }
        prefixes:      { type: array, items: { type: string }, description: "List of CIDR prefixes (up to 10)" }

    ValidateResult:
      type: object
      properties:
        ip:        { type: string }
        valid:     { type: boolean }
        type:      { type: string, enum: [IPv4, IPv6] }
        is_public: { type: boolean }
        is_bogon:  { type: boolean }

    FraudResult:
      type: object
      properties:
        ip:              { type: string }
        fraud_score:     { type: integer, minimum: 0, maximum: 100 }
        risk_level:      { type: string, enum: [low, medium, high, critical] }
        recommendation:  { type: string, enum: [allow, review, challenge, block] }
        signals:         { type: array, items: { type: string } }

    ProbeResult:
      type: object
      properties:
        ip:               { type: string }
        probed_at:        { type: string, format: date-time }
        cache_age_days:   { type: integer }
        rescan_queued:    { type: boolean }
        open_ports:       { type: array, items: { type: integer } }
        reverse_dns:      { type: string, nullable: true }
        http_server:      { type: string, nullable: true }
        tls_certificate:
          type: object
          properties:
            subject:    { type: string }
            issuer:     { type: string }
            sans:       { type: array, items: { type: string } }
            not_before: { type: string, format: date }
            not_after:  { type: string, format: date }
        detected_categories: { type: array, items: { type: string } }

    ProxyAttributionResult:
      type: object
      properties:
        ip:               { type: string }
        provider:         { type: string, nullable: true, description: "e.g. brightdata, oxylabs, nordvpn" }
        network_type:     { type: string, enum: [residential_proxy, vpn, datacenter, tor, mobile_proxy, unknown] }
        recommendation:   { type: string, enum: [no_proxy_detected, treat_as_commercial_proxy, treat_as_vpn, treat_as_datacenter, block_or_challenge] }

    Account:
      type: object
      properties:
        plan:           { type: string, enum: [Free, Starter, Pro, Business, Enterprise] }
        email:          { type: string, format: email }
        monthly_limit:  { type: integer, nullable: true, description: "null = unlimited (Enterprise)" }
        batch_size:     { type: integer }

    Usage:
      type: object
      properties:
        period_start:   { type: string, format: date }
        period_end:     { type: string, format: date }
        lookups_used:   { type: integer }
        lookups_limit:  { type: integer, nullable: true }
        percent_used:   { type: number, format: float }

    MmdbSource:
      type: object
      properties:
        source:       { type: string }
        filename:     { type: string }
        size_bytes:   { type: integer }
        updated_at:   { type: string, format: date-time }
        etag:         { type: string }
        download_url: { type: string, format: uri }
