openapi: 3.0.3
info:
  title: ipxt.com IP Lookup API
  description: |
    REST API for IP geolocation lookup on ipxt.com.

    **Authentication:** All endpoints except `/health` require a Bearer API key.
    Requests must originate from your registered allowed IP address.

    **Security:** Never expose your API key in frontend code, public repositories, or client-side JavaScript.
    Use `YOUR_KEY` as a placeholder in examples.
  version: "1.0.0"
  contact:
    name: ipxt.com Support
    email: support@ipxt.com
  license:
    name: Proprietary

servers:
  - url: https://ipxt.com/api/v1
    description: Production

tags:
  - name: Public
    description: No authentication required
  - name: Lookup
    description: IP geolocation lookup
  - name: Account
    description: API key usage and limits

security:
  - BearerAuth: []
  - ApiKeyHeader: []

paths:
  /health:
    get:
      tags: [Public]
      summary: Health check
      description: Returns API status. No authentication required.
      security: []
      operationId: getHealth
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                success: true
                data:
                  status: ok
                  version: "1"
                meta: {}

  /account:
    get:
      tags: [Account]
      summary: Account and usage
      description: Returns plan, daily usage, rate limits, and masked key prefix for the authenticated API key.
      operationId: getAccount
      responses:
        "200":
          description: Account information
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AccountResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /me:
    get:
      tags: [Lookup]
      summary: Lookup caller IP
      description: Returns geolocation for the connecting IP (`REMOTE_ADDR`). Must match the key's allowed IP.
      operationId: getMe
      parameters:
        - $ref: "#/components/parameters/FieldsQuery"
        - $ref: "#/components/parameters/SectionsQuery"
        - $ref: "#/components/parameters/CacheQuery"
        - $ref: "#/components/parameters/RefreshQuery"
      responses:
        "200":
          description: Geo data for caller IP
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LookupResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/IpMismatch"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          $ref: "#/components/responses/LookupFailed"

  /me/{shortcut}:
    get:
      tags: [Lookup]
      summary: Caller IP shortcut
      description: Returns a single grouped field for the caller IP. Use `plain=1` for text/plain output on supported shortcuts.
      operationId: getMeShortcut
      parameters:
        - $ref: "#/components/parameters/ShortcutPath"
        - $ref: "#/components/parameters/PlainQuery"
        - $ref: "#/components/parameters/CacheQuery"
        - $ref: "#/components/parameters/RefreshQuery"
      responses:
        "200":
          description: Shortcut value (JSON or plain text)
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ShortcutGeoResponse"
                  - $ref: "#/components/schemas/ShortcutCoordinatesResponse"
                  - $ref: "#/components/schemas/ShortcutNetworkResponse"
                  - $ref: "#/components/schemas/ShortcutSecurityResponse"
            text/plain:
              schema:
                type: string
              examples:
                city:
                  value: Ashburn
                coordinates:
                  value: "39.03,-77.5"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          $ref: "#/components/responses/LookupFailed"

  /lookup:
    post:
      tags: [Lookup]
      summary: Batch IP lookup
      description: Look up multiple IPs in a single request. Maximum batch size is 50 (configurable). Item-level errors do not fail the whole batch.
      operationId: postBatchLookup
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BatchRequest"
            examples:
              withFields:
                summary: With field selection
                value:
                  ips:
                    - 8.8.8.8
                    - 1.1.1.1
                  fields:
                    - ip
                    - country
                    - city
                    - lat
                    - lon
                    - isp
              withSections:
                summary: With sections
                value:
                  ips:
                    - 8.8.8.8
                    - 1.1.1.1
                  sections:
                    - location
                    - network
      responses:
        "200":
          description: Batch results
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BatchResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /lookup/{ip}:
    get:
      tags: [Lookup]
      summary: Lookup IP address
      description: Returns full geolocation data for a public IPv4 or IPv6 address.
      operationId: getLookup
      parameters:
        - $ref: "#/components/parameters/IpPath"
        - $ref: "#/components/parameters/FieldsQuery"
        - $ref: "#/components/parameters/SectionsQuery"
        - $ref: "#/components/parameters/CacheQuery"
        - $ref: "#/components/parameters/RefreshQuery"
      responses:
        "200":
          description: Geo lookup result
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LookupResponse"
              examples:
                full:
                  summary: Full response
                  value:
                    success: true
                    data:
                      ip: 8.8.8.8
                      geo:
                        country: United States
                        city: Ashburn
                        lat: 39.03
                        lon: -77.5
                    meta:
                      cached: true
                      queried_at: "2026-05-21T14:14:41+00:00"
                fields:
                  summary: Selected fields
                  value:
                    success: true
                    data:
                      ip: 8.8.8.8
                      geo:
                        country: United States
                        city: Ashburn
                        lat: 39.03
                        lon: -77.5
                    meta:
                      cached: true
                      queried_at: "2026-05-21T14:14:41+00:00"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/IpMismatch"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          $ref: "#/components/responses/LookupFailed"

  /lookup/{ip}/{shortcut}:
    get:
      tags: [Lookup]
      summary: IP lookup shortcut
      description: Convenience endpoint for a single grouped value. Supported shortcuts return JSON; add `plain=1` for text/plain on city, country, coordinates, timezone, currency, isp, continent.
      operationId: getLookupShortcut
      parameters:
        - $ref: "#/components/parameters/IpPath"
        - $ref: "#/components/parameters/ShortcutPath"
        - $ref: "#/components/parameters/PlainQuery"
        - $ref: "#/components/parameters/CacheQuery"
        - $ref: "#/components/parameters/RefreshQuery"
      responses:
        "200":
          description: Shortcut value
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/XRateLimitLimit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/XRateLimitRemaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/XRateLimitReset"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ShortcutGeoResponse"
                  - $ref: "#/components/schemas/ShortcutCoordinatesResponse"
                  - $ref: "#/components/schemas/ShortcutNetworkResponse"
                  - $ref: "#/components/schemas/ShortcutSecurityResponse"
            text/plain:
              schema:
                type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "502":
          $ref: "#/components/responses/LookupFailed"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: "API key as Bearer token. Example: Bearer YOUR_KEY"
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-Api-Key
      description: Alternative API key header

  headers:
    XRateLimitLimit:
      description: Total request limit for the active window
      schema:
        type: integer
        example: 1000
    XRateLimitRemaining:
      description: Remaining requests in the current window
      schema:
        type: integer
        example: 872
    XRateLimitReset:
      description: Unix timestamp when the limit window resets
      schema:
        type: integer
        example: 1779398400

  parameters:
    IpPath:
      name: ip
      in: path
      required: true
      description: IPv4 or IPv6 address
      schema:
        type: string
      example: 8.8.8.8

    ShortcutPath:
      name: shortcut
      in: path
      required: true
      schema:
        type: string
        enum:
          - continent
          - country
          - city
          - coordinates
          - timezone
          - currency
          - isp
          - network
          - security

    FieldsQuery:
      name: fields
      in: query
      description: Comma-separated list of fields to return. Cannot be combined with `sections`.
      schema:
        type: string
      example: country,city,lat,lon

    SectionsQuery:
      name: sections
      in: query
      description: Comma-separated sections (location, network, time, finance, security). Cannot be combined with `fields`.
      schema:
        type: string
      example: location,network

    PlainQuery:
      name: plain
      in: query
      description: Return plain text instead of JSON (shortcut endpoints only)
      schema:
        type: string
        enum: ["1"]

    CacheQuery:
      name: cache
      in: query
      description: "cache=0 bypasses cache read"
      schema:
        type: string
        enum: ["0", "1"]

    RefreshQuery:
      name: refresh
      in: query
      description: "refresh=1 forces cache refresh"
      schema:
        type: string
        enum: ["1"]

  responses:
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            missing:
              value:
                success: false
                error:
                  code: AUTH_MISSING
                  message: API key is required.
            invalid:
              value:
                success: false
                error:
                  code: AUTH_INVALID
                  message: Invalid or revoked API key.

    IpMismatch:
      description: Request IP does not match key allowed IP
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: AUTH_IP_MISMATCH
              message: API key is not authorized for this IP address.

    BadRequest:
      description: Invalid request parameters or body
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          examples:
            fieldInvalid:
              value:
                success: false
                error:
                  code: FIELD_INVALID
                  message: "Invalid field requested: foo"
            paramConflict:
              value:
                success: false
                error:
                  code: PARAM_CONFLICT
                  message: Use either fields or sections, not both.
            ipInvalid:
              value:
                success: false
                error:
                  code: IP_INVALID
                  message: Invalid IP address.

    RateLimited:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: RATE_LIMITED
              message: Daily API request limit exceeded.

    LookupFailed:
      description: Geo data could not be retrieved
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
          example:
            success: false
            error:
              code: LOOKUP_FAILED
              message: Geo data not found for this IP.

  schemas:
    ErrorResponse:
      type: object
      required: [success, error]
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              enum:
                - AUTH_MISSING
                - AUTH_INVALID
                - AUTH_IP_MISMATCH
                - IP_INVALID
                - IP_PRIVATE
                - IP_RESERVED
                - LOOKUP_FAILED
                - RATE_LIMITED
                - FIELD_INVALID
                - SECTION_INVALID
                - PARAM_CONFLICT
                - FORMAT_INVALID
                - BATCH_TOO_LARGE
                - BODY_INVALID
                - METHOD_NOT_ALLOWED
                - SERVER_ERROR
            message:
              type: string

    HealthResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            status:
              type: string
              example: ok
            version:
              type: string
              example: "1"
        meta:
          type: object

    Geo:
      type: object
      properties:
        continent:
          type: string
          nullable: true
        continent_code:
          type: string
          nullable: true
        country:
          type: string
          nullable: true
        country_code:
          type: string
          nullable: true
        region:
          type: string
          nullable: true
        region_code:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        district:
          type: string
          nullable: true
        zip:
          type: string
          nullable: true
        lat:
          type: number
          format: float
          nullable: true
        lon:
          type: number
          format: float
          nullable: true
        timezone:
          type: string
          nullable: true
        utc_offset:
          type: integer
          nullable: true
        currency:
          type: string
          nullable: true
        isp:
          type: string
          nullable: true
        org:
          type: string
          nullable: true
        as:
          type: string
          nullable: true
        as_name:
          type: string
          nullable: true
        mobile:
          type: boolean
          nullable: true
        proxy:
          type: boolean
          nullable: true
        hosting:
          type: boolean
          nullable: true

    LookupMeta:
      type: object
      properties:
        cached:
          type: boolean
        cache_ttl:
          type: integer
        queried_at:
          type: string
          format: date-time

    LookupResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            ip:
              type: string
            geo:
              $ref: "#/components/schemas/Geo"
        meta:
          $ref: "#/components/schemas/LookupMeta"

    ShortcutGeoResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            ip:
              type: string
            geo:
              type: object
        meta:
          $ref: "#/components/schemas/LookupMeta"

    ShortcutCoordinatesResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            ip:
              type: string
            coordinates:
              type: object
              properties:
                lat:
                  type: number
                lon:
                  type: number
        meta:
          $ref: "#/components/schemas/LookupMeta"

    ShortcutNetworkResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            ip:
              type: string
            network:
              type: object
              properties:
                isp:
                  type: string
                org:
                  type: string
                as:
                  type: string
                as_name:
                  type: string
        meta:
          $ref: "#/components/schemas/LookupMeta"

    ShortcutSecurityResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            ip:
              type: string
            security:
              type: object
              properties:
                mobile:
                  type: boolean
                proxy:
                  type: boolean
                hosting:
                  type: boolean
        meta:
          $ref: "#/components/schemas/LookupMeta"

    AccountResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            plan:
              type: string
              example: free
            requests_today:
              type: integer
            daily_limit:
              type: integer
            remaining:
              type: integer
            rate_limit_per_minute:
              type: integer
            key_prefix:
              type: string
              example: ipxt_4e7f...
            created_at:
              type: string
              format: date-time
              nullable: true
            last_used_at:
              type: string
              format: date-time
              nullable: true
        meta:
          type: object

    BatchRequest:
      type: object
      required: [ips]
      properties:
        ips:
          type: array
          minItems: 1
          maxItems: 50
          items:
            type: string
          example:
            - 8.8.8.8
            - 1.1.1.1
        fields:
          type: array
          items:
            type: string
            enum:
              - ip
              - continent
              - continent_code
              - country
              - country_code
              - region
              - region_code
              - city
              - district
              - zip
              - lat
              - lon
              - timezone
              - utc_offset
              - currency
              - isp
              - org
              - as
              - as_name
              - mobile
              - proxy
              - hosting
        sections:
          type: array
          items:
            type: string
            enum:
              - location
              - network
              - time
              - finance
              - security

    BatchResponse:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            results:
              type: array
              items:
                oneOf:
                  - $ref: "#/components/schemas/BatchSuccessItem"
                  - $ref: "#/components/schemas/BatchErrorItem"
        meta:
          type: object
          properties:
            count:
              type: integer
            queried_at:
              type: string
              format: date-time

    BatchSuccessItem:
      type: object
      properties:
        success:
          type: boolean
          example: true
        ip:
          type: string
        geo:
          $ref: "#/components/schemas/Geo"
        meta:
          type: object
          properties:
            cached:
              type: boolean

    BatchErrorItem:
      type: object
      properties:
        success:
          type: boolean
          example: false
        ip:
          type: string
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string
