openapi: 3.0.3
info:
  title: CSV2GEO Geocoding API
  description: |
    Fast, accurate geocoding API powered by 446M+ addresses and 15M+ places worldwide.

    ## Features
    - Forward geocoding (address → coordinates)
    - Reverse geocoding (coordinates → address)
    - Batch processing (up to 10,000 addresses per request)
    - **Places API** - Search 15.5M+ POIs (businesses, landmarks, restaurants)
    - **Divisions API** - 205K+ administrative boundaries (cities, counties, neighborhoods)
    - Global coverage with rooftop-level accuracy

    ## Authentication
    All requests require an API key. Include it as:
    - Query parameter: `?api_key=YOUR_KEY`
    - Header: `Authorization: Bearer YOUR_KEY`

    Get your API key at [csv2geo.com/api-keys](https://csv2geo.com/api-keys)

    ### API Key Permissions
    Each API key can have specific permissions:
    - `forward` - Forward geocoding (address → coordinates)
    - `reverse` - Reverse geocoding (coordinates → address)
    - `batch` - Batch processing (POST endpoints)
    - `places` - Places/POI search (search, nearby, categories)
    - `divisions` - Administrative boundaries (search, contains, subtypes)

    ## Rate Limits
    Rate limits are enforced per API key. Check response headers for your current usage:
    - `X-RateLimit-Limit` - Maximum requests per minute
    - `X-RateLimit-Remaining` - Requests remaining in current window
    - `X-RateLimit-Reset` - Unix timestamp when the window resets

    | Tier | Requests/min | Batch Size |
    |------|--------------|------------|
    | Free | 100 | 100 |
    | Starter | 1,000 | 1,000 |
    | Growth | 5,000 | 5,000 |
    | Pro | 10,000 | 10,000 |
  version: 1.0.0
  contact:
    name: Scale Campaign
    url: https://csv2geo.com
    email: admin@csv2geo.com
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

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

security:
  - ApiKeyQuery: []
  - ApiKeyHeader: []

tags:
  - name: Geocoding
    description: Forward geocoding - convert addresses to coordinates
  - name: Reverse Geocoding
    description: Reverse geocoding - convert coordinates to addresses
  - name: Batch
    description: Batch processing for multiple addresses
  - name: Places
    description: Search for points of interest (POIs) - businesses, landmarks, and more
  - name: Divisions
    description: Administrative boundaries - cities, counties, neighborhoods, states

paths:
  /geocode:
    get:
      tags:
        - Geocoding
      summary: Geocode a single address
      description: Convert a single address to latitude/longitude coordinates
      operationId: geocodeSingle
      parameters:
        - name: q
          in: query
          required: true
          description: The address to geocode
          schema:
            type: string
          example: "1600 Pennsylvania Ave, Washington DC"
        - name: country
          in: query
          required: false
          description: Limit results to a specific country (ISO 3166-1 alpha-2)
          schema:
            type: string
          example: "US"
        - name: limit
          in: query
          required: false
          description: Maximum number of results to return (1-5)
          schema:
            type: integer
            minimum: 1
            maximum: 5
            default: 1
        - name: format
          in: query
          required: false
          description: Response format
          schema:
            type: string
            enum: [full, simple]
            default: full
      responses:
        '200':
          description: Successful geocoding response
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: Maximum requests per minute
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests remaining in current window
            X-RateLimit-Reset:
              schema:
                type: integer
              description: Unix timestamp when window resets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GeocodeResponse'
        '400':
          description: Bad request - invalid parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized - invalid or missing API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_key:
                  summary: Missing API key
                  value:
                    error:
                      code: missing_api_key
                      message: "API key is required. Provide it via 'api_key' query parameter or 'Authorization: Bearer <key>' header."
                      status: 401
                invalid_key:
                  summary: Invalid API key
                  value:
                    error:
                      code: invalid_api_key
                      message: "Invalid API key provided."
                      status: 401
        '403':
          description: Forbidden - insufficient permissions
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: insufficient_permissions
                  message: "This API key does not have 'forward' geocoding permission."
                  status: 403
        '429':
          description: Rate limit exceeded
          headers:
            Retry-After:
              schema:
                type: integer
              description: Seconds until rate limit resets
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: rate_limit_exceeded
                  message: "Rate limit exceeded. Please wait before making more requests."
                  status: 429
                  retry_after: 45

    post:
      tags:
        - Batch
      summary: Batch geocode multiple addresses
      description: Convert up to 10,000 addresses to coordinates in a single request
      operationId: geocodeBatch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BatchGeocodeRequest'
      responses:
        '200':
          description: Successful batch geocoding response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchGeocodeResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /reverse:
    get:
      tags:
        - Reverse Geocoding
      summary: Reverse geocode a single coordinate
      description: Convert latitude/longitude coordinates to a human-readable address
      operationId: reverseSingle
      parameters:
        - name: lat
          in: query
          required: true
          description: Latitude
          schema:
            type: number
            format: double
            minimum: -90
            maximum: 90
          example: 38.8977
        - name: lng
          in: query
          required: true
          description: Longitude
          schema:
            type: number
            format: double
            minimum: -180
            maximum: 180
          example: -77.0365
        - name: format
          in: query
          required: false
          description: Response format
          schema:
            type: string
            enum: [full, simple]
            default: full
      responses:
        '200':
          description: Successful reverse geocoding response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ReverseGeocodeResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

    post:
      tags:
        - Batch
      summary: Batch reverse geocode multiple coordinates
      description: Convert up to 10,000 coordinates to addresses in a single request
      operationId: reverseBatch
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/BatchReverseRequest'
      responses:
        '200':
          description: Successful batch reverse geocoding response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BatchReverseResponse'
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /health:
    get:
      tags:
        - System
      summary: Health check
      description: Check API status and version
      operationId: healthCheck
      security: []
      responses:
        '200':
          description: API is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: "ok"
                  version:
                    type: string
                    example: "1.0.0"
                  timestamp:
                    type: string
                    format: date-time

  /places:
    get:
      tags:
        - Places
      summary: Search places by name
      description: |
        Search for points of interest (POIs) by name. Returns businesses, landmarks, and other places matching your query.

        Uses text search to match place names and brand names.
      operationId: searchPlaces
      parameters:
        - name: q
          in: query
          required: true
          description: Search query (place name, brand, etc.)
          schema:
            type: string
          example: "starbucks"
        - name: category
          in: query
          required: false
          description: Filter by category (e.g., "coffee_shop", "restaurant")
          schema:
            type: string
          example: "coffee_shop"
        - name: country
          in: query
          required: false
          description: Filter by country (ISO 3166-1 alpha-2). Defaults to "US"
          schema:
            type: string
            default: "US"
          example: "US"
        - name: limit
          in: query
          required: false
          description: Maximum number of results (1-50)
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
      responses:
        '200':
          description: Successful search response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlacesSearchResponse'
        '400':
          description: Bad request - missing query parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: missing_query
                  message: "The 'q' parameter is required for place search"
                  status: 400
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /places/nearby:
    get:
      tags:
        - Places
      summary: Find places near a location
      description: |
        Search for places within a radius of a given location. Results are sorted by distance from the center point.

        Uses geospatial indexing for efficient nearby searches.
      operationId: nearbyPlaces
      parameters:
        - name: lat
          in: query
          required: true
          description: Latitude of center point
          schema:
            type: number
            format: double
            minimum: -90
            maximum: 90
          example: 40.7128
        - name: lng
          in: query
          required: true
          description: Longitude of center point
          schema:
            type: number
            format: double
            minimum: -180
            maximum: 180
          example: -74.0060
        - name: radius
          in: query
          required: false
          description: Search radius in meters (1-50000). Default is 1000m (1km)
          schema:
            type: integer
            minimum: 1
            maximum: 50000
            default: 1000
        - name: category
          in: query
          required: false
          description: Filter by category
          schema:
            type: string
          example: "restaurant"
        - name: limit
          in: query
          required: false
          description: Maximum number of results (1-50)
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
      responses:
        '200':
          description: Successful nearby search response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PlacesNearbyResponse'
        '400':
          description: Bad request - invalid coordinates or parameters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_coordinates:
                  summary: Missing coordinates
                  value:
                    error:
                      code: missing_coordinates
                      message: "Both 'lat' and 'lng' parameters are required"
                      status: 400
                invalid_latitude:
                  summary: Invalid latitude
                  value:
                    error:
                      code: invalid_latitude
                      message: "Latitude must be a number between -90 and 90"
                      status: 400
                invalid_longitude:
                  summary: Invalid longitude
                  value:
                    error:
                      code: invalid_longitude
                      message: "Longitude must be a number between -180 and 180"
                      status: 400
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /places/categories:
    get:
      tags:
        - Places
      summary: List available place categories
      description: |
        Returns all available place categories in the database. Use these values to filter searches by category.
      operationId: listCategories
      responses:
        '200':
          description: List of categories
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CategoriesResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /divisions:
    get:
      tags:
        - Divisions
      summary: Search divisions by name
      description: |
        Search for administrative divisions (cities, counties, neighborhoods) by name.
        Uses text search to match division names.
      operationId: searchDivisions
      parameters:
        - name: q
          in: query
          required: true
          description: Search query (division name)
          schema:
            type: string
          example: "los angeles"
        - name: subtype
          in: query
          required: false
          description: Filter by division type (locality, county, neighborhood, etc.)
          schema:
            type: string
          example: "locality"
        - name: country
          in: query
          required: false
          description: Filter by country (ISO 3166-1 alpha-2). Defaults to "US"
          schema:
            type: string
            default: "US"
          example: "US"
        - name: limit
          in: query
          required: false
          description: Maximum number of results (1-50)
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
      responses:
        '200':
          description: Successful search response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DivisionsSearchResponse'
        '400':
          description: Bad request - missing query parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: missing_query
                  message: "The 'q' parameter is required for division search"
                  status: 400
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /divisions/contains:
    get:
      tags:
        - Divisions
      summary: Find divisions near a location
      description: |
        Find administrative divisions near a given coordinate. Returns divisions sorted by distance.
        Useful for answering "What city/county/neighborhood is this location in?"
      operationId: divisionsContains
      parameters:
        - name: lat
          in: query
          required: true
          description: Latitude of the point
          schema:
            type: number
            format: double
            minimum: -90
            maximum: 90
          example: 40.7128
        - name: lng
          in: query
          required: true
          description: Longitude of the point
          schema:
            type: number
            format: double
            minimum: -180
            maximum: 180
          example: -74.0060
        - name: subtype
          in: query
          required: false
          description: Filter by division type
          schema:
            type: string
          example: "locality"
        - name: limit
          in: query
          required: false
          description: Maximum number of results (1-50)
          schema:
            type: integer
            minimum: 1
            maximum: 50
            default: 10
      responses:
        '200':
          description: Successful lookup response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DivisionsContainsResponse'
        '400':
          description: Bad request - invalid coordinates
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                missing_coordinates:
                  summary: Missing coordinates
                  value:
                    error:
                      code: missing_coordinates
                      message: "Both 'lat' and 'lng' parameters are required"
                      status: 400
                invalid_latitude:
                  summary: Invalid latitude
                  value:
                    error:
                      code: invalid_latitude
                      message: "Latitude must be a number between -90 and 90"
                      status: 400
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /divisions/subtypes:
    get:
      tags:
        - Divisions
      summary: List available division subtypes
      description: |
        Returns all available division subtypes (e.g., country, region, county, locality, neighborhood).
        Use these values to filter searches by subtype.
      operationId: listSubtypes
      responses:
        '200':
          description: List of subtypes
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DivisionsSubtypesResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

components:
  securitySchemes:
    ApiKeyQuery:
      type: apiKey
      in: query
      name: api_key
      description: API key passed as query parameter
    ApiKeyHeader:
      type: http
      scheme: bearer
      description: API key passed as Bearer token in Authorization header

  schemas:
    GeocodeResponse:
      type: object
      properties:
        query:
          type: string
          description: The original query
          example: "1600 Pennsylvania Ave, Washington DC"
        results:
          type: array
          items:
            $ref: '#/components/schemas/GeocodeResult'
        meta:
          $ref: '#/components/schemas/Meta'

    GeocodeResult:
      type: object
      properties:
        formatted_address:
          type: string
          description: Full formatted address
          example: "1600 Pennsylvania Avenue NW, Washington, DC 20500"
        location:
          $ref: '#/components/schemas/Location'
        accuracy:
          type: string
          description: Accuracy level of the geocode
          enum: [rooftop, range_interpolation, geometric_center, approximate]
          example: "rooftop"
        accuracy_score:
          type: number
          format: float
          description: Confidence score (0-1)
          example: 0.98
        components:
          $ref: '#/components/schemas/AddressComponents'
        source:
          type: string
          description: Data source attribution
          example: "Overture Maps Foundation"

    Location:
      type: object
      properties:
        lat:
          type: number
          format: double
          description: Latitude
          example: 38.8977
        lng:
          type: number
          format: double
          description: Longitude
          example: -77.0365

    AddressComponents:
      type: object
      properties:
        house_number:
          type: string
          example: "1600"
        street:
          type: string
          example: "Pennsylvania Avenue NW"
        unit:
          type: string
          nullable: true
          example: null
        city:
          type: string
          example: "Washington"
        state:
          type: string
          example: "DC"
        postcode:
          type: string
          example: "20500"
        country:
          type: string
          description: ISO 3166-1 alpha-2 country code
          example: "US"

    ReverseGeocodeResponse:
      type: object
      properties:
        query:
          $ref: '#/components/schemas/Location'
        results:
          type: array
          items:
            $ref: '#/components/schemas/GeocodeResult'
        meta:
          $ref: '#/components/schemas/Meta'

    BatchGeocodeRequest:
      type: object
      required:
        - addresses
      properties:
        addresses:
          type: array
          description: List of addresses to geocode (max 10,000)
          maxItems: 10000
          items:
            type: string
          example:
            - "1600 Pennsylvania Ave, Washington DC"
            - "350 Fifth Avenue, New York, NY"
            - "1 Infinite Loop, Cupertino, CA"

    BatchGeocodeResponse:
      type: object
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/GeocodeResponse'
        meta:
          $ref: '#/components/schemas/BatchMeta'

    BatchReverseRequest:
      type: object
      required:
        - coordinates
      properties:
        coordinates:
          type: array
          description: List of coordinates to reverse geocode (max 10,000)
          maxItems: 10000
          items:
            $ref: '#/components/schemas/Location'
          example:
            - lat: 38.8977
              lng: -77.0365
            - lat: 40.7484
              lng: -73.9857

    BatchReverseResponse:
      type: object
      properties:
        results:
          type: array
          items:
            $ref: '#/components/schemas/ReverseGeocodeResponse'
        meta:
          $ref: '#/components/schemas/BatchMeta'

    Meta:
      type: object
      properties:
        version:
          type: string
          example: "v1"
        timestamp:
          type: string
          format: date-time
        credits_used:
          type: integer
          example: 1
        credits_remaining:
          type: integer
          example: 9999

    BatchMeta:
      type: object
      properties:
        version:
          type: string
          example: "v1"
        timestamp:
          type: string
          format: date-time
        total_addresses:
          type: integer
          example: 3
        successful:
          type: integer
          example: 3
        failed:
          type: integer
          example: 0
        credits_used:
          type: integer
          example: 3
        credits_remaining:
          type: integer
          example: 9997

    PlaceResult:
      type: object
      properties:
        name:
          type: string
          description: Place name
          example: "Starbucks"
        location:
          $ref: '#/components/schemas/Location'
        category:
          type: string
          description: Primary category
          example: "coffee_shop"
        categories:
          type: array
          description: All categories (primary + alternates)
          items:
            type: string
          example: ["coffee_shop", "cafe", "food_and_drink"]
        confidence:
          type: number
          format: float
          description: Data confidence score (0-1)
          example: 0.95
        address:
          type: string
          nullable: true
          description: Freeform address
          example: "123 Main St, New York, NY 10001"
        city:
          type: string
          nullable: true
          example: "New York"
        state:
          type: string
          nullable: true
          example: "NY"
        postcode:
          type: string
          nullable: true
          example: "10001"
        country:
          type: string
          description: ISO 3166-1 alpha-2 country code
          example: "US"
        phone:
          type: string
          nullable: true
          description: Primary phone number
          example: "+1-555-123-4567"
        website:
          type: string
          nullable: true
          description: Primary website URL
          example: "https://www.starbucks.com"
        brand:
          type: string
          nullable: true
          description: Brand name (if applicable)
          example: "Starbucks"
        operating_status:
          type: string
          nullable: true
          description: Operating status (e.g., "open", "closed_permanently")
          example: "open"

    PlacesSearchResponse:
      type: object
      properties:
        query:
          type: string
          description: The search query
          example: "starbucks"
        results:
          type: array
          items:
            $ref: '#/components/schemas/PlaceResult'
        meta:
          $ref: '#/components/schemas/Meta'

    PlacesNearbyResponse:
      type: object
      properties:
        query:
          $ref: '#/components/schemas/PlacesNearbyQuery'
        results:
          type: array
          items:
            $ref: '#/components/schemas/PlaceResult'
        meta:
          $ref: '#/components/schemas/Meta'

    PlacesNearbyQuery:
      type: object
      properties:
        location:
          $ref: '#/components/schemas/Location'
        radius:
          type: integer
          description: Search radius in meters
          example: 1000
        category:
          type: string
          nullable: true
          description: Category filter (if provided)
          example: "restaurant"

    CategoriesResponse:
      type: object
      properties:
        categories:
          type: array
          description: List of all available categories
          items:
            type: string
          example: ["coffee_shop", "restaurant", "hotel", "gas_station", "pharmacy"]
        count:
          type: integer
          description: Total number of categories
          example: 150
        meta:
          $ref: '#/components/schemas/Meta'

    DivisionResult:
      type: object
      properties:
        name:
          type: string
          description: Division name
          example: "Los Angeles"
        location:
          $ref: '#/components/schemas/Location'
        subtype:
          type: string
          description: Division type (locality, county, neighborhood, etc.)
          example: "locality"
        country:
          type: string
          description: ISO 3166-1 alpha-2 country code
          example: "US"
        region:
          type: string
          nullable: true
          description: Region code (e.g., US-CA for California)
          example: "US-CA"
        population:
          type: integer
          nullable: true
          description: Population (if available)
          example: 4030904
        wikidata:
          type: string
          nullable: true
          description: Wikidata ID for this division
          example: "Q65"
        parent_division_id:
          type: string
          nullable: true
          description: Overture ID of the parent division
          example: "9b3d1c83-d266-4c67-b21f-6ff37f4d6ccb"

    DivisionsSearchResponse:
      type: object
      properties:
        query:
          type: string
          description: The search query
          example: "los angeles"
        results:
          type: array
          items:
            $ref: '#/components/schemas/DivisionResult'
        meta:
          $ref: '#/components/schemas/Meta'

    DivisionsContainsResponse:
      type: object
      properties:
        query:
          $ref: '#/components/schemas/Location'
        results:
          type: array
          items:
            $ref: '#/components/schemas/DivisionResult'
        meta:
          $ref: '#/components/schemas/Meta'

    DivisionsSubtypesResponse:
      type: object
      properties:
        subtypes:
          type: array
          description: List of all available division subtypes
          items:
            type: string
          example: ["country", "region", "county", "locality", "neighborhood", "macrohood", "microhood"]
        count:
          type: integer
          description: Total number of subtypes
          example: 7
        meta:
          $ref: '#/components/schemas/Meta'

    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: |
                Error code. Possible values:
                - `missing_api_key` - No API key provided
                - `invalid_api_key` - API key not found or malformed
                - `api_key_revoked` - API key has been revoked
                - `insufficient_permissions` - API key lacks required permission
                - `rate_limit_exceeded` - Too many requests
                - `invalid_request` - Missing or invalid parameters
              example: "invalid_api_key"
            message:
              type: string
              description: Human-readable error message
              example: "The provided API key is invalid or has been revoked."
            status:
              type: integer
              description: HTTP status code
              example: 401
            retry_after:
              type: integer
              description: Seconds to wait before retrying (only for rate_limit_exceeded)
              example: 45
