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=siConversion Formulas
| Measurement | Metric | Imperial | Formula |
|---|---|---|---|
| Temperature | °C | °F | °F = °C × 9/5 + 32 |
| Wind speed | m/s | mph | mph = m/s × 2.237 |
| Precipitation | mm | inches | in = mm × 0.03937 |
| Visibility | meters | miles | mi = m × 0.000621 |
| Pressure | hPa | inHg | inHg = hPa × 0.02953 |
| Elevation | meters | feet | ft = m × 3.281 |
Field Name Changes
When switching units, field names change to reflect the unit. This prevents accidentally mixing unit systems:
| Metric Field | Imperial Field |
|---|---|
temperature_c | temperature_f |
feels_like_c | feels_like_f |
dew_point_c | dew_point_f |
wind_speed_ms | wind_speed_mph |
wind_gust_ms | wind_gust_mph |
precipitation_mm | precipitation_in |
precipitation_sum_mm | precipitation_sum_in |
visibility_m | visibility_mi |
pressure_hpa | pressure_inHg |
elevation_m | elevation_ft |
Common Reference Points
| Condition | Metric | Imperial |
|---|---|---|
| Freezing point | 0°C | 32°F |
| Room temperature | 20°C | 68°F |
| Body temperature | 37°C | 98.6°F |
| Light breeze | 3 m/s | 6.7 mph |
| Strong wind | 15 m/s | 33.6 mph |
| Hurricane force | 33+ m/s | 74+ mph |
| Light rain | 0.5 mm/hr | 0.02 in/hr |
| Heavy rain | 7.5 mm/hr | 0.30 in/hr |
| Standard pressure | 1013.25 hPa | 29.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:
| Code | Description | Category |
|---|---|---|
| 0 | Clear sky | Clear |
| 1 | Mainly clear | Clear |
| 2 | Partly cloudy | Cloudy |
| 3 | Overcast | Cloudy |
| 45 | Fog | Fog |
| 48 | Depositing rime fog | Fog |
| 51 | Light drizzle | Drizzle |
| 53 | Moderate drizzle | Drizzle |
| 55 | Dense drizzle | Drizzle |
| 56 | Light freezing drizzle | Drizzle |
| 57 | Dense freezing drizzle | Drizzle |
| 61 | Slight rain | Rain |
| 63 | Moderate rain | Rain |
| 65 | Heavy rain | Rain |
| 66 | Light freezing rain | Rain |
| 67 | Heavy freezing rain | Rain |
| 71 | Slight snowfall | Snow |
| 73 | Moderate snowfall | Snow |
| 75 | Heavy snowfall | Snow |
| 77 | Snow grains | Snow |
| 80 | Slight rain showers | Showers |
| 81 | Moderate rain showers | Showers |
| 82 | Violent rain showers | Showers |
| 85 | Slight snow showers | Showers |
| 86 | Heavy snow showers | Showers |
| 95 | Thunderstorm | Thunderstorm |
| 96 | Thunderstorm with slight hail | Thunderstorm |
| 99 | Thunderstorm with heavy hail | Thunderstorm |
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
| Plan | Requests/Second | Requests/Month | Burst Limit |
|---|---|---|---|
| Free | 2 | 10,000 | 5 req |
| Starter ($29) | 10 | 100,000 | 20 req |
| Pro ($99) | 30 | 500,000 | 50 req |
| Business ($299) | 100 | 2,000,000 | 200 req |
| Enterprise | Custom | Unlimited | Custom |
Rate Limit Headers
Every API response includes rate limit information in the headers:
| Header | Example | Description |
|---|---|---|
X-RateLimit-Limit | 30 | Your plan's per-second limit |
X-RateLimit-Remaining | 28 | Requests remaining in this window |
X-RateLimit-Reset | 1710504000 | Unix timestamp when window resets |
Retry-After | 2 | Seconds 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