Example 1: Local News Station
How a local news station could use the CalHeatScore API to display an area's CalHeatScores during weather forecasts.
Bringing CalHeatScore to Your Weather Forecast
Imagine you're a meteorologist at a local TV station and you're interested in integrating CalHeatScore forecasts via the API into your weather reporting.
The API is free, public, and requires no credentials, which means integrating CalHeatScore into your broadcast workflow is straightforward. You can query scores for every ZIP code in your viewing area, pull the 7-day forecast to align with your 7-day weather outlook, and color-code your broadcast map to match the CalHeatScore scale.
The workflow below walks through how to pull heat scores for your coverage area and prepare them for broadcast, from a single API call to a formatted dataset your graphics team can use.
Step-by-Step: Pull CalHeatScores for Your Coverage Area
Step 1: Identify your coverage ZIP codes
Determine the ZIP codes in your broadcast area. For example, a Fresno-area station might cover these ZIP codes: 93650, 93711, 93720, 93722, 93726, 93727, 93741.
Step 2: Query the API for your ZIP codes
Fetch the 7-day forecast for your coverage area in a single request:
curl "https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query?where=ZIP_CODE+IN+('93650','93711','93720','93722','93726','93727','93741')&outFields=ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2,CHS_Day_3,CHS_Day_4,CHS_Day_5,CHS_Day_6&returnGeometry=false&f=json"
import requests
BASE_URL = (
"https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/"
"CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query"
)
# Fresno-area ZIP codes
zip_codes = ["93650", "93711", "93720", "93722", "93726", "93727", "93741"]
where_clause = "ZIP_CODE IN (" + ",".join(f"'{z}'" for z in zip_codes) + ")"
params = {
"where": where_clause,
"outFields": "ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2,CHS_Day_3,CHS_Day_4,CHS_Day_5,CHS_Day_6",
"returnGeometry": "false",
"f": "json",
}
response = requests.get(BASE_URL, params=params)
data = response.json()
if "error" in data:
print(f"Error: {data['error']['message']}")
else:
for feature in data["features"]:
attrs = feature["attributes"]
scores = [attrs[f"CHS_Day_{d}"] for d in range(7)]
print(f"ZIP {attrs['ZIP_CODE']}: {' → '.join(scores)}")
const baseUrl =
"https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/" +
"CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query";
// Fresno-area ZIP codes
const zipCodes = ["93650", "93711", "93720", "93722", "93726", "93727", "93741"];
const whereClause = `ZIP_CODE IN (${zipCodes.map((z) => `'${z}'`).join(",")})`;
const params = new URLSearchParams({
where: whereClause,
outFields: "ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2,CHS_Day_3,CHS_Day_4,CHS_Day_5,CHS_Day_6",
returnGeometry: "false",
f: "json",
});
fetch(`${baseUrl}?${params}`)
.then((res) => res.json())
.then((data) => {
if (data.error) {
console.error(`Error: ${data.error.message}`);
return;
}
data.features.forEach((feature) => {
const a = feature.attributes;
const scores = Array.from({ length: 7 }, (_, d) => a[`CHS_Day_${d}`]).join(" → ");
console.log(`ZIP ${a.ZIP_CODE}: ${scores}`);
});
})
.catch((err) => console.error("Request failed:", err));
{
"features": [
{
"attributes": {
"ZIP_CODE": "93650",
"DATE": "2026-07-15",
"CHS_Day_0": "3",
"CHS_Day_1": "4",
"CHS_Day_2": "4",
"CHS_Day_3": "3",
"CHS_Day_4": "2",
"CHS_Day_5": "1",
"CHS_Day_6": "0"
}
},
{
"attributes": {
"ZIP_CODE": "93711",
"DATE": "2026-07-15",
"CHS_Day_0": "3",
"CHS_Day_1": "4",
"CHS_Day_2": "4",
"CHS_Day_3": "3",
"CHS_Day_4": "2",
"CHS_Day_5": "1",
"CHS_Day_6": "1"
}
}
]
}
Step 3: Map scores to labels and colors
Translate the 0–4 scores into language your viewers will understand:
| CalHeatScore | Label | HEX Color Code | Associated Meaning |
|---|---|---|---|
| 0 | Low | #DEDEDE | Little to no heat-related health impacts are expected. |
| 1 | Mild | #FAE0C8 | Warm day. A minor increase in heat-related health impacts is expected among those sensitive to heat. |
| 2 | Moderate | #EA8753 | Very warm day. A moderate increase in heat-related health impacts is expected among those sensitive to heat. |
| 3 | High | #C23A00 | Hot day. An increase in heat-related health impacts is expected for everyone, especially those without access to adequate cooling and hydration or those engaged in strenuous physical activity outdoors. This level of heat can pose a significant risk to health. |
| 4 | Severe | #700006 | Extreme heat day. A major increase in heat-related health impacts is expected for everyone, especially those without access to adequate cooling and hydration or those engaged in strenuous physical activity outdoors. This level of heat can pose a very serious risk to health. |
Step 4: Build your 7-day heat-health risk outlook
Align the CalHeatScore 7-day forecast with your weather outlook. Each CHS_Day_ field maps directly to your forecast days:
| API Field | Broadcast Day |
|---|---|
CHS_Day_0 |
Today |
CHS_Day_1 |
Tomorrow |
CHS_Day_2 |
Day 3 of your forecast |
CHS_Day_3 |
Day 4 of your forecast |
CHS_Day_4 |
Day 5 of your forecast |
CHS_Day_5 |
Day 6 of your forecast |
CHS_Day_6 |
Day 7 of your forecast |
Step 5: Automate daily updates
Since CalHeatScore data refreshes daily, set up a scheduled script to pull fresh scores each morning before your first broadcast:
import requests
import csv
from datetime import datetime
BASE_URL = (
"https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/"
"CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query"
)
# Your station's coverage ZIP codes
zip_codes = ["93650", "93711", "93720", "93722", "93726", "93727", "93741"]
where_clause = "ZIP_CODE IN (" + ",".join(f"'{z}'" for z in zip_codes) + ")"
params = {
"where": where_clause,
"outFields": "ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2,CHS_Day_3,CHS_Day_4,CHS_Day_5,CHS_Day_6",
"returnGeometry": "false",
"f": "json",
}
response = requests.get(BASE_URL, params=params)
data = response.json()
if "error" in data:
print(f"Error: {data['error']['message']}")
else:
# Export to CSV for your graphics team
filename = f"heat_scores_{datetime.now().strftime('%Y-%m-%d')}.csv"
with open(filename, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["ZIP Code", "Date", "Today", "Day 1", "Day 2", "Day 3", "Day 4", "Day 5", "Day 6"])
for feature in data["features"]:
a = feature["attributes"]
writer.writerow([
a["ZIP_CODE"], a["DATE"],
a["CHS_Day_0"], a["CHS_Day_1"], a["CHS_Day_2"],
a["CHS_Day_3"], a["CHS_Day_4"], a["CHS_Day_5"], a["CHS_Day_6"]
])
print(f"Saved {len(data['features'])} ZIP codes to {filename}")
const fs = require("fs");
const baseUrl =
"https://services1.arcgis.com/PCHfdHz4GlDNAhBb/arcgis/rest/services/" +
"CalHeatScore_Live_Data_for_API_Use/FeatureServer/0/query";
// Your station's coverage ZIP codes
const zipCodes = ["93650", "93711", "93720", "93722", "93726", "93727", "93741"];
const whereClause = `ZIP_CODE IN (${zipCodes.map((z) => `'${z}'`).join(",")})`;
const params = new URLSearchParams({
where: whereClause,
outFields: "ZIP_CODE,DATE,CHS_Day_0,CHS_Day_1,CHS_Day_2,CHS_Day_3,CHS_Day_4,CHS_Day_5,CHS_Day_6",
returnGeometry: "false",
f: "json",
});
fetch(`${baseUrl}?${params}`)
.then((res) => res.json())
.then((data) => {
if (data.error) {
console.error(`Error: ${data.error.message}`);
return;
}
// Export to CSV for your graphics team
const header = "ZIP Code,Date,Today,Day 1,Day 2,Day 3,Day 4,Day 5,Day 6";
const rows = data.features.map((f) => {
const a = f.attributes;
return [a.ZIP_CODE, a.DATE,
a.CHS_Day_0, a.CHS_Day_1, a.CHS_Day_2,
a.CHS_Day_3, a.CHS_Day_4, a.CHS_Day_5, a.CHS_Day_6
].join(",");
});
const today = new Date().toISOString().split("T")[0];
const filename = `heat_scores_${today}.csv`;
fs.writeFileSync(filename, [header, ...rows].join("\n"));
console.log(`Saved ${data.features.length} ZIP codes to ${filename}`);
})
.catch((err) => console.error("Request failed:", err));
Schedule this script to run each morning (e.g., via cron, Task Scheduler, or a CI pipeline) and deliver the CSV to your graphics team's shared drive. The data refreshes daily, so a single morning pull gives you accurate scores for the full broadcast day.