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.
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.Put it in an environment variable
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.
Look up an address
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"// 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# 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-11The last line of each example shows what it prints for this address. Set a
User-Agentthat names your app, as the Python example does.Or look up a point
If you already have coordinates, send
latandlonin WGS84 decimal degrees instead ofaddress. It skips geocoding and is the better choice when you have a parcel centroid or rooftop point.curl -sG https://api.floodzoneapi.com/api/v1/flood/zone \ -H "Authorization: Bearer $FZA_KEY" \ -d lat=29.9511 -d lon=-90.0715Read the parts that matter
flood_zone.mappedisfalsewhere FEMA has no digital map. Treat that as "unknown", not as low risk.flood_zone.sfhais the yes or no most applications need.flood_zone.static_bfeisnullunless FEMA publishes a single elevation for the zone. Checkdatumbefore comparing it with other elevations.firm_panel.effective_datetells you how current the map is at that point.location.precisiontells 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.
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) and503 upstream_unavailable(retry afterRetry-Afterseconds; 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.