Flood Zone API Get a free key

Quickstart: your first flood zone lookup

Five minutes from sign-up to a zone in your terminal. You need an email address and curl, Node 18 or Python 3.

  1. Get a key

    Sign up for the free plan (100 calls a month, no card). Click the link in the confirmation email, then choose Create a key on your account page. The key starts with fza_ and is shown once, so store it somewhere safe.

  2. Put it in an environment variable

    Shell
    export FZA_KEY="fza_paste_your_key_here"

    Keep keys on your server. A key in browser JavaScript can be copied by anyone who opens the page and will use up your quota.

  3. Look up an address

    curl
    curl -sG https://api.floodzoneapi.com/api/v1/flood/zone \
      -H "Authorization: Bearer $FZA_KEY" \
      --data-urlencode "address=1700 Convention Center Dr, Miami Beach, FL 33139"
    JavaScript (Node 18+)
    // lookup.mjs (Node 18 or later): node lookup.mjs
    const url = new URL('https://api.floodzoneapi.com/api/v1/flood/zone');
    url.searchParams.set('address', '1700 Convention Center Dr, Miami Beach, FL 33139');
    
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${process.env.FZA_KEY}` },
    });
    const body = await res.json();
    if (!res.ok) {
      throw new Error(`${res.status} ${body.error}: ${body.message}`);
    }
    
    const { zone, sfha, static_bfe } = body.flood_zone;
    console.log(zone, sfha, static_bfe?.elevation, body.firm_panel?.effective_date);
    // AE true 8 2009-09-11
    Python 3
    # lookup.py (Python 3.8 or later, standard library only): python3 lookup.py
    import json
    import os
    import urllib.error
    import urllib.parse
    import urllib.request
    
    query = urllib.parse.urlencode({"address": "1700 Convention Center Dr, Miami Beach, FL 33139"})
    request = urllib.request.Request(
        "https://api.floodzoneapi.com/api/v1/flood/zone?" + query,
        headers={
            "Authorization": "Bearer " + os.environ["FZA_KEY"],
            "User-Agent": "my-app/1.0",
        },
    )
    try:
        with urllib.request.urlopen(request, timeout=30) as response:
            body = json.load(response)
    except urllib.error.HTTPError as err:
        body = json.load(err)
        raise SystemExit(f"{err.code} {body['error']}: {body['message']}")
    
    zone = body["flood_zone"]
    panel = body["firm_panel"] or {}
    print(zone["zone"], zone["sfha"], (zone["static_bfe"] or {}).get("elevation"), panel.get("effective_date"))
    # AE True 8.0 2009-09-11

    The last line of each example shows what it prints for this address. Set a User-Agent that names your app, as the Python example does.

  4. Or look up a point

    If you already have coordinates, send lat and lon in WGS84 decimal degrees instead of address. It skips geocoding and is the better choice when you have a parcel centroid or rooftop point.

    curl
    curl -sG https://api.floodzoneapi.com/api/v1/flood/zone \
      -H "Authorization: Bearer $FZA_KEY" \
      -d lat=29.9511 -d lon=-90.0715
  5. Read the parts that matter

    • flood_zone.mapped is false where FEMA has no digital map. Treat that as "unknown", not as low risk.
    • flood_zone.sfha is the yes or no most applications need.
    • flood_zone.static_bfe is null unless FEMA publishes a single elevation for the zone. Check datum before comparing it with other elevations.
    • firm_panel.effective_date tells you how current the map is at that point.
    • location.precision tells you how the address was placed. Census matches are interpolated along the street, so a point near a zone line can land on the wrong side; send your own coordinates when precision matters.
  6. Handle the errors that will happen

    Plan for three: 404 address_not_found (show the user a message, or geocode yourself), 429 (quota or rate limit) and 503 upstream_unavailable (retry after Retry-After seconds; not counted). All error codes.

Watch your usage from the response headers: X-Quota-Limit and X-Quota-Remaining come back on every metered call.