Working with GeoShake Data in ObsPy: A Practical Guide
GeoShake publishes its seismic data openly under CC BY 4.0 — no account, no API key, no request form. If you use ObsPy, you can be reading ground motion from the network in about thirty seconds.
This guide shows exactly how, using real commands with their real output. It also tells you plainly what this data is not good for, because that matters more than the tutorial part.
What's available
| Service | Endpoint | What you get |
|---|---|---|
| FDSN station | https://api.geoshake.org/fdsnws/station/1/ |
Station metadata (network, code, location) |
| FDSN event | https://api.geoshake.org/fdsnws/event/1/ |
Detected event catalogue |
| SeedLink | seedlink.geoshake.org:18000 |
Live 1 Hz ground-motion stream |
| SAC download | api.geoshake.org/api/stations/…/sac |
Event waveform bursts (~110 Hz) |
Everything is anonymous and unauthenticated. The licence is CC BY 4.0 — use it commercially, use it in a paper, just credit the network.
Station metadata
from obspy.clients.fdsn import Client
client = Client("https://api.geoshake.org", timeout=40)
inventory = client.get_stations(network="GW", level="station")
print(inventory)
Real output:
Inventory created at 2026-08-04T21:03:40.034000Z
Created by: GeoShake
Sending institution: GeoShake (GeoShake Community Seismic Network)
Contains:
Networks (1):
GW
Stations (9):
GW.GEO274YU (GEO-274YU)
GW.GEO2TRS8 (GEO-2TRS8)
GW.GEOA73T7 (GEO-A73T7)
GW.GEOBH2JN (GEO-BH2JN)
GW.GEODQZFV (GEO-DQZFV)
GW.GEOFVYSA (GEO-FVYSA)
GW.GEOQA2HF (GEO-QA2HF)
GW.GEOT7ND5 (GEO-T7ND5)
GW.GEOT9ZBK (GEO-T9ZBK)
Channels (0):
Note Channels (0). That is not a bug — see What this data is not below.
Geographic filtering works as you'd expect:
inv = client.get_stations(
network="GW", minlatitude=40, maxlatitude=42, level="station"
)
print(len(inv[0]), "stations") # -> 6 stations
Event catalogue
catalog = client.get_events()
print(catalog)
4 Event(s) in Catalog:
2026-07-02T09:42:24.533000Z | +36.824, +30.257 | 4.7 MMI | automatic
2026-07-02T13:18:54.640000Z | +36.574, +31.007 | 2.5 MMI | automatic
2026-07-10T10:10:36.932000Z | +36.574, +31.057 | 2.1 MMI | automatic
2026-07-12T09:26:49.467000Z | +37.272, +31.105 | 4.5 MMI | automatic
Two things to read carefully here. The magnitude type is MMI — a network-derived shaking intensity class, not a moment magnitude. And every event is marked automatic: no seismologist has reviewed these. An event enters the catalogue when three or more stations trigger and pass a moveout test consistent with a single source propagating at roughly 6 km/s.
The catalogue is small because the network is young. It will grow.
Live stream over SeedLink
Each station publishes a 1 Hz activity envelope on the LNZ channel, continuously.
from obspy.clients.seedlink.basic_client import Client as SeedLinkClient
from obspy import UTCDateTime
sl = SeedLinkClient("seedlink.geoshake.org", port=18000, timeout=30)
print(sl.get_info(level="station"))
[('GW', 'BH2JN'), ('GW', 'FVYSA'), ('GW', 'QA2HF')]
Pulling the last three minutes:
end = UTCDateTime()
stream = sl.get_waveforms("GW", "BH2JN", "00", "LNZ", end - 180, end)
print(stream)
4 Trace(s) in Stream:
GW.BH2JN.00.LNZ | 2026-08-04T21:02:06Z - 2026-08-04T21:02:12Z | 1.0 Hz, 7 samples
GW.BH2JN.00.LNZ | 2026-08-04T21:02:14Z - 2026-08-04T21:03:11Z | 1.0 Hz, 58 samples
GW.BH2JN.00.LNZ | 2026-08-04T21:03:13Z - 2026-08-04T21:04:10Z | 1.0 Hz, 58 samples
GW.BH2JN.00.LNZ | 2026-08-04T21:04:12Z - 2026-08-04T21:05:06Z | 1.0 Hz, 58 samples
The gaps between traces are real. Stations are hosted on domestic internet connections, and a second or two goes missing when a packet does. Merge with stream.merge(method=1) if you need a continuous trace.
The same server works with jAmaSeis, which makes this usable in a classroom: point it at seedlink.geoshake.org:18000 and students watch a live station.
Event waveforms
Triggered events are stored as short high-rate bursts and served as SAC:
import urllib.request
from obspy import read
url = ("https://api.geoshake.org/api/stations/"
"GEO-BH2JN/waveforms/85/sac?comp=mag")
urllib.request.urlretrieve(url, "event.sac")
st = read("event.sac")
tr = st[0]
print(tr)
print(f"peak {abs(tr.data).max():.4f} m/s^2")
GW.BH2JN..MAG | 2026-07-15T19:30:52.279000Z - 2026-07-15T19:30:58.619796Z | 109.1 Hz, 693 samples
peak 0.2447 m/s^2
Station coordinates travel in the SAC header (stla, stlo), so ObsPy has what it needs for distance calculations. Available components are x, y, z and mag (vector magnitude). The window runs from roughly 2.3 s before the trigger to 4 s after — the pre-event history comes from a ring buffer on the device.
What this data is not
This section matters more than everything above it.
The sensors are not calibrated. GeoShake stations are consumer-grade MEMS accelerometers. No instrument response is published, which is why station metadata stops at station level and Channels comes back empty. Amplitudes are indicative, not metrologically traceable. If your analysis needs a known transfer function, this is the wrong dataset.
The network code is provisional. We publish under GW, and GW is not registered with the FDSN. We moved to it after discovering our previous code, GS, belongs to the US Geological Survey — a name collision that would have polluted any shared archive. Registration is planned once the network is large enough to be worth a permanent code. Until then, treat GW as a local identifier and don't merge it into a shared archive assuming permanence.
Detections are automatic and unreviewed. False positives exist. A truck, a slammed door and a small earthquake can all trigger a station; the multi-station association filters most of that, not all of it.
Coverage is uneven and young. Nine stations, most of them clustered. The earliest continuous recording starts 2026-07-11.
Two rough edges you'll hit. ObsPy prints a warning that our services don't advertise starttime, endtime, location (station) or mindepth, maxdepth, orderby (event). The station service accepts starttime without error but ignores it — you'll get every station back rather than a filtered set, with no complaint. Filter client-side for now. Also, SeedLink currently carries three stations while the FDSN station service lists nine; the newer stations are not streaming yet.
None of this makes the data useless. It makes it specific: dense, cheap, low-fidelity coverage that complements professional networks rather than competing with them.
Citation
If this data ends up in something you publish, please cite it:
We'd genuinely like to hear about it — get in touch.
The firmware is open too
If you want to know exactly how a detection is made rather than trusting the catalogue, the firmware is public under GPL-3.0, alongside the TypeScript reference implementation its signal processing is verified against. The device asserts that its C code matches that reference on every boot and refuses to start quietly if it doesn't.
That's the part we'd most like a seismologist to pick apart: github.com/GeoShake/geoshake
The full API documentation, including endpoints not covered here, lives at api.geoshake.org/developer.
To be clear about scope: the firmware, the enclosure designs and the data are open. The PCB design and the cloud backend are proprietary — so the device as a whole is not open source hardware, and we don't describe it that way.
Ready to join the network?
Get the GeoShake T1 sensor and start detecting earthquakes at home.