Units & Conversions

How to select unit systems and convert between them.

Selecting Units

Pass the units query parameter to control the unit system for all values in the response:

# Metric (default)
GET /v1/weather/london?units=metric

# Imperial
GET /v1/weather/london?units=imperial

# SI (strict scientific units)
GET /v1/weather/london?units=si

Conversion Formulas

MeasurementMetricImperialFormula
Temperature°C°F°F = °C × 9/5 + 32
Wind speedm/smphmph = m/s × 2.237
Precipitationmminchesin = mm × 0.03937
Visibilitymetersmilesmi = m × 0.000621
PressurehPainHginHg = hPa × 0.02953
Elevationmetersfeetft = m × 3.281

Field Name Changes

When switching units, field names change to reflect the unit. This prevents accidentally mixing unit systems:

Metric FieldImperial Field
temperature_ctemperature_f
feels_like_cfeels_like_f
dew_point_cdew_point_f
wind_speed_mswind_speed_mph
wind_gust_mswind_gust_mph
precipitation_mmprecipitation_in
precipitation_sum_mmprecipitation_sum_in
visibility_mvisibility_mi
pressure_hpapressure_inHg
elevation_melevation_ft

Common Reference Points

ConditionMetricImperial
Freezing point0°C32°F
Room temperature20°C68°F
Body temperature37°C98.6°F
Light breeze3 m/s6.7 mph
Strong wind15 m/s33.6 mph
Hurricane force33+ m/s74+ mph
Light rain0.5 mm/hr0.02 in/hr
Heavy rain7.5 mm/hr0.30 in/hr
Standard pressure1013.25 hPa29.92 inHg

Weather Codes

WMO standard weather condition codes used in all Qanto responses.

WMO Code Reference

The weather_code field uses WMO (World Meteorological Organization) standard codes. These are integers from 0 to 99:

CodeDescriptionCategory
0Clear skyClear
1Mainly clearClear
2Partly cloudyCloudy
3OvercastCloudy
45FogFog
48Depositing rime fogFog
51Light drizzleDrizzle
53Moderate drizzleDrizzle
55Dense drizzleDrizzle
56Light freezing drizzleDrizzle
57Dense freezing drizzleDrizzle
61Slight rainRain
63Moderate rainRain
65Heavy rainRain
66Light freezing rainRain
67Heavy freezing rainRain
71Slight snowfallSnow
73Moderate snowfallSnow
75Heavy snowfallSnow
77Snow grainsSnow
80Slight rain showersShowers
81Moderate rain showersShowers
82Violent rain showersShowers
85Slight snow showersShowers
86Heavy snow showersShowers
95ThunderstormThunderstorm
96Thunderstorm with slight hailThunderstorm
99Thunderstorm with heavy hailThunderstorm

JavaScript Icon Mapping

JavaScript
const weatherIcons = {
  0: { icon: "sun", label: "Clear sky" },
  1: { icon: "sun", label: "Mainly clear" },
  2: { icon: "cloud-sun", label: "Partly cloudy" },
  3: { icon: "cloud", label: "Overcast" },
  45: { icon: "cloud-fog", label: "Fog" },
  48: { icon: "cloud-fog", label: "Rime fog" },
  51: { icon: "cloud-drizzle", label: "Light drizzle" },
  53: { icon: "cloud-drizzle", label: "Drizzle" },
  55: { icon: "cloud-drizzle", label: "Dense drizzle" },
  56: { icon: "snowflake", label: "Freezing drizzle" },
  57: { icon: "snowflake", label: "Dense freezing drizzle" },
  61: { icon: "cloud-rain", label: "Light rain" },
  63: { icon: "cloud-rain", label: "Rain" },
  65: { icon: "cloud-rain", label: "Heavy rain" },
  66: { icon: "cloud-rain", label: "Freezing rain" },
  67: { icon: "cloud-rain", label: "Heavy freezing rain" },
  71: { icon: "snowflake", label: "Light snow" },
  73: { icon: "snowflake", label: "Snow" },
  75: { icon: "snowflake", label: "Heavy snow" },
  77: { icon: "snowflake", label: "Snow grains" },
  80: { icon: "cloud-rain", label: "Rain showers" },
  81: { icon: "cloud-rain", label: "Moderate showers" },
  82: { icon: "cloud-rain", label: "Violent showers" },
  85: { icon: "snowflake", label: "Snow showers" },
  86: { icon: "snowflake", label: "Heavy snow showers" },
  95: { icon: "cloud-lightning", label: "Thunderstorm" },
  96: { icon: "cloud-lightning", label: "Thunderstorm + hail" },
  99: { icon: "cloud-lightning", label: "Thunderstorm + heavy hail" },
};

function getWeatherIcon(code) {
  return weatherIcons[code] || { icon: "help-circle", label: "Unknown" };
}

Python Categorization

Python
def categorize_weather(code: int) -> str:
    """Categorize a WMO weather code into a simple group."""
    if code <= 1:
        return "clear"
    elif code <= 3:
        return "cloudy"
    elif code <= 48:
        return "fog"
    elif code <= 57:
        return "drizzle"
    elif code <= 67:
        return "rain"
    elif code <= 77:
        return "snow"
    elif code <= 86:
        return "showers"
    elif code <= 99:
        return "thunderstorm"
    return "unknown"

# Usage
weather = get_weather("london")
category = categorize_weather(weather["current"]["weather_code"])
print(f"Current conditions: {category}")  # "cloudy"

Rate Limits

Understanding and handling API rate limits.

Current Limits

PlanRequests/SecondRequests/MonthBurst Limit
Free210,0005 req
Starter ($29)10100,00020 req
Pro ($99)30500,00050 req
Business ($299)1002,000,000200 req
EnterpriseCustomUnlimitedCustom

Rate Limit Headers

Every API response includes rate limit information in the headers:

HeaderExampleDescription
X-RateLimit-Limit30Your plan's per-second limit
X-RateLimit-Remaining28Requests remaining in this window
X-RateLimit-Reset1710504000Unix timestamp when window resets
Retry-After2Seconds to wait (only on 429 responses)

Python Rate Limit Handling

Python
import requests
import time

class QantoClient:
    def __init__(self, api_key):
        self.api_key = api_key
        self.base_url = "https://api.qanto.com"
        self.session = requests.Session()
        self.session.headers["Authorization"] = f"Bearer {api_key}"

    def get(self, path, params=None, max_retries=3):
        url = f"{self.base_url}{path}"

        for attempt in range(max_retries):
            response = self.session.get(url, params=params)

            # Log rate limit status
            remaining = response.headers.get("X-RateLimit-Remaining")
            limit = response.headers.get("X-RateLimit-Limit")
            if remaining:
                print(f"Rate limit: {remaining}/{limit} remaining")

            if response.status_code == 200:
                return response.json()

            if response.status_code == 429:
                retry_after = int(response.headers.get("Retry-After", 2))
                print(f"Rate limited! Waiting {retry_after}s...")
                time.sleep(retry_after)
                continue

            if response.status_code >= 500:
                wait = 2 ** attempt
                print(f"Server error. Retrying in {wait}s...")
                time.sleep(wait)
                continue

            response.raise_for_status()

        raise Exception("Max retries exceeded")

# Usage
client = QantoClient("sk_live_YOUR_KEY")
weather = client.get("/v1/weather/london")

JavaScript Rate Limit Handling

JavaScript
class QantoClient {
  constructor(apiKey) {
    this.apiKey = apiKey;
    this.baseUrl = "https://api.qanto.com";
  }

  async get(path, params = {}, maxRetries = 3) {
    const url = new URL(`${this.baseUrl}${path}`);
    Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));

    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const response = await fetch(url, {
        headers: { Authorization: `Bearer ${this.apiKey}` },
      });

      // Log rate limit status
      const remaining = response.headers.get("X-RateLimit-Remaining");
      const limit = response.headers.get("X-RateLimit-Limit");
      if (remaining) console.log(`Rate limit: ${remaining}/${limit}`);

      if (response.ok) return response.json();

      if (response.status === 429) {
        const retryAfter = parseInt(response.headers.get("Retry-After") || "2");
        console.log(`Rate limited! Waiting ${retryAfter}s...`);
        await new Promise((r) => setTimeout(r, retryAfter * 1000));
        continue;
      }

      if (response.status >= 500) {
        const wait = Math.pow(2, attempt) * 1000;
        await new Promise((r) => setTimeout(r, wait));
        continue;
      }

      const { error } = await response.json();
      throw new Error(`${response.status}: ${error.message}`);
    }
    throw new Error("Max retries exceeded");
  }
}

// Usage
const client = new QantoClient("sk_live_YOUR_KEY");
const weather = await client.get("/v1/weather/london");

Best Practices

  • Cache responses — weather data is valid for 5-15 minutes (check cache_ttl_seconds in meta)
  • Use exponential backoff on 429 and 5xx errors, starting with the Retry-After value
  • Monitor the X-RateLimit-Remaining header to proactively slow down before hitting limits
  • Batch location requests where possible instead of making individual calls
  • Use webhooks for alerts instead of polling the alerts endpoint
  • Request only the fields you need using the fields parameter to reduce response size
  • Use the models parameter to request a specific model rather than all models
  • Set up monitoring to alert your team when you approach 80% of your monthly quota