openapi: 3.0.3
info:
  title: coords.zone
  description: >
    GPS coordinate → plant hardiness zone. Returns the USDA hardiness zone for
    any point on Earth, plus local country zones where available.
  version: "1"
  contact:
    url: https://coords.zone
  license:
    name: Commercial
    url: https://coords.zone

servers:
  - url: https://coords.zone

paths:
  /api/v1/zone:
    get:
      summary: Get hardiness zone for a coordinate
      operationId: getZone
      parameters:
        - name: lat
          in: query
          required: true
          description: Latitude (-90 to 90)
          schema:
            type: number
            format: double
            minimum: -90
            maximum: 90
          example: 48.14
        - name: lng
          in: query
          required: true
          description: Longitude (-180 to 180)
          schema:
            type: number
            format: double
            minimum: -180
            maximum: 180
          example: 11.58
      security:
        - bearerAuth: []
        - {}
      responses:
        "200":
          description: Zone data for the coordinate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ZoneResponse"
              example:
                usda_zone: "8a"
                local_zone: "Z8a"
                lat: 48.14
                lng: 11.58
        "401":
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "Invalid API key"
        "402":
          description: >
            Free tier exhausted (1 000 req/month) — applies to unsubscribed
            accounts and subscriptions that have been canceled. Upgrade at
            https://coords.zone/dashboard.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "Monthly request limit reached. Upgrade your plan."
        "429":
          description: >
            Rate limit exceeded. Requests are capped at 10 per second —
            per IP for unauthenticated requests, per API key for
            authenticated ones. Unauthenticated requests are additionally
            limited to 100 per month per IP.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "Too many requests"

  /api/v1/country:
    get:
      summary: Get country and available local zones for a coordinate
      operationId: getCountry
      parameters:
        - name: lat
          in: query
          required: true
          description: Latitude (-90 to 90)
          schema:
            type: number
            format: double
            minimum: -90
            maximum: 90
          example: 48.14
        - name: lng
          in: query
          required: true
          description: Longitude (-180 to 180)
          schema:
            type: number
            format: double
            minimum: -180
            maximum: 180
          example: 11.58
      security:
        - bearerAuth: []
        - {}
      responses:
        "200":
          description: Country data for the coordinate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CountryResponse"
              example:
                name: "Germany"
                iso_code: "DE"
                zones: ["Z1a", "Z1b", "Z2a", "Z2b", "Z3a", "Z3b", "Z4a", "Z4b", "Z5a", "Z5b", "Z6a", "Z6b", "Z7a", "Z7b", "Z8a", "Z8b", "Z9a", "Z9b", "Z10a", "Z10b", "Z11a", "Z11b", "Z12a", "Z12b", "Z13a", "Z13b"]
                lat: 48.14
                lng: 11.58
        "401":
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "Invalid API key"
        "402":
          description: >
            Free tier exhausted (1 000 req/month) — applies to unsubscribed
            accounts and subscriptions that have been canceled. Upgrade at
            https://coords.zone/dashboard.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "Monthly request limit reached. Upgrade your plan."
        "429":
          description: >
            Rate limit exceeded. Requests are capped at 10 per second —
            per IP for unauthenticated requests, per API key for
            authenticated ones. Unauthenticated requests are additionally
            limited to 100 per month per IP.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "Too many requests"

  /api/v1/climate:
    get:
      summary: Get raw climate inputs for a coordinate
      operationId: getClimate
      parameters:
        - name: lat
          in: query
          required: true
          description: Latitude (-90 to 90)
          schema:
            type: number
            format: double
            minimum: -90
            maximum: 90
          example: 48.14
        - name: lng
          in: query
          required: true
          description: Longitude (-180 to 180)
          schema:
            type: number
            format: double
            minimum: -180
            maximum: 180
          example: 11.58
      security:
        - bearerAuth: []
        - {}
      responses:
        "200":
          description: Raw climate band values for the coordinate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClimateResponse"
              example:
                min_temp: -14.5
                max_temp: 19.5
                frost_last_doy: 96
                frost_last_date: "Apr 6"
                frost_first_doy: 310
                frost_first_date: "Nov 6"
                lat: 48.14
                lng: 11.58
        "401":
          description: Invalid API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "Invalid API key"
        "402":
          description: >
            Free tier exhausted (1 000 req/month) — applies to unsubscribed
            accounts and subscriptions that have been canceled. Upgrade at
            https://coords.zone/dashboard.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "Monthly request limit reached. Upgrade your plan."
        "429":
          description: >
            Rate limit exceeded. Requests are capped at 10 per second —
            per IP for unauthenticated requests, per API key for
            authenticated ones. Unauthenticated requests are additionally
            limited to 100 per month per IP.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "Too many requests"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >
        API key with `czk_` prefix. Obtain at https://coords.zone/dashboard.
        Authenticated requests bypass IP rate limiting (still capped at
        10 requests per second per key) and get 1 000 free requests per
        month. Beyond that, pay per usage: $0.003 per request
        (~$3 per 1 000 requests), billed monthly via Stripe. No commitment —
        cancel anytime.

  schemas:
    ZoneResponse:
      type: object
      required: [usda_zone, lat, lng]
      properties:
        usda_zone:
          type: string
          nullable: true
          description: >
            USDA hardiness zone (1a–13b). Null only for coordinates outside
            climate data coverage (rare edge cases at extreme latitudes).
          example: "8a"
        local_zone:
          type: string
          nullable: true
          description: >
            Country-specific zone, using that country's local scale (see
            `GET /api/v1/country` for the available zones for a given
            country). Null when no local zone system applies to this
            coordinate's country.
          example: null
        lat:
          type: number
          description: Echo of the input latitude
          example: 48.14
        lng:
          type: number
          description: Echo of the input longitude
          example: 11.58

    CountryResponse:
      type: object
      required: [name, iso_code, zones, lat, lng]
      properties:
        name:
          type: string
          nullable: true
          description: >
            Country name for the coordinate. Null if the point falls outside
            all known countries (e.g. international waters).
          example: "Germany"
        iso_code:
          type: string
          nullable: true
          description: >
            ISO country code (alpha-2, or alpha-3 fallback) for the
            coordinate. Null if the point falls outside all known countries.
          example: "DE"
        zones:
          type: array
          items:
            type: string
          description: >
            Available zones for this country. Uses the country's local zone
            system when one exists (e.g. Australia, the UK, South Africa,
            Germany, Sweden, Norway); otherwise falls back to the full USDA
            range (1a–13b), since USDA zones are a global climate
            classification independent of borders.
          example: ["1a", "1b", "2a", "2b", "3a", "3b", "4a", "4b", "5a", "5b", "6a", "6b", "7a", "7b", "8a", "8b", "9a", "9b", "10a", "10b", "11a", "11b", "12a", "12b", "13a", "13b"]
        lat:
          type: number
          description: Echo of the input latitude
          example: 48.14
        lng:
          type: number
          description: Echo of the input longitude
          example: 11.58

    ClimateResponse:
      type: object
      required: [min_temp, max_temp, frost_last_doy, frost_first_doy, lat, lng]
      properties:
        min_temp:
          type: number
          nullable: true
          description: >
            Average annual extreme minimum temperature (°C) — the statistic
            USDA hardiness zones are derived from. Null over international
            waters or other areas outside coverage.
          example: -15.5
        max_temp:
          type: number
          nullable: true
          description: >
            Average warmest-month mean temperature (°C). Null outside
            coverage.
          example: 28.3
        frost_last_doy:
          type: integer
          nullable: true
          description: >
            Average day-of-year of the last spring frost. Null outside
            coverage.
          example: 94
        frost_last_date:
          type: string
          nullable: true
          description: >
            `frost_last_doy` as a `"Mon D"` date string.
          example: "Apr 4"
        frost_first_doy:
          type: integer
          nullable: true
          description: >
            Average day-of-year of the first autumn frost. Null outside
            coverage.
          example: 297
        frost_first_date:
          type: string
          nullable: true
          description: >
            `frost_first_doy` as a `"Mon D"` date string.
          example: "Oct 24"
        lat:
          type: number
          description: Echo of the input latitude
          example: 48.14
        lng:
          type: number
          description: Echo of the input longitude
          example: 11.58

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          example: "Invalid API key"
