Tropical cyclone tracking and intensity data. Spire aggregates data from the NHC (NOAA/NWS), CPHC, and JTWC to provide global coverage of tropical cyclone activity, including historical tracks dating back to 1990.
Overview¶
The forecasts are issued by the responsible government centres following their own procedures, so the notion of spatial resolution does not apply. Active storms are typically updated every 6 hours, more often as a cyclone approaches landfall, and the cadence varies by forecasting centre. Each storm carries an observed track (past positions) and, while active, a forecast track from the latest advisory; the ATCF identifier ties the two together. Only the two- or three-letter category codes appear in the responses; their meaning is given on this page.
Endpoints¶
| Method | Endpoint | Description |
|---|---|---|
| GET | /storm/json | Get storm data as JSON |
| GET | /storm/kmz | Get storm data as KMZ |
Storm JSON¶
GET /storm/jsonRetrieve tropical cyclone track and intensity information as JSON. Storms are identified by their ATCF ID.
Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
status | string | No | active (default), inactive, or any |
id | string | No | ATCF ID (e.g., AL022024) |
name | string | No | Storm name (case insensitive, does not include category) |
category | string | No | Minimum category reached (returns storms at this level or higher) |
intensity | number | No | Minimum sustained wind speed, in knots, reached at any point along the track |
region | string | No | Basin or sub-basin code |
period | string | No | Date range as two comma-separated dates, YYYY-MM-DD,YYYY-MM-DD (e.g., 2023-01-01,2023-07-15). Any storm with a position inside the range is returned with its full track. |
data_filter | string | No | all (default), analysis, or forecast |
limit | integer | No | Max storms to return (1–100, default: 100) |
offset | integer | No | Number of storms to skip (for pagination) |
ATCF ID Format¶
Storm IDs follow the format BBXXYYYY:
BB— 2-character ATCF basin code (AL, EP, WP, CP, IO, SH)XX— 2-digit annual cyclone number (1–99, not reused until the following season)YYYY— 4-digit year the storm formed
Example: AL022024 = Atlantic basin, 2nd storm of 2024
Storm Categories¶
Categories are listed in order of increasing intensity. When filtering by category, all storms that reached that level or higher are returned.
| Code | Description |
|---|---|
XX | Unknown |
DS | Dissipating |
IN | Inland |
PT | Post-Tropical Cyclone |
LO | Low Pressure Center |
SS | Subtropical Storm |
TD | Tropical Depression |
TS | Tropical Storm |
TC1 | Category 1 Hurricane / Typhoon |
TC2 | Category 2 Hurricane / Typhoon |
TC3 | Category 3 Hurricane / Typhoon |
TC4 | Category 4 Hurricane / Typhoon |
TC5 | Category 5 Hurricane / Super Typhoon |
Basins¶
| Code | Name |
|---|---|
NA | North Atlantic |
SA | South Atlantic |
EP | Eastern North Pacific |
WP | Western North Pacific |
NI | North Indian |
SI | South Indian |
SP | Southern Pacific |
Sub-Basins¶
| Code | Name |
|---|---|
CS | Caribbean Sea |
GM | Gulf of Mexico |
MM | Missing (no sub-basin assigned) |
CP | Central Pacific |
AS | Arabian Sea |
BB | Bay of Bengal |
WA | Western Australia |
EA | Eastern Australia |
Example Requests¶
Get active storms:
curl -X GET \
'https://api.wx.spire.com/storm/json?status=active' \
-H 'spire-api-key: YOUR_API_KEY'import requests
response = requests.get(
"https://api.wx.spire.com/storm/json",
params={"status": "active"},
headers={"spire-api-key": "YOUR_API_KEY"},
)
data = response.json()const response = await fetch(
"https://api.wx.spire.com/storm/json?status=active",
{ headers: { "spire-api-key": "YOUR_API_KEY" } }
);
const data = await response.json();Get a specific storm by name:
curl -X GET \
'https://api.wx.spire.com/storm/json?name=Beryl&status=any' \
-H 'spire-api-key: YOUR_API_KEY'import requests
response = requests.get(
"https://api.wx.spire.com/storm/json",
params={"name": "Beryl", "status": "any"},
headers={"spire-api-key": "YOUR_API_KEY"},
)
data = response.json()const response = await fetch(
"https://api.wx.spire.com/storm/json?name=Beryl&status=any",
{ headers: { "spire-api-key": "YOUR_API_KEY" } }
);
const data = await response.json();Get storms in a time period:
curl -X GET \
'https://api.wx.spire.com/storm/json?period=2024-06-01,2024-11-30&status=inactive' \
-H 'spire-api-key: YOUR_API_KEY'import requests
response = requests.get(
"https://api.wx.spire.com/storm/json",
params={
"period": "2024-06-01,2024-11-30",
"status": "inactive",
},
headers={"spire-api-key": "YOUR_API_KEY"},
)
data = response.json()const params = new URLSearchParams({
period: "2024-06-01,2024-11-30",
status: "inactive",
});
const response = await fetch(
`https://api.wx.spire.com/storm/json?${params}`,
{ headers: { "spire-api-key": "YOUR_API_KEY" } }
);
const data = await response.json();Get major hurricanes (Category 3+):
curl -X GET \
'https://api.wx.spire.com/storm/json?category=TC3®ion=NA&status=any' \
-H 'spire-api-key: YOUR_API_KEY'import requests
response = requests.get(
"https://api.wx.spire.com/storm/json",
params={"category": "TC3", "region": "NA", "status": "any"},
headers={"spire-api-key": "YOUR_API_KEY"},
)
data = response.json()const response = await fetch(
"https://api.wx.spire.com/storm/json?category=TC3®ion=NA&status=any",
{ headers: { "spire-api-key": "YOUR_API_KEY" } }
);
const data = await response.json();Response¶
The response is an array of storm objects, each with separate observed_track and forecast_track arrays:
[
{
"atcf_id": "WP052023",
"storm_name": "Doksuri",
"basin": "WP",
"subbasin": "MM",
"season": 2023,
"is_active": true,
"advisory_number": 18,
"advisory_issuance_time": "2023-07-25T18:00:00+00:00",
"max_development_category": "TC4",
"max_sustained_wind": 128.0,
"min_mslp": 926.0,
"dates_active": {
"start": "2023-07-17",
"end": "ongoing"
},
"observed_track": [
{
"lat": 7.1,
"lon": 136.2,
"category_code": "XX",
"basin": "WP",
"subbasin": "MM",
"wind_gusts": null,
"max_sustained_wind": 20.0,
"min_mslp": 1004.0,
"movement_direction": null,
"movement_speed": null,
"wind_extent_radii": {},
"time": "2023-07-17T00:00:00+00:00"
}
],
"forecast_track": [
{
"lat": 19.6,
"lon": 120.6,
"category_code": "TC3",
"basin": "WP",
"subbasin": "MM",
"wind_gusts": 135.0,
"max_sustained_wind": 110.0,
"min_mslp": null,
"movement_direction": 315.0,
"movement_speed": 6.0,
"wind_extent_radii": {
"34kts": {
"wind_speed": 34.0,
"ne": 190.0,
"se": 210.0,
"nw": 180.0,
"sw": 150.0
},
"50kts": {
"wind_speed": 50.0,
"ne": 80.0,
"se": 80.0,
"nw": 70.0,
"sw": 90.0
},
"64kts": {
"wind_speed": 64.0,
"ne": 40.0,
"se": 40.0,
"nw": 30.0,
"sw": 30.0
}
},
"forecast_hour": 12,
"forecast_time": "2023-07-26T06:00:00+00:00"
}
]
}
]Response Fields — Storm Summary¶
| Field | Type | Description |
|---|---|---|
atcf_id | string | ATCF ID storm identifier |
storm_name | string | Storm name (empty until named) |
basin | string | Ocean basin code |
subbasin | string | Sub-basin code |
season | integer | Year of the storm season |
is_active | boolean | Whether storm is currently active |
advisory_number | integer | Advisory number (null for inactive storms) |
advisory_issuance_time | string | Advisory issuance time (null for inactive storms) |
max_development_category | string | Highest category reached over observed track |
max_sustained_wind | number | Maximum sustained wind (knots) over observed track |
min_mslp | number | Minimum sea-level pressure (hPa) over observed track |
dates_active | object | Start/end dates for all positions |
Response Fields — Track Positions¶
| Field | Type | Description |
|---|---|---|
lat | number | Latitude of storm center |
lon | number | Longitude of storm center |
category_code | string | Storm category at this position |
basin | string | Basin at this position |
subbasin | string | Sub-basin at this position |
wind_gusts | number | Maximum wind gusts (knots) |
max_sustained_wind | number | Maximum sustained wind (knots) |
min_mslp | number | Minimum sea-level pressure (hPa) |
movement_direction | number | Direction storm is moving toward (degrees from N) |
movement_speed | number | Speed of storm movement (knots) |
wind_extent_radii | object | Wind Radii for 34, 50, 64 kt thresholds (NE/SE/NW/SW quadrants, nautical miles) |
time | string | Valid time (observed track only) |
forecast_time | string | Valid time (forecast track only) |
forecast_hour | integer | Hours from advisory issuance (forecast track only) |
Storm KMZ¶
GET /storm/kmzRetrieve storm data as KMZ file for visualization in Google Earth Pro.
Parameters¶
Same parameters as /storm/json.
Example Request¶
curl -OJ -X GET \
'https://api.wx.spire.com/storm/kmz?name=Beryl&status=any' \
-H 'spire-api-key: YOUR_API_KEY'import requests
response = requests.get(
"https://api.wx.spire.com/storm/kmz",
params={"name": "Beryl", "status": "any"},
headers={"spire-api-key": "YOUR_API_KEY"},
)
with open("Beryl.kmz", "wb") as f:
f.write(response.content)import { writeFile } from "fs/promises";
const response = await fetch(
"https://api.wx.spire.com/storm/kmz?name=Beryl&status=any",
{ headers: { "spire-api-key": "YOUR_API_KEY" } }
);
const buffer = Buffer.from(await response.arrayBuffer());
await writeFile("Beryl.kmz", buffer);Python Example¶
Track active storms and monitor intensity:
import requests
headers = {'spire-api-key': 'YOUR_API_KEY'}
# Get all active storms
response = requests.get(
'https://api.wx.spire.com/storm/json',
headers=headers,
params={'status': 'active'}
)
storms = response.json()
for storm in storms:
print(f"\n{storm['storm_name']} ({storm['atcf_id']})")
print(f" Basin: {storm['basin']}")
print(f" Category: {storm['max_development_category']}")
print(f" Max Wind: {storm['max_sustained_wind']} kt")
print(f" Min Pressure: {storm['min_mslp']} hPa")
print(f" Active: {storm['is_active']}")
# Get latest observed position
if storm['observed_track']:
latest = storm['observed_track'][-1]
print(f" Latest Position: {latest['lat']:.1f}N, {latest['lon']:.1f}E")
print(f" As of: {latest['time']}")
# Show forecast positions
if storm.get('forecast_track'):
print(f" Forecast positions: {len(storm['forecast_track'])}")
for pos in storm['forecast_track'][:3]:
print(f" +{pos['forecast_hour']}h: {pos['lat']:.1f}N, {pos['lon']:.1f}E "
f"({pos['category_code']}, {pos['max_sustained_wind']} kt)")Plot Storm Tracks¶
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cfeature
# Get historical storms for Atlantic 2024 season
response = requests.get(
'https://api.wx.spire.com/storm/json',
headers=headers,
params={
'region': 'NA',
'period': '2024-06-01,2024-11-30',
'status': 'inactive'
}
)
storms = response.json()
fig, ax = plt.subplots(
figsize=(12, 8),
subplot_kw={'projection': ccrs.PlateCarree()}
)
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.COASTLINE)
ax.set_extent([-100, -10, 5, 50])
for storm in storms:
# Plot observed track
obs = storm.get('observed_track', [])
if obs:
lats = [p['lat'] for p in obs]
lons = [p['lon'] for p in obs]
ax.plot(lons, lats, label=storm['storm_name'], transform=ccrs.PlateCarree())
ax.legend(loc='upper left')
plt.title('Atlantic Tropical Cyclones 2024')
plt.show()