{
  "openapi": "3.1.0",
  "info": {
    "title": "ipatlas IP Geolocation API",
    "version": "1.0.0",
    "summary": "Fast IPv4/IPv6 geolocation API and hosted Model Context Protocol (MCP) server.",
    "description": "Turn any public IPv4 or IPv6 address into continent, country, EU status, region, approximate city, coordinates, IANA time zone, and ASN. Free for 50,000 lookups/month (no credit card); keyless tier allows 1,000 lookups/day per IP. Looked-up IP addresses are never logged. Powered by DB-IP Lite (CC BY 4.0).",
    "contact": {
      "name": "ipatlas",
      "url": "https://ipatlas.dev",
      "email": "hello@ipatlas.dev"
    },
    "license": {
      "name": "DB-IP CC BY 4.0 (geolocation data)",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    }
  },
  "servers": [
    {
      "url": "https://api.ipatlas.dev",
      "description": "Production API"
    }
  ],
  "paths": {
    "/v1/me": {
      "get": {
        "operationId": "lookupMe",
        "summary": "Look up the caller's public IP address",
        "description": "Returns geolocation data for the caller's public IPv4 or IPv6 address as seen by the API.",
        "security": [
          {},
          { "BearerAuth": [] },
          { "ApiKeyHeader": [] },
          { "ApiKeyQuery": [] }
        ],
        "responses": {
          "200": {
            "description": "Geolocation result for the caller's IP address.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" },
              "X-RateLimit-Window": { "$ref": "#/components/headers/X-RateLimit-Window" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LookupResult" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/UnauthorizedError" },
          "404": { "$ref": "#/components/responses/NotFoundError" },
          "429": { "$ref": "#/components/responses/RateLimitError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailableError" }
        }
      }
    },
    "/v1/{ip}": {
      "get": {
        "operationId": "lookupIP",
        "summary": "Look up any IPv4 or IPv6 address",
        "description": "Returns geolocation data for any IPv4 or IPv6 address. Private and reserved addresses return 200 with {\"ip\": \"...\", \"reserved\": true}.",
        "security": [
          {},
          { "BearerAuth": [] },
          { "ApiKeyHeader": [] },
          { "ApiKeyQuery": [] }
        ],
        "parameters": [
          {
            "name": "ip",
            "in": "path",
            "required": true,
            "description": "IPv4 or IPv6 address to look up (for example, 8.8.8.8 or 2606:4700:4700::1111).",
            "schema": {
              "type": "string",
              "examples": ["8.8.8.8", "2606:4700:4700::1111"]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Geolocation result for the requested IP address.",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" },
              "X-RateLimit-Window": { "$ref": "#/components/headers/X-RateLimit-Window" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LookupResult" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequestError" },
          "401": { "$ref": "#/components/responses/UnauthorizedError" },
          "404": { "$ref": "#/components/responses/NotFoundError" },
          "429": { "$ref": "#/components/responses/RateLimitError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailableError" }
        }
      }
    },
    "/mcp": {
      "post": {
        "operationId": "mcpStreamableHTTP",
        "summary": "Model Context Protocol (MCP) Streamable HTTP endpoint",
        "description": "JSON-RPC 2.0 endpoint supporting initialize, ping, tools/list, and tools/call for lookup_ip(ip), my_location(), and lookup_batch(ips<=100). JSON-RPC notifications return 202 Accepted with an empty body.",
        "security": [
          {},
          { "BearerAuth": [] },
          { "ApiKeyHeader": [] },
          { "ApiKeyQuery": [] }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/MCPRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC 2.0 response.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MCPResponse" }
              }
            }
          },
          "202": {
            "description": "JSON-RPC notification accepted (empty body)."
          },
          "400": {
            "description": "JSON-RPC parse or invalid request error.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/MCPResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/UnauthorizedError" },
          "403": {
            "description": "Forbidden Origin header.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimitError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailableError" }
        }
      }
    },
    "/v1/signup": {
      "post": {
        "operationId": "createFreeKey",
        "summary": "Create a free API key",
        "description": "Creates a free API key (50,000 lookups/month) for the supplied email address. The full key is returned once in the response.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "example": "you@example.com"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Newly created free API key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["key", "monthly_quota"],
                  "properties": {
                    "key": {
                      "type": "string",
                      "description": "API key (prefix ipa_), shown once."
                    },
                    "monthly_quota": {
                      "type": "integer",
                      "example": 50000
                    },
                    "note": {
                      "type": "string",
                      "description": "Short human-readable note about the key."
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequestError" },
          "429": { "$ref": "#/components/responses/RateLimitError" }
        }
      }
    },
    "/v1/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "Get plan, quotas, and usage for your API key",
        "security": [
          { "BearerAuth": [] },
          { "ApiKeyHeader": [] },
          { "ApiKeyQuery": [] }
        ],
        "responses": {
          "200": {
            "description": "Account plan and current usage counters.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AccountResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/UnauthorizedError" }
        }
      }
    },
    "/v1/checkout": {
      "post": {
        "operationId": "createCheckoutSession",
        "summary": "Create a Stripe Checkout session to upgrade your API key",
        "security": [
          { "BearerAuth": [] },
          { "ApiKeyHeader": [] },
          { "ApiKeyQuery": [] }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["plan"],
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": ["dev", "pro", "scale"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stripe Checkout URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["url"],
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequestError" },
          "401": { "$ref": "#/components/responses/UnauthorizedError" },
          "409": {
            "description": "Subscription already active for this key.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimitError" },
          "502": { "$ref": "#/components/responses/ServiceUnavailableError" },
          "503": { "$ref": "#/components/responses/ServiceUnavailableError" }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Service status and GeoIP database build date",
        "responses": {
          "200": {
            "description": "Service is healthy.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": ["status", "version", "data_build"],
                  "properties": {
                    "status": { "type": "string", "example": "ok" },
                    "version": { "type": "string" },
                    "data_build": { "type": "string", "format": "date", "example": "2026-10-01" }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key passed as Authorization: Bearer ipa_..."
      },
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key passed as X-API-Key: ipa_..."
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "key",
        "description": "API key passed as ?key=ipa_... (prefer headers so keys do not appear in URL logs)"
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Quota or rate limit for the reported window.",
        "schema": { "type": "integer" }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests remaining in the reported window (0 on HTTP 429).",
        "schema": { "type": "integer" }
      },
      "X-RateLimit-Reset": {
        "description": "Seconds until the reported rate-limit window resets.",
        "schema": { "type": "integer" }
      },
      "X-RateLimit-Window": {
        "description": "Active rate-limit window reported by the response.",
        "schema": {
          "type": "string",
          "enum": ["second", "day", "month", "concurrency", "hour"]
        }
      }
    },
    "schemas": {
      "LookupResult": {
        "type": "object",
        "required": ["ip"],
        "properties": {
          "ip": {
            "type": "string",
            "description": "Canonical IPv4 or IPv6 address.",
            "example": "8.8.8.8"
          },
          "continent": {
            "type": "string",
            "description": "Two-letter continent code.",
            "example": "NA"
          },
          "country_code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country code.",
            "example": "US"
          },
          "country": {
            "type": "string",
            "description": "English country name.",
            "example": "United States"
          },
          "is_eu": {
            "type": "boolean",
            "description": "True if the country is a member of the European Union.",
            "example": false
          },
          "region": {
            "type": "string",
            "description": "First-level subdivision (state, province, or region).",
            "example": "California"
          },
          "city": {
            "type": "string",
            "description": "Approximate city name. City is approximate, especially on mobile networks.",
            "example": "Mountain View"
          },
          "latitude": {
            "type": "number",
            "description": "Approximate latitude in decimal degrees.",
            "example": 37.422
          },
          "longitude": {
            "type": "number",
            "description": "Approximate longitude in decimal degrees.",
            "example": -122.085
          },
          "timezone": {
            "type": "string",
            "description": "IANA time zone derived from coordinates.",
            "example": "America/Los_Angeles"
          },
          "asn": {
            "$ref": "#/components/schemas/ASN"
          },
          "reserved": {
            "type": "boolean",
            "description": "True when the IP address is in a private or reserved range."
          },
          "attribution": {
            "type": "string",
            "description": "Data source attribution string.",
            "example": "IP geolocation data by DB-IP.com (CC BY 4.0, https://db-ip.com). Served by ipatlas.dev"
          }
        }
      },
      "ASN": {
        "type": "object",
        "required": ["number", "organization"],
        "properties": {
          "number": {
            "type": "integer",
            "description": "Autonomous System Number.",
            "example": 15169
          },
          "organization": {
            "type": "string",
            "description": "Network operator or ISP name.",
            "example": "Google LLC"
          }
        }
      },
      "AccountResponse": {
        "type": "object",
        "required": ["plan", "status", "rps", "daily_quota", "used_today", "monthly_quota", "used_this_month", "max_inflight"],
        "properties": {
          "plan": {
            "type": "string",
            "enum": ["free", "dev", "pro", "scale"]
          },
          "status": {
            "type": "string",
            "description": "Stripe subscription status (for example active, trialing, past_due, canceled, refunded) or empty string for free keys."
          },
          "rps": { "type": "integer", "example": 50 },
          "daily_quota": { "type": "integer", "example": 5000 },
          "used_today": { "type": "integer", "example": 12 },
          "monthly_quota": { "type": "integer", "example": 50000 },
          "used_this_month": { "type": "integer", "example": 340 },
          "max_inflight": { "type": "integer", "example": 8 }
        }
      },
      "MCPRequest": {
        "type": "object",
        "required": ["jsonrpc", "method"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "type": ["string", "integer", "null"] },
          "method": {
            "type": "string",
            "examples": ["initialize", "ping", "tools/list", "tools/call"]
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "MCPResponse": {
        "type": "object",
        "required": ["jsonrpc", "id"],
        "properties": {
          "jsonrpc": { "type": "string", "const": "2.0" },
          "id": { "type": ["string", "integer", "null"] },
          "result": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "integer" },
              "message": { "type": "string" }
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string" }
        }
      },
      "RateLimitErrorResponse": {
        "type": "object",
        "required": ["error", "window", "limit", "retry_after"],
        "properties": {
          "error": { "type": "string" },
          "window": {
            "type": "string",
            "enum": ["second", "day", "month", "concurrency", "hour"]
          },
          "limit": { "type": "integer" },
          "retry_after": { "type": "integer" },
          "upgrade_url": {
            "type": "string",
            "format": "uri",
            "example": "https://ipatlas.dev/pricing"
          }
        }
      }
    },
    "responses": {
      "BadRequestError": {
        "description": "Invalid request parameters or body.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "UnauthorizedError": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "NotFoundError": {
        "description": "No geolocation data found for this IP address.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "RateLimitError": {
        "description": "Rate limit or quota reached.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": { "type": "integer" }
          },
          "X-RateLimit-Limit": { "$ref": "#/components/headers/X-RateLimit-Limit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/X-RateLimit-Remaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/X-RateLimit-Reset" },
          "X-RateLimit-Window": { "$ref": "#/components/headers/X-RateLimit-Window" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/RateLimitErrorResponse" }
          }
        }
      },
      "ServiceUnavailableError": {
        "description": "Server busy or upstream payment provider unavailable.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": { "type": "integer" }
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      }
    }
  }
}
