Earthquake Science Technology Guide

Working with GeoShake Data in ObsPy: A Practical Guide

6 min read By GeoShake Team
Seismic waveform on a dark monitor beside an out-of-focus sensor — reading GeoShake ground-motion data with ObsPy

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.


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:

DOI: 10.5281/zenodo.21313786

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.

Get GeoShake T1

Share this article: