Earthquake reports
Earthquake report data comes from reports published by the Central Weather Administration (CWA). Each report includes the epicenter, magnitude, and depth, plus the observed seismic intensity for each county or city and each station. The server checks the CWA for new reports every few seconds. Use it for an earthquake list, historical queries, and intensity maps.
- For a new report as soon as it is published, use the SSE stream
eq.report.v1. - The list (
/api/v2/eq/report) does not include intensity by area. When you need the intensity distribution, takeidfrom the list and then request the single report.
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 right now.
Query the earthquake report list
Section titled “Query the earthquake report list”Query earthquake reports by condition. The response is an array. By default it is sorted by origin time, newest first, one report per page, and includes only reports whose maximum intensity across Taiwan is 1 or higher (see Notes).
limitPage size, 1–150. A value outside the range is clamped to the nearest bound.
pagePage number, 1–10000.
sortSort field: time (origin time), intensity (maximum intensity across Taiwan), magnitude, or depth. Ties are broken by report id, newest first.
orderSort direction: asc or desc.
minIntensityLower bound on the maximum intensity across Taiwan, 0–9 (see Intensity scale). Pass minIntensity=0 to include intensity 0.
maxIntensityUpper bound on the maximum intensity across Taiwan, 0–9.
minMagnitudeLower bound on magnitude, 0–10.
maxMagnitudeUpper bound on magnitude, 0–10.
minDepthLower bound on depth (kilometers), 0–700.
maxDepthUpper bound on depth (kilometers), 0–700.
startTimeStart date YYYY-MM-DD (Taiwan time), inclusive of 00:00:00 that day. An invalid format is ignored.
endTimeEnd date YYYY-MM-DD (Taiwan time), inclusive of 23:59:59 that day. An invalid format is ignored.
cityCounty or city name, exact match, for example 花蓮縣 or 臺北市 (use 臺, not 台). Returns only reports that recorded an intensity in that county or city.
cityMinIntLower bound on county or city intensity. With city, it applies to that county or city. Alone, a report is included if any county or city matches.
cityMaxIntUpper bound on county or city intensity. Same rules as cityMinInt.
curl -L "https://api.core.exptech.dev/api/v2/eq/report"curl -L "https://api.core-tnn1.exptech.dev/api/v2/eq/report"curl -L "https://api.core-tyo1.exptech.dev/api/v2/eq/report"const res = await fetch("https://api.core.exptech.dev/api/v2/eq/report");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);const res = await fetch("https://api.core-tnn1.exptech.dev/api/v2/eq/report");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);const res = await fetch("https://api.core-tyo1.exptech.dev/api/v2/eq/report");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.exptech.dev/api/v2/eq/report", timeout=10)res.raise_for_status()print(res.json())import requests
res = requests.get("https://api.core-tnn1.exptech.dev/api/v2/eq/report", timeout=10)res.raise_for_status()print(res.json())import requests
res = requests.get("https://api.core-tyo1.exptech.dev/api/v2/eq/report", timeout=10)res.raise_for_status()print(res.json())Success (an empty array [] when nothing matches)
[ { "id": "115079-2026-1109-122157", "lat": 23.62, "lon": 120.66, "depth": 10.5, "loc": "雲林縣政府東南東方 23.2 公里 (位於雲林縣古坑鄉)", "mag": 4.2, "time": 1794198117000, "int": 3, "trem": 0, "md5": "3F2A9C1E7B5D4A608E1F2C3D4B5A6978" }]The parameters are not in canonical form. Redirects to the canonical URL
HTTP/1.1 302 FoundLocation: /api/v2/eq/report?limit=20&minIntensity=4If-None-Match matches the ETag. Empty bodyNo response body
Server error
{ "error": "<錯誤訊息>"}Try it
Response fields
Section titled “Response fields”The response is an array. Each element is one report:
| Field | Type | Description |
|---|---|---|
[].id | string | Report ID, format XXXXXX-YYYY-MMDD-HHMMSS. The first 3 digits are the ROC year, the next 3 are the CWA report number (000 for a small-area earthquake with no number), and the rest is the origin date and time (Taiwan time). Example: 115079-2026-1109-122157. |
[].lat | number | Epicenter latitude (degrees). |
[].lon | number | Epicenter longitude (degrees). |
[].depth | number | Focal depth (kilometers). |
[].loc | string | Epicenter description, kept as the CWA original text (it may contain consecutive spaces). |
[].mag | number | Richter magnitude. |
[].time | int | Origin time, Unix milliseconds. |
[].int | int | Maximum seismic intensity across Taiwan, 0–9 (see Intensity scale). |
[].trem | int | Reserved field. Currently always 0. |
[].md5 | string | Hash of the report contents, including intensity by area. 32 uppercase hex digits. It changes when the contents are updated, so you can use it to decide whether to fetch the single report again. |
Get one earthquake report
Section titled “Get one earthquake report”Fetch one report by id, including intensity for each county or city and each station.
idReport ID (path parameter), format XXXXXX-YYYY-MMDD-HHMMSS, for example 114000-2025-0829-100546. Query parameters are ignored.
curl "https://api.core.exptech.dev/api/v2/eq/report/<id>"curl "https://api.core-tnn1.exptech.dev/api/v2/eq/report/<id>"curl "https://api.core-tyo1.exptech.dev/api/v2/eq/report/<id>"const res = await fetch("https://api.core.exptech.dev/api/v2/eq/report/<id>");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);const res = await fetch("https://api.core-tnn1.exptech.dev/api/v2/eq/report/<id>");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);const res = await fetch("https://api.core-tyo1.exptech.dev/api/v2/eq/report/<id>");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.exptech.dev/api/v2/eq/report/<id>", timeout=10)res.raise_for_status()print(res.json())import requests
res = requests.get( "https://api.core-tnn1.exptech.dev/api/v2/eq/report/<id>", timeout=10,)res.raise_for_status()print(res.json())import requests
res = requests.get( "https://api.core-tyo1.exptech.dev/api/v2/eq/report/<id>", timeout=10,)res.raise_for_status()print(res.json())Success
{ "id": "114000-2025-0829-100546", "lat": 22.86, "lon": 121.28, "depth": 16.9, "loc": "臺東縣政府東北方 17.5 公里 (位於臺東縣近海)", "mag": 4, "time": 1756433146000, "list": { "臺東縣": { "int": 2, "town": { "東河": { "lat": 22.97, "lon": 121.3, "int": 2 }, "鹿野": { "lat": 22.91, "lon": 121.13, "int": 2 }, "成功": { "lat": 23.1, "lon": 121.37, "int": 1 } } }, "花蓮縣": { "int": 1, "town": { "富里": { "lat": 23.2, "lon": 121.29, "int": 1 } } } }, "trem": 0}If-None-Match matches the ETag. Empty bodyNo response body
The ID format is wrong, or there is no such report (no response body)No response body
Try it
Response fields
Section titled “Response fields”| Field | Type | Description |
|---|---|---|
id | string | Report ID. Same format as the list. |
lat | number | Epicenter latitude (degrees). |
lon | number | Epicenter longitude (degrees). |
depth | number | Focal depth (kilometers). |
loc | string | Epicenter description. |
mag | number | Richter magnitude. |
time | int | Origin time, Unix milliseconds. |
list | object | Seismic intensity by county or city. The key is the county or city name (for example 臺東縣). An empty object {} when there are no observations. |
<縣市>.int | int | Maximum seismic intensity in that county or city, 0–9. |
<縣市>.town | object | Seismic intensity at each station in that county or city. The key is the station name. |
<測站>.lat | number | Station latitude (degrees). |
<測站>.lon | number | Station longitude (degrees). |
<測站>.int | int | Station seismic intensity, 0–9. |
trem | int | Reserved field. Currently always 0. |
A single report does not include int (maximum intensity across Taiwan) or md5. For the maximum across Taiwan, take the maximum int among the counties and cities in list.
Intensity scale
Section titled “Intensity scale”int, minIntensity, cityMinInt, and the other intensity fields use the integers 0–9 for the Central Weather Administration (CWA) intensity scale:
| Value | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| Intensity | 0 | 1 | 2 | 3 | 4 | 5 lower | 5 upper | 6 lower | 6 upper | 7 |
Advanced example
Section titled “Advanced example”Query the list by condition, then fetch intensity by area for each id (the list does not include it). Build the query in canonical form (parameters in alphabetical order, defaults omitted) and you skip the extra 302:
# 20 most recent reports with maximum intensity 4 or highercurl -L "https://api.core.exptech.dev/api/v2/eq/report?limit=20&minIntensity=4"
# Every report on 2024-04-03 (Taiwan time), including intensity 0curl -L "https://api.core.exptech.dev/api/v2/eq/report?endTime=2024-04-03&limit=150&minIntensity=0&startTime=2024-04-03"const BASE = "https://api.core.exptech.dev";
// 1. List: the 20 most recent reports with maximum intensity 4 or higherconst params = new URLSearchParams({ limit: "20", minIntensity: "4" });const listRes = await fetch(`${BASE}/api/v2/eq/report?${params}`);if (!listRes.ok) throw new Error(`HTTP ${listRes.status}`);const reports = await listRes.json();
// 2. Single report: intensity for each county or city and each stationfor (const r of reports) { console.log(r.id, new Date(r.time).toISOString(), `M${r.mag}`, `max intensity ${r.int}`);
const res = await fetch(`${BASE}/api/v2/eq/report/${r.id}`); if (res.status === 404) continue; // no such report if (!res.ok) throw new Error(`HTTP ${res.status}`); const report = await res.json();
for (const [city, area] of Object.entries(report.list)) { for (const [station, s] of Object.entries(area.town)) { console.log(" ", city, area.int, station, s.int); } }}-
Intensity 0 is filtered out by default. The list’s
minIntensitydefaults to1. If you omit it, reports whose maximum intensity across Taiwan is 0 do not appear. PassminIntensity=0to list every report. -
A query that is not in canonical form returns 302 first. The list (v2 and v1) canonicalizes the query. If it differs from the canonical form, the response is
302to the canonical URL, and the data comes after that. Canonical form:- Parameters are sorted by name;
- Parameters equal to the default are removed (for example
page=1,sort=time,limit=1); - Unknown parameters are removed;
- Numbers are clamped to the bounds and trailing zeros are dropped (for example
limit=500becomeslimit=150, andminMagnitude=4.50becomesminMagnitude=4.5). A date in the wrong format is removed.
The browser
fetchfollows a 302 automatically.curlneeds-L. Building the URL by the rules above saves a round trip. A single report (/api/v2/eq/report/{id}) is not canonicalized and does not return 302. -
A bad parameter value does not return 400. An integer or decimal that cannot be parsed is treated as
0and then clamped (for exampleminIntensity=abcis the same asminIntensity=0). An unknownsortororderfalls back to the default. -
Cache and ETag. The response includes
ETag. The sameIf-None-Matchreturns304(no body). The server caches for 1 second.HEADis supported. -
Error responses. A server error returns
500with a body{"error": "..."}. The message is for debugging. Do not branch on its text. A single report that is missing, or an ID in the wrong format, returns404with no body. -
Time and coordinates.
timeis Unix milliseconds. The date parametersstartTimeandendTimeare read as Taiwan time (UTC+8). Epicenter coordinates are rounded to 2 decimal places. -
The list and the single report have different fields. The list has
intandmd5, and nolist. The single report haslist, and nointormd5.
Legacy v1
Section titled “Legacy v1”Returns the latest N reports (by origin time, newest first). Every report includes intensity for each county or city and each station. There is no filter on intensity, magnitude, or the other fields, and intensity 0 reports are not filtered out.
limitNumber of reports to return, 1–50. An invalid value is treated as 1. A value over 50 is treated as 50.
curl -L "https://api.core.exptech.dev/api/v1/eq/report"curl -L "https://api.core-tnn1.exptech.dev/api/v1/eq/report"curl -L "https://api.core-tyo1.exptech.dev/api/v1/eq/report"const res = await fetch("https://api.core.exptech.dev/api/v1/eq/report");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);const res = await fetch("https://api.core-tnn1.exptech.dev/api/v1/eq/report");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);const res = await fetch("https://api.core-tyo1.exptech.dev/api/v1/eq/report");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.exptech.dev/api/v1/eq/report", timeout=10)res.raise_for_status()print(res.json())import requests
res = requests.get("https://api.core-tnn1.exptech.dev/api/v1/eq/report", timeout=10)res.raise_for_status()print(res.json())import requests
res = requests.get("https://api.core-tyo1.exptech.dev/api/v1/eq/report", timeout=10)res.raise_for_status()print(res.json())Success (an empty array [] when there is no data)
[ { "id": "CWA-EQ114000-2025-0829-100546", "no": "2025082910054600000", "lat": 22.86, "lon": 121.28, "depth": 16.9, "loc": "臺東縣政府東北方 17.5 公里 (位於臺東縣近海)", "mag": 4, "time": 1756433146000, "list": { "臺東縣": { "int": 2, "town": { "東河": { "lat": 22.97, "lon": 121.3, "int": 2 } } } } }]The parameters are not in canonical form. Redirects to the canonical URL
HTTP/1.1 302 FoundLocation: /api/v1/eq/reportIf-None-Match matches the ETag. Empty bodyNo response body
Server error
{ "error": "<錯誤訊息>"}Try it
Differences from v2:
| Field | Type | Description |
|---|---|---|
[].id | string | Report ID. Compared with v2, it has a CWA-EQ prefix, for example CWA-EQ114000-2025-0829-100546. Remove the prefix to get the v2 ID. |
[].no | string | The CWA earthquake number. The first 14 digits are the origin time YYYYMMDDHHMMSS (Taiwan time). v2 does not have this field. |
[].list | object | Seismic intensity by county or city and by station. Same shape as list on a v2 single report. |
The other fields (lat, lon, depth, loc, mag, time) match v2. v1 has no int, trem, or md5. ?limit=1 equals the default, so it 302s to the URL with no query.
