{
  "openapi": "3.1.0",
  "info": {
    "title": "whatsmy.fyi API",
    "version": "1.0.0",
    "description": "Free IP geolocation API powered by Cloudflare Workers. No third-party databases. No IP query logging. Sub-50ms latency globally.",
    "contact": {
      "email": "enterprise@whatsmy.fyi",
      "url": "https://whatsmy.fyi/enterprise"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    }
  },
  "servers": [
    {
      "url": "https://whatsmy.fyi",
      "description": "Production"
    }
  ],
  "security": [
    { "BearerAuth": [] }
  ],
  "paths": {
    "/api/v1/ip": {
      "get": {
        "operationId": "getIp",
        "summary": "Get IP metadata",
        "description": "Returns IP address, geolocation, and connection metadata for the requesting IP. The response shape depends on the API key's scope (ip, geo, or full).",
        "tags": ["IP"],
        "security": [
          { "BearerAuth": [] },
          { "ApiKeyQuery": [] }
        ],
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": false,
            "description": "API key as query parameter (fallback to Authorization header)",
            "schema": { "type": "string", "example": "wmf_your_api_key" }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful IP lookup",
            "headers": {
              "X-RateLimit-Limit": {
                "schema": { "type": "integer" },
                "description": "Daily request limit (10000 for free tier)"
              },
              "X-RateLimit-Remaining": {
                "schema": { "type": "integer" },
                "description": "Requests remaining today"
              },
              "X-RateLimit-Reset": {
                "schema": { "type": "integer" },
                "description": "Unix timestamp of next reset (midnight UTC)"
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/IpResponse" },
                "examples": {
                  "geo": {
                    "summary": "Geo scope response",
                    "value": {
                      "status": "success",
                      "ip": "203.0.113.42",
                      "ipv6": null,
                      "city": "Amsterdam",
                      "region": "North Holland",
                      "region_code": "NH",
                      "country": "NL",
                      "country_name": "Netherlands",
                      "continent": "EU",
                      "latitude": 52.374,
                      "longitude": 4.8897,
                      "timezone": "Europe/Amsterdam",
                      "timezone_offset": 7200,
                      "postal_code": "1011",
                      "asn": 1101,
                      "org": "SURF B.V.",
                      "as": "AS1101 SURF B.V.",
                      "is_eu": true,
                      "currency": "EUR"
                    }
                  },
                  "full": {
                    "summary": "Full scope response",
                    "value": {
                      "status": "success",
                      "ip": "203.0.113.42",
                      "ipv6": null,
                      "city": "Amsterdam",
                      "region": "North Holland",
                      "region_code": "NH",
                      "country": "NL",
                      "country_name": "Netherlands",
                      "continent": "EU",
                      "latitude": 52.374,
                      "longitude": 4.8897,
                      "timezone": "Europe/Amsterdam",
                      "timezone_offset": 7200,
                      "postal_code": "1011",
                      "asn": 1101,
                      "org": "SURF B.V.",
                      "as": "AS1101 SURF B.V.",
                      "is_eu": true,
                      "currency": "EUR",
                      "http_protocol": "HTTP/3",
                      "tls_version": "TLSv1.3",
                      "tls_cipher": "AEAD-AES128-GCM-SHA256",
                      "rtt": 12,
                      "colo": "AMS",
                      "proxy": false,
                      "hosting": false
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "examples": {
                  "missing": {
                    "value": {
                      "error": "missing_api_key",
                      "message": "No API key provided. Use Authorization: Bearer <key> or ?key=<key>.",
                      "docs": "https://whatsmy.fyi/docs#authentication"
                    }
                  },
                  "invalid": {
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key provided does not exist.",
                      "docs": "https://whatsmy.fyi/docs#authentication"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Inactive API key",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": {
                  "error": "inactive_api_key",
                  "message": "This API key has been deactivated.",
                  "docs": "https://whatsmy.fyi/docs#authentication"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "headers": {
              "Retry-After": {
                "schema": { "type": "integer" },
                "description": "Seconds until the daily limit resets"
              },
              "X-RateLimit-Limit": { "schema": { "type": "integer" } },
              "X-RateLimit-Remaining": { "schema": { "type": "integer" } },
              "X-RateLimit-Reset": { "schema": { "type": "integer" } }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": {
                  "error": "rate_limit_exceeded",
                  "message": "You have reached the free tier limit of 10,000 requests/day. Resets at midnight UTC.",
                  "docs": "https://whatsmy.fyi/docs#rate-limits",
                  "enterprise": "https://whatsmy.fyi/enterprise"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": {
                  "error": "internal_error",
                  "message": "An unexpected error occurred. Please try again.",
                  "docs": "https://whatsmy.fyi/docs#errors"
                }
              }
            }
          }
        }
      },
      "options": {
        "operationId": "corsPreflightIp",
        "summary": "CORS preflight",
        "tags": ["IP"],
        "responses": {
          "204": { "description": "CORS preflight response" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Pass your API key as a Bearer token in the Authorization header."
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "key",
        "description": "Pass your API key as a ?key= query parameter (fallback)."
      }
    },
    "schemas": {
      "IpResponse": {
        "type": "object",
        "required": ["status"],
        "properties": {
          "status":          { "type": "string", "enum": ["success"] },
          "ip":              { "type": ["string", "null"], "description": "IPv4 address (null if IPv6-only)" },
          "ipv6":            { "type": ["string", "null"], "description": "IPv6 address (null if IPv4-only)" },
          "city":            { "type": ["string", "null"], "description": "City name (geo+)" },
          "region":          { "type": ["string", "null"], "description": "Region / state name (geo+)" },
          "region_code":     { "type": ["string", "null"], "description": "ISO 3166-2 code (geo+)" },
          "country":         { "type": ["string", "null"], "description": "ISO 3166-1 alpha-2 country code (geo+)" },
          "country_name":    { "type": ["string", "null"], "description": "Full country name in English (geo+)" },
          "continent":       { "type": ["string", "null"], "description": "Two-letter continent code (geo+)" },
          "latitude":        { "type": ["number", "null"], "description": "Latitude (geo+)" },
          "longitude":       { "type": ["number", "null"], "description": "Longitude (geo+)" },
          "timezone":        { "type": ["string", "null"], "description": "IANA timezone identifier (geo+)" },
          "timezone_offset": { "type": ["integer", "null"], "description": "UTC offset in seconds (geo+)" },
          "postal_code":     { "type": ["string", "null"], "description": "Postal code (geo+)" },
          "asn":             { "type": ["integer", "null"], "description": "Autonomous System Number (geo+)" },
          "org":             { "type": ["string", "null"], "description": "AS organization name / ISP (geo+)" },
          "as":              { "type": ["string", "null"], "description": "Formatted AS string, e.g. 'AS1101 SURF B.V.' (geo+)" },
          "is_eu":           { "type": ["boolean", "null"], "description": "Whether the IP is in the EU (geo+)" },
          "currency":        { "type": ["string", "null"], "description": "ISO 4217 currency code for the country (geo+)" },
          "http_protocol":   { "type": ["string", "null"], "description": "HTTP protocol version (full)" },
          "tls_version":     { "type": ["string", "null"], "description": "TLS version (full)" },
          "tls_cipher":      { "type": ["string", "null"], "description": "TLS cipher suite (full)" },
          "rtt":             { "type": ["integer", "null"], "description": "Round-trip time to Cloudflare edge in ms (full)" },
          "colo":            { "type": ["string", "null"], "description": "Cloudflare datacenter IATA code (full)" },
          "proxy":           { "type": ["boolean", "null"], "description": "Known VPN/proxy ASN (full)" },
          "hosting":         { "type": ["boolean", "null"], "description": "Known hosting/cloud ASN (full)" }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error", "message", "docs"],
        "properties": {
          "error":      { "type": "string", "description": "Snake_case error code" },
          "message":    { "type": "string", "description": "Human-readable explanation" },
          "docs":       { "type": "string", "description": "Link to relevant docs section" },
          "enterprise": { "type": "string", "description": "Enterprise page URL (present on 429 only)" }
        }
      }
    }
  }
}
