DEV Community

Julien
Julien

Posted on

Building an Environmental Risk Dashboard: How to Access US Hazard Data via REST API

If you've ever tried to build an app that accounts for natural disaster risk — whether it's a real estate platform, a relocation tool, or an insurance estimator — you know how hard it is to find clean, standardized environmental risk data. Most hazard data sources are buried in government PDFs, locked behind expensive subscriptions, or require you to stitch together a dozen different datasets. That's why I was excited to discover a free environmental risk API that serves up standardized hazard data for every US zip code, city, county, and state through a dead-simple REST endpoint.

In this tutorial, I'll walk you through how to query the API, parse the results, and build something actually useful with it.

What Data Is Available?

The API returns risk scores on a 0–100 scale across 12 environmental hazard categories:

Hazard Category What It Measures
Earthquake Seismic activity risk
Flood Flood zone exposure
Wildfire Fire risk based on vegetation, climate, topology
Tornado Tornado frequency and severity
Hurricane Hurricane/storm surge exposure
Air Quality Long-term air pollution levels
Heat Extreme heat event frequency
Winter Weather Blizzard/ice storm risk
Landslide Terrain-based landslide susceptibility
Volcanic Activity Proximity to active volcanic zones
Drought Long-term drought probability

Scores are normalized to a 0–100 range, where higher values mean greater risk. This makes it trivially easy to compare locations side-by-side or build color-coded visualizations.

Getting Started: Your First API Call

The base endpoint is:

https://environmental-hazards-api-425658670453.europe-west1.run.app/api/v3/hazards
Enter fullscreen mode Exit fullscreen mode

You query it by passing location parameters. No API key required for basic usage. Let's start with the simplest case — looking up risk scores by zip code.

Example: Get Risk Scores for a Zip Code (cURL)

curl -s "https://environmental-hazards-api-425658670453.europe-west1.run.app/api/v3/hazards?zip=33101" | python3 -m json.tool
Enter fullscreen mode Exit fullscreen mode

This returns a JSON object with risk scores for Miami's 33101 zip code. You'll get something like:

{
  "location": {
    "zip": "33101",
    "city": "Miami",
    "state": "FL",
    "county": "Miami-Dade County"
  },
  "risk_scores": {
    "earthquake": 12,
    "flood": 78,
    "wildfire": 15,
    "tornado": 35,
    "hurricane": 92,
    "air_quality": 42,
    "heat": 88,
    "winter_weather": 3,
    "landslide": 8,
    "volcanic_activity": 0,
    "drought": 45
  }
}
Enter fullscreen mode Exit fullscreen mode

Right away you can see — Miami has a hurricane risk of 92 and heat risk of 88, but essentially zero volcanic activity. Makes sense.

Querying by City and State

If you don't have a zip code handy, you can query by city and state code:

curl -s "https://environmental-hazards-api-425658670453.europe-west1.run.app/api/v3/hazards?city=los_angeles&state_code=ca"
Enter fullscreen mode Exit fullscreen mode

The API also supports county-level and state-level lookups. For a full state summary:

curl -s "https://environmental-hazards-api-425658670453.europe-west1.run.app/api/v3/hazards?state_code=CA&level=state"
Enter fullscreen mode Exit fullscreen mode

This is useful when you want aggregate risk profiles at a higher level — say, comparing California to Florida.

Python Example: Batch Risk Comparison

Here's where things get practical. Let's say you're helping a user compare environmental risk across multiple zip codes — maybe they're deciding where to buy a house or relocate.

import requests
import pandas as pd

BASE_URL = "https://environmental-hazards-api-425658670453.europe-west1.run.app/api/v3/hazards"

def get_risk_scores(zip_code: str) -> dict:
    """Fetch environmental risk scores for a given zip code."""
    response = requests.get(BASE_URL, params={"zip": zip_code})
    response.raise_for_status()
    data = response.json()
    return {
        "zip": zip_code,
        "city": data["location"]["city"],
        "state": data["location"]["state"],
        **data["risk_scores"]
    }

# Zip codes to compare (from different regions of the US)
zip_codes = ["33101", "90210", "10001", "77001", "98101"]

# Fetch data for all locations
results = [get_risk_scores(zc) for zc in zip_codes]

# Build a DataFrame for easy comparison
df = pd.DataFrame(results)

# Show only the risk columns, sorted by hurricane risk
risk_cols = ["earthquake", "flood", "wildfire", "tornado", "hurricane",
             "air_quality", "heat", "winter_weather", "landslide",
             "volcanic_activity", "drought"]

print(df[["zip", "city", "state"] + risk_cols].sort_values("hurricane", ascending=False).to_string(index=False))
Enter fullscreen mode Exit fullscreen mode

This script pulls risk data for five zip codes across the country and builds a comparison table. You could easily extend this to loop through hundreds of zip codes, pipe the results into a CSV, or feed them into a visualization library.

Finding the Highest Risk

Want to quickly identify which hazard is the biggest threat for a given location?

def get_top_risks(zip_code: str, count: int = 3) -> list[dict]:
    """Return the top N highest risk categories for a zip code."""
    response = requests.get(BASE_URL, params={"zip": zip_code})
    response.raise_for_status()
    scores = response.json()["risk_scores"]

    sorted_risks = sorted(scores.items(), key=lambda x: x[1], reverse=True)
    return [{"hazard": hazard, "score": score} for hazard, score in sorted_risks[:count]]

top_risks = get_top_risks("33101")
for risk in top_risks:
    print(f"  {risk['hazard']}: {risk['score']}/100")
Enter fullscreen mode Exit fullscreen mode

JavaScript Example: Frontend Risk Widget

If you're building a web app, here's how to fetch and display risk data using the Fetch API. This pattern works great in React, Vue, Svelte, or vanilla JS.

const API_BASE = "https://environmental-hazards-api-425658670453.europe-west1.run.app/api/v3/hazards";

/**
 * Fetch environmental risk scores for a zip code
 * and render a color-coded risk bar for each category.
 */
async function renderRiskWidget(zipCode, containerId) {
  const container = document.getElementById(containerId);
  container.innerHTML = "<p>Loading risk data...</p>";

  try {
    const response = await fetch(`${API_BASE}?zip=${zipCode}`);
    if (!response.ok) throw new Error(`API returned ${response.status}`);

    const data = await response.json();
    const { location, risk_scores } = data;

    // Build the widget
    let html = `<h3>Environmental Risk: ${location.city}, ${location.state} (${location.zip})</h3>`;

    for (const [hazard, score] of Object.entries(risk_scores)) {
      const color = score >= 70 ? "#e74c3c" : score >= 40 ? "#f39c12" : "#27ae60";
      const label = hazard.replace(/_/g, " ").replace(/\b\w/g, c => c.toUpperCase());

      html += `
        <div style="margin-bottom: 8px;">
          <div style="display: flex; justify-content: space-between; margin-bottom: 2px;">
            <span>${label}</span>
            <strong>${score}/100</strong>
          </div>
          <div style="background: #eee; border-radius: 4px; height: 10px;">
            <div style="background: ${color}; width: ${score}%; height: 10px; border-radius: 4px;"></div>
          </div>
        </div>`;
    }

    container.innerHTML = html;
  } catch (err) {
    container.innerHTML = `<p style="color: red;">Error: ${err.message}</p>`;
  }
}

// Usage: renderRiskWidget("90210", "risk-widget");
Enter fullscreen mode Exit fullscreen mode

Drop this into any HTML page with a <div id="risk-widget"></div> and you've got an instant visual risk profile. The color coding (red/orange/green based on severity) makes it immediately scannable.

Comparing Two Locations Side-by-Side

One of the more useful features is the comparison endpoint. Let's say someone is deciding between Los Angeles and New York:

curl -s "https://environmental-hazards-api-425658670453.europe-west1.run.app/api/v3/hazards?zip=90210&compare_zip=10001"
Enter fullscreen mode Exit fullscreen mode

This returns risk data for both locations in a single response, making it easy to build comparison tables or dual-bar charts. In a real app, you'd parse this and render something like:

# Python: Parse comparison data and find the biggest differences
import requests

response = requests.get(BASE_URL, params={"zip": "90210", "compare_zip": "10001"})
data = response.json()

loc_a = data["locations"][0]  # Beverly Hills
loc_b = data["locations"][1]  # New York

print(f"Comparing {loc_a['location']['city']} vs {loc_b['location']['city']}\n")

for hazard in loc_a["risk_scores"]:
    score_a = loc_a["risk_scores"][hazard]
    score_b = loc_b["risk_scores"][hazard]
    diff = score_a - score_b
    direction = "" if diff > 0 else ""
    print(f"  {hazard:20s}: {score_a:3d} vs {score_b:3d}  ({direction} {abs(diff):2d})")
Enter fullscreen mode Exit fullscreen mode

The comparison output immediately highlights where the real differences are — Los Angeles has higher earthquake and wildfire risk, while New York has higher winter weather risk. This kind of side-by-side view is incredibly useful for relocation tools.

Practical Project: Environmental Risk Email Alerts

Here's a more complete project idea — a script that checks risk scores for a list of zip codes and sends an email alert when any location crosses a danger threshold. This could be useful for property managers, insurance agents, or just cautious homeowners.

import requests
import smtplib
from email.mime.text import MIMEText
from typing import List, Dict

API_BASE = "https://environmental-hazards-api-425658670453.europe-west1.run.app/api/v3/hazards"
THRESHOLD = 75  # Alert when any risk score exceeds this value

def check_risks(zip_codes: List[str]) -> List[Dict]:
    """Check all zip codes and return any with high-risk scores."""
    alerts = []

    for zc in zip_codes:
        try:
            resp = requests.get(API_BASE, params={"zip": zc})
            resp.raise_for_status()
            data = resp.json()

            for hazard, score in data["risk_scores"].items():
                if score >= THRESHOLD:
                    alerts.append({
                        "zip": zc,
                        "city": data["location"]["city"],
                        "state": data["location"]["state"],
                        "hazard": hazard,
                        "score": score,
                    })
        except requests.RequestException as e:
            print(f"Error fetching {zc}: {e}")

    return alerts

def send_alert_email(alerts: List[Dict], recipient: str) -> None:
    """Send a summary email of all high-risk alerts."""
    if not alerts:
        print("No alerts to send.")
        return

    body_lines = ["⚠️ Environmental Risk Alert Report ⚠️\n"]
    for alert in alerts:
        body_lines.append(
            f"- {alert['city']}, {alert['state']} ({alert['zip']}): "
            f"{alert['hazard']} risk = {alert['score']}/100"
        )

    msg = MIMEText("\n".join(body_lines))
    msg["Subject"] = f"Environmental Risk Alert: {len(alerts)} high-risk items"
    msg["From"] = "alerts@example.com"
    msg["To"] = recipient

    # Configure your SMTP server here
    # with smtplib.SMTP("smtp.example.com", 587) as server:
    #     server.starttls()
    #     server.login("user", "password")
    #     server.send_message(msg)

    print(f"Would send {len(alerts)} alerts to {recipient}")

# Run the check
monitored_zips = ["33101", "90210", "77001", "94102", "02101"]
alerts = check_risks(monitored_zips)
send_alert_email(alerts, "user@example.com")
Enter fullscreen mode Exit fullscreen mode

Wire this up to a cron job or a serverless function (AWS Lambda, Cloudflare Workers, etc.) and you've got automated environmental monitoring. You could extend it to track changes over time, push to Slack instead of email, or generate weekly summary reports.

A Few Things to Keep in Mind

Rate limiting: The API is free, which means it has reasonable rate limits. If you're batching thousands of requests, add a small delay between calls (time.sleep(0.5) in Python). For production use, check the RapidAPI listing for higher rate limits and guaranteed uptime.

Caching: Risk scores don't change every day. Cache responses locally (Redis, SQLite, even a JSON file) if you're building something that queries the same locations repeatedly. This speeds up your app and reduces API load.

Score interpretation: A score of 50 doesn't mean "moderate danger" in absolute terms — it means the location is at the 50th percentile relative to other US locations for that hazard type. Always present scores as comparative rather than absolute.

What Could You Build?

Here are a few ideas beyond what we covered:

  • Real estate listing enhancer: Automatically display risk badges on property listings
  • Relocation decision tool: Let users input their priorities (e.g., "I care most about flood and wildfire risk") and rank cities accordingly
  • Insurance premium estimator: Factor environmental risk into rough premium calculations
  • Interactive US risk map: Fetch scores for thousands of zip codes and render a choropleth map with D3.js or Mapbox
  • School safety dashboard: Show environmental risk profiles for school districts

The data comes from environmental hazard data API, which aggregates and standardizes hazard data from multiple federal and state sources into a single, developer-friendly endpoint. The platform covers every US zip code, city, county, and state — and it's free to use.

Wrapping Up

Environmental risk data has traditionally been one of those things that's technically public but practically inaccessible. Having it available through a clean REST API with consistent scoring makes a huge difference.

Here's what we covered:

  1. Querying by zip code, city, or state using simple GET requests
  2. Comparing two locations with the built-in comparison endpoint
  3. Building a visual risk widget in vanilla JavaScript
  4. Batch processing in Python with pandas for analysis
  5. Automated alerting with a threshold-based email script

All the code in this article is ready to copy, paste, and run. The API returns consistent JSON with no authentication needed for basic usage, so you can literally start building in under five minutes.

If you build something with this data, I'd love to hear about it — drop a comment below.


Tags: python api tutorial datascience

Top comments (0)