{
  "openapi": "3.1.0",
  "info": {
    "title": "SourceIP Lookup API",
    "version": "1.0.0",
    "summary": "Attribute an IP address to the operator that runs it.",
    "description": "SourceIP maps IPv4 and IPv6 addresses to the operators that publish them — CDNs, cloud platforms, SaaS APIs, public DNS, monitoring and security scanners. Every range is sourced from a list the operating organisation publishes itself.\n\nSourceIP is not a safe list, allow-list, or threat feed. A match identifies who operates an address; it is not a statement that traffic is benign.",
    "contact": { "name": "SourceIP", "email": "hello@sourceip.io", "url": "https://sourceip.io/contact" },
    "license": { "name": "Proprietary", "url": "https://sourceip.io/legal/terms" }
  },
  "servers": [{ "url": "https://sourceip.io", "description": "Production" }],
  "security": [{ "ApiKeyAuth": [] }],
  "paths": {
    "/v1/lookup": {
      "get": {
        "operationId": "lookupIp",
        "summary": "Look up a single IP address",
        "description": "Returns every provider whose published ranges contain the address. A successful lookup with no match returns 200 with an empty providers array, not 404.",
        "parameters": [
          {
            "name": "ip",
            "in": "query",
            "required": true,
            "description": "IPv4 or IPv6 address in standard notation. CIDR ranges are not accepted.",
            "schema": { "type": "string" },
            "examples": {
              "ipv4": { "value": "8.8.8.8" },
              "ipv6": { "value": "2001:4860:4860::8888" }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lookup succeeded. May contain zero providers.",
            "headers": {
              "X-RateLimit-Remaining": {
                "description": "Lookups left in the current UTC day (anonymous tier only).",
                "schema": { "type": "integer" }
              }
            },
            "content": {
              "application/json": { "schema": { "$ref": "#/components/schemas/LookupResponse" } }
            }
          },
          "400": {
            "description": "Missing or unparseable ip parameter.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "403": {
            "description": "Missing or invalid API key.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the quota resets (midnight UTC).",
                "schema": { "type": "integer" }
              }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "500": {
            "description": "Server error. Retry with backoff.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "x-api-key" }
    },
    "schemas": {
      "LookupResponse": {
        "type": "object",
        "required": ["ip", "last_updated", "providers"],
        "properties": {
          "ip": { "type": "string", "description": "The address queried, echoed back." },
          "last_updated": {
            "type": "string",
            "format": "date-time",
            "description": "ISO8601 build time of the snapshot that answered this query. A dataset-level value: the pipeline rebuilds all providers in one pass."
          },
          "providers": {
            "type": "array",
            "description": "Providers whose published ranges contain the address, sorted by precedence. Empty when there is no match.",
            "items": { "$ref": "#/components/schemas/Provider" }
          }
        }
      },
      "Provider": {
        "type": "object",
        "required": ["id", "name", "category", "trust_level", "ip_cidr"],
        "properties": {
          "id": { "type": "string", "description": "Stable UUID for the provider." },
          "name": { "type": "string" },
          "description": { "type": "string", "description": "What the organisation is and what these ranges are for." },
          "category": {
            "type": "string",
            "description": "Service category.",
            "examples": ["cloud", "cdn", "public_dns", "security", "monitoring", "saas"]
          },
          "explanation": { "type": "string", "description": "Why this traffic would appear on your network." },
          "logo": { "type": "string", "description": "Logo URL. May be empty." },
          "precedence": { "type": "integer", "description": "Ordering hint for overlapping matches; higher sorts first. Not a confidence score." },
          "trust_level": {
            "type": "integer",
            "enum": [-1, 0, 1, 2],
            "description": "How much weight the attribution carries. 1 = trusted, the provider directly operates the service. 2 = verified shared infrastructure, supplied to third parties, so activity is not attributable to the provider. 0 = informational context only. -1 = untrusted; a match must not imply benign behaviour."
          },
          "ip_cidr": { "type": "string", "description": "The specific CIDR that matched." }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": { "error": { "type": "string" } }
      }
    }
  }
}
