Typhoons
These endpoints collect four typhoon products issued by the Central Weather Administration (CWA): track (track), potential track (potential), wind-radius strike probability (probability), and typhoon warnings (warning). There is also an index of active tropical cyclones and GeoJSON for map overlays. The server checks the source once a minute. Historical snapshots are kept for 7 days. Use them for a typhoon page, a track map, and warning notices. The shared format is in the Weather API.
Data license
Section titled “Data license”AttributionData: Central Weather Administration (CWA), ExpTech Studio (exptech.dev)
- Central Weather Administration (CWA): CWA's own data terms (such as the Open Government Data License) also apply; CWA's official releases take precedence.
Where to show it, examples and commercial-use requests: Data licensing and attribution.
Authentication
Section titled “Authentication”No API Token is required.
Shared rules
Section titled “Shared rules”- Every endpoint returns
{ updated, cyclones: [...] }, except the GeoJSON endpoint. When several tropical cyclones are active,cycloneshas several entries. tdNois the unique id of a tropical cyclone (CWA’s tropical-depression number). It is unique inside one snapshot. The four products match each other bytdNo. An unnamed tropical depression still has atdNo. In that casename,cwaName, andtyNoare"".- Times are Unix seconds. When there is no data,
updatedis0. - A missing value is
null. - Coordinates: the index, track, and warnings use two fields,
latandlon. Potential track, strike probability, and GeoJSON use a[longitude, latitude]array. Values are to 4 decimal places. - Units: wind speed and gust in m/s; pressure in hPa; movement speed in km/h; radius in km. Movement direction is a 16-point compass abbreviation, for example
NNW. - When no tropical cyclone is active,
cycloneson the index,track,potential, andprobabilityis[].warningusually keeps the latest cancellation (msgTypeisCancel).
The four products
Section titled “The four products”kind |
Contents | Use |
|---|---|---|
track |
Typhoon track: past and current fixes, current motion, quadrant wind radii, and forecast fixes at each lead time | The most complete numbers. Use the four quadrant radii here to judge the wind radius |
potential |
Potential-track forecast: past track, forecast track, potential-track area (cone of uncertainty), the radius of Beaufort force 7 winds, and each forecast time | Draw a potential-track map |
probability |
Wind-radius strike probability: nested contours at 20%, 40%, 60%, 80%, and 100% | Draw a probability map, or see which probability region a coordinate falls in |
warning |
Typhoon warning: issue, update, or cancellation, with the warning text, typhoon information, and affected counties and cities | Show the current sea or land typhoon warning |
Active tropical cyclones
Section titled “Active tropical cyclones”One latest fix and intensity per tropical cyclone, taken from track.
curl "https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon"const res = await fetch("https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);import requests
res = requests.get( "https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon", timeout=10,)res.raise_for_status()print(res.json())Success
{ "updated": 1794614400, "cyclones": [ { "tdNo": "27", "name": "DOLPHIN", "cwaName": "白海豚", "year": 2026, "tyNo": "26", "t": 1794614400, "lat": 18.3, "lon": 128.9, "wind": 33, "gust": 43, "pres": 970, "speed": 22, "dir": "WNW" } ]}Not modified (If-None-Match matched). No bodyNo response body
Try it
Response fields
Section titled “Response fields”| Field | Type | Description |
|---|---|---|
updated | number | Time of the latest fix, Unix seconds. 0 when there is no data |
cyclones[].tdNo | string | Tropical-depression number. The unique id |
cyclones[].name | string | International English name. An empty string when unnamed |
cyclones[].cwaName | string | Chinese name. An empty string when unnamed |
cyclones[].year | number | Year |
cyclones[].tyNo | string | Typhoon number. An empty string while it is still a tropical depression |
cyclones[].t | number | Time of this fix, Unix seconds |
cyclones[].lat | number | Center latitude |
cyclones[].lon | number | Center longitude |
cyclones[].wind | number | null | Maximum sustained wind near the center (m/s) |
cyclones[].gust | number | null | Maximum gust (m/s) |
cyclones[].pres | number | null | Central pressure (hPa) |
cyclones[].speed | number | null | Movement speed (km/h) |
cyclones[].dir | string | null | Movement direction, a 16-point compass abbreviation |
Typhoon data
Section titled “Typhoon data”The latest copy of one of the four products, selected by kind. The structure of each is in the sections below.
kindPath parameter. track, potential, probability, or warning
curl "https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon/<kind>"const res = await fetch("https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon/<kind>");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);import requests
res = requests.get( "https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon/<kind>", timeout=10,)res.raise_for_status()print(res.json())Success (track as the example)
{ "updated": 1794614400, "cyclones": [ { "tdNo": "27", "name": "DOLPHIN", "cwaName": "白海豚", "year": 2026, "tyNo": "26", "analysis": [ { "t": 1794528000, "lat": 16.9, "lon": 131.2, "wind": 28, "gust": 38, "pres": 980 }, { "t": 1794614400, "lat": 18.3, "lon": 128.9, "wind": 33, "gust": 43, "pres": 970 } ], "now": { "speed": 22, "dir": "WNW", "move": [ "以每小時22公里速度,向西北西進行", "WNW 22 KM/HR" ], "c15": { "avg": 180, "ne": 200, "se": 180, "sw": 160, "nw": 180 }, "c25": null }, "forecast": [ { "tau": 6, "t": 1794636000, "lat": 18.8, "lon": 127.9, "wind": 35, "gust": 45, "pres": 965, "speed": 20, "dir": "WNW", "r15": 180, "r70": 80, "state": null } ] } ]}Not modified (If-None-Match matched). No bodyNo response body
Try it
| Field | Type | Description |
|---|---|---|
updated | number | Time of the latest fix, Unix seconds. 0 when there is no data |
cyclones[].tdNo、name、cwaName、year、tyNo | string / number | Same as Active tropical cyclones |
cyclones[].analysis[] | object[] | Past and current fixes, oldest first |
t | number | Fix time, Unix seconds |
lat、lon | number | Center coordinates |
wind、gust、pres | number | null | Maximum sustained wind near the center, maximum gust (m/s), and central pressure (hPa) |
cyclones[].now.speed | number | null | Current movement speed (km/h) |
cyclones[].now.dir | string | null | Current movement direction |
cyclones[].now.move | string[] | null | Movement description [Chinese, English] |
cyclones[].now.c15 | object | null | Radius of Beaufort force 7 winds (km): avg is the average, and ne, se, sw, nw are the four quadrants. null when the storm is too weak |
cyclones[].now.c25 | object | null | Radius of Beaufort force 10 winds (km). Same shape as c15 |
cyclones[].forecast[].tau | number | Forecast lead time (hours) |
cyclones[].forecast[].t | number | Forecast time, Unix seconds |
cyclones[].forecast[].lat、lon | number | Forecast center coordinates |
cyclones[].forecast[].wind、gust、pres | number | null | Forecast intensity |
cyclones[].forecast[].speed、dir | number | null / string | null | Forecast movement speed (km/h) and direction |
cyclones[].forecast[].r15 | number | null | Radius of Beaufort force 7 winds (km) |
cyclones[].forecast[].r70 | number | null | 70% probability radius (km) |
cyclones[].forecast[].state | string[] | null | Weakening or transition description [Chinese, English] |
To decide whether a place is inside the wind radius, use the four quadrant radii on now.c15 / now.c25. The wind circle on potential is a symmetric circle, suitable only as a sketch.
Potential track
Section titled “Potential track”{ "updated": 1794614400, "cyclones": [ { "tdNo": "27", "name": "白海豚", "past": [[131.2, 16.9], [128.9, 18.3]], "forecast": [[128.9, 18.3], [127.9, 18.8], [125.6, 19.9]], "cone": [[128.4, 18.0], [127.3, 18.4], [124.8, 19.3], [126.1, 20.6], [128.9, 18.3], [128.4, 18.0]], "circle": [[130.6, 18.3], [128.9, 20.0], [127.2, 18.3], [128.9, 16.6], [130.6, 18.3]], "current": [128.9, 18.3], "points": [ { "label": "11月14日14時", "lng": 127.9, "lat": 18.8 }, { "label": "11月15日08時", "lng": 125.6, "lat": 19.9 } ] } ]}| Field | Type | Description |
|---|---|---|
updated | number | Data time, Unix seconds. 0 when there is no data |
cyclones[].tdNo | string | Tropical-depression number. An empty string when it cannot be matched |
cyclones[].name | string | Name from the source data, for example the Chinese name or TD27 |
cyclones[].past | number[][] | Past track [[longitude, latitude], ...] |
cyclones[].forecast | number[][] | Forecast track |
cyclones[].cone | number[][] | Outline of the potential-track area (cone of uncertainty) |
cyclones[].circle | number[][] | null | Radius of Beaufort force 7 winds (a symmetric circle, for illustration). null when the storm is weaker |
cyclones[].current | number[] | null | Current center [longitude, latitude] |
cyclones[].points[] | object[] | Each forecast time: label is the time text, plus lng and lat |
Strike probability
Section titled “Strike probability”{ "updated": 1794614400, "cyclones": [ { "tdNo": "27", "levels": [ { "p": 100, "coords": [[128.6, 18.0], [129.2, 18.4], [128.9, 18.9], [128.6, 18.0]] }, { "p": 20, "coords": [[126.0, 17.2], [130.4, 17.6], [129.8, 21.0], [125.1, 20.5], [126.0, 17.2]] } ] } ]}| Field | Type | Description |
|---|---|---|
updated | number | Data time, Unix seconds. 0 when there is no data |
cyclones[].tdNo | string | Tropical-depression number |
cyclones[].levels[].p | number | Strike probability (%): 20, 40, 60, 80, 100 |
cyclones[].levels[].coords | number[][] | Closed contour for that probability [[longitude, latitude], ...] |
levels is ordered from highest probability to lowest (100 is the innermost ring). To see which probability a coordinate is in, test point-in-polygon starting from the contour with the highest p.
Typhoon warning
Section titled “Typhoon warning”CWA usually has one typhoon warning at a time, so cyclones has length 0 or 1.
{ "updated": 1794615000, "cyclones": [ { "tdNo": "27", "active": true, "id": "CWA-Weather_typhoon-warning_202611140830000000", "sent": 1794615000, "status": "Actual", "msgType": "Cancel", "scope": "Public", "event": "颱風", "urgency": "Past", "severity": "Minor", "certainty": "Observed", "effective": 1794615000, "onset": 1794615000, "expires": 1794618600, "headline": "解除颱風警報", "senderName": "中央氣象署", "typhoon": { "no": "26", "name": "DOLPHIN", "cwaName": "白海豚", "reportNo": "12", "category": "END", "analysis": { "t": 1794614400, "lat": 18.3, "lon": 128.9, "wind": 33, "gust": 43, "pres": 970, "r15": 180, "scale": ["中度颱風", "TYPHOON"] }, "prediction": { "t": 1794700800, "lat": 20.1, "lon": 125.0, "wind": 30, "gust": 40, "pres": 975, "r15": 160, "scale": null } }, "sections": [ { "title": "命名與位置", "text": "中度颱風白海豚(國際命名 DOLPHIN)……" } ], "areas": [ { "name": "臺北市", "code": "63000000" } ] } ]}| Field | Type | Description |
|---|---|---|
updated | number | Issue time of the latest warning, Unix seconds. 0 when there is no data |
cyclones[].tdNo | string | Tropical-depression number. An empty string when it cannot be matched to a current tropical cyclone (common for an old cancellation) |
cyclones[].active | boolean | Whether the warning body was parsed |
cyclones[].id | string | Warning id |
cyclones[].sent | number | null | Issue time, Unix seconds |
cyclones[].status | string | For example Actual |
cyclones[].msgType | string | Alert means issued, Update means updated, Cancel means cancelled |
cyclones[].scope、event、urgency、severity、certainty | string | Scope, event, urgency, severity, and certainty of the warning, kept as the source text |
cyclones[].effective、onset、expires | number | null | Effective, onset, and expiry times, Unix seconds |
cyclones[].headline | string | Headline, for example a sea typhoon warning or a cancellation |
cyclones[].senderName | string | Issuing office |
cyclones[].typhoon.no | string | Typhoon number (the same as tyNo, which is separate from tdNo) |
cyclones[].typhoon.name、cwaName | string | English name and Chinese name |
cyclones[].typhoon.reportNo | string | Warning report number |
cyclones[].typhoon.category | string | Warning category, kept as the source text. For example END on a cancellation |
cyclones[].typhoon.analysis | object | null | Current fix: t, lat, lon, wind, gust, pres, r15 (radius of Beaufort force 7 winds, km), scale (intensity [Chinese, English]) |
cyclones[].typhoon.prediction | object | null | Forecast fix. Same structure as analysis |
cyclones[].sections[] | object[] | Sections of the warning text: title and text |
cyclones[].areas[] | object[] | Affected counties and cities: name, and code for the county or city |
Historical times
Section titled “Historical times”Historical snapshot times for one product, as delta-encoded Unix seconds, oldest first. See Delta-encoded time lists for how to decode them.
kindPath parameter. track, potential, probability, or warning
curl "https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon/<kind>/list"const res = await fetch("https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon/<kind>/list");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);import requests
res = requests.get( "https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon/<kind>/list", timeout=10,)res.raise_for_status()print(res.json())Success
[ 1794528000, 21600, 21600, 21600, 21600]Not modified (If-None-Match matched). No bodyNo response body
Try it
Response fields
Section titled “Response fields”| Field | Type | Description |
|---|---|---|
[0] | number | Time of the oldest snapshot, in Unix seconds |
[i](i ≥ 1) | number | Gap from the previous time, in seconds |
The snapshot time is the time of the data itself: the latest fix time for track, the source data time for potential and probability, and the warning issue time (sent) for warning. When there are no snapshots, the response is [].
Typhoon data at a time
Section titled “Typhoon data at a time”The snapshot of one product at a chosen time. The structure matches the latest data for that product. It is served only on static.core. api.core returns 404 for this path.
kindPath parameter. track, potential, probability, or warning
timePath parameter. A 10-digit Unix timestamp in seconds. It must be a value that appeared in /{kind}/list
curl "https://static.core-tnn1.exptech.dev/api/v5/meteor/typhoon/<kind>/<time>"const res = await fetch("https://static.core-tnn1.exptech.dev/api/v5/meteor/typhoon/<kind>/<time>");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);import requests
res = requests.get( "https://static.core-tnn1.exptech.dev/api/v5/meteor/typhoon/<kind>/<time>", timeout=10,)res.raise_for_status()print(res.json())Success (probability as the example)
{ "updated": 1794614400, "cyclones": [ { "tdNo": "27", "levels": [ { "p": 100, "coords": [ [ 128.6, 18 ], [ 129.2, 18.4 ], [ 128.9, 18.9 ], [ 128.6, 18 ] ] } ] } ]}Not modified (If-None-Match matched). No bodyNo response body
time is not an integer, or there is no snapshot at that time (including past the retention window). No bodyNo response body
Try it
Response fields
Section titled “Response fields”Same fields as the matching kind under Typhoon data.
GeoJSON overlay
Section titled “GeoJSON overlay”A GeoJSON FeatureCollection built from the latest potential and probability. Add it directly to MapLibre, Leaflet, and other maps.
curl "https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon/geojson"const res = await fetch("https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon/geojson");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);import requests
res = requests.get( "https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon/geojson", timeout=10,)res.raise_for_status()print(res.json())Success
{ "type": "FeatureCollection", "features": [ { "type": "Feature", "geometry": { "type": "LineString", "coordinates": [ [ 128.9, 18.3 ], [ 127.9, 18.8 ], [ 125.6, 19.9 ] ] }, "properties": { "kind": "forecast", "tdNo": "27" } }, { "type": "Feature", "geometry": { "type": "Point", "coordinates": [ 127.9, 18.8 ] }, "properties": { "kind": "forecastPoint", "label": "11月14日14時", "tdNo": "27" } }, { "type": "Feature", "geometry": { "type": "Polygon", "coordinates": [ [ [ 128.6, 18 ], [ 129.2, 18.4 ], [ 128.9, 18.9 ], [ 128.6, 18 ] ] ] }, "properties": { "kind": "probability", "p": 100, "tdNo": "27" } } ]}Not modified (If-None-Match matched). No bodyNo response body
Try it
Response fields
Section titled “Response fields”properties.kind on each feature says what it is. Every feature also has properties.tdNo.
properties.kind |
Geometry | Contents |
|---|---|---|
past |
LineString | Past track |
forecast |
LineString | Forecast track |
cone |
Polygon | Potential-track area |
circle15 |
Polygon | Radius of Beaufort force 7 winds (a symmetric circle) |
current |
Point | Current center |
forecastPoint |
Point | A forecast time. properties.label is the time text |
probability |
Polygon | Strike-probability contour. properties.p is the probability (%) |
When there is no typhoon, features is [].
Advanced examples
Section titled “Advanced examples”Use the GeoJSON overlay directly as a MapLibre GL JS source, then filter on kind for the shapes to draw, for example the potential-track area:
map.addSource("typhoon", { type: "geojson", data: "https://api.core-tnn1.exptech.dev/api/v5/meteor/typhoon/geojson",});map.addLayer({ id: "typhoon-cone", type: "fill", source: "typhoon", filter: ["==", ["get", "kind"], "cone"], paint: { "fill-color": "#f97316", "fill-opacity": 0.2 },});- Every time is Unix seconds. A missing value is
null. - Historical snapshots are kept for 7 days, and each product keeps at least its latest entry, so the last copy is still available after typhoon season ends (usually a cancellation).
tdNoonpotential,probability, andwarningis matched totrackby the server, using the name, number, or position. When it cannot be matched, the value is an empty string.- Source: typhoon data in CWA open data.
- Which server to call is in the Weather API. Timestamp snapshots go to
static.core. Everything else goes toapi.core. Use the TNN-1 URLs directly.
