Build download links
Choose data criteria and generate a link to fetch a zip file of data. Open it and your browser downloads a zip file. This is the quickest route for most people, and everything else on this page is a variation on it.
- Type the three-letter code of an observatory, for example
eskfor Eskdalemuir,lerfor Lerwick orhadfor Hartland. Separate several codes with commas. - Choose the dates. The “up to” date is the boundary of the period, so use 1 January 2021 to get all of 2020.
- Open the link, then unzip the file. It contains your data files and a short summary of your request.
Other ways to get data
If a downloaded zip isn't what you need, other services are also available:
Browse and plot first The Observatory Data Portal lets you look at the data before you download anything. Automate it with curl or Python Repeat downloads for many dates or observatories, or feed the results into your own analysis. Read data straight into your code The text endpoints return CSV or JSON directly, with no zip to unpack. Connect HAPI-aware software Use our HAPI server if your tools already speak the Heliophysics API standard. Try requests in the interactive docs The full reference for every endpoint and option, where you can fill in a form and run a request.Query parameters
esk. Lower case is safest in links.second, minute, hour, day, month or year, plus k for the three-hourly local K-index.2020-01-01, optionally with a time (2020-01-01T00:00:00). All times are UTC. start_dt and end_dt mark the boundaries of the period, so 2020-01-01 to 2021-01-01 requests the year 2020.XYZF gives north (X), east (Y), vertical (Z) and total field (F). HDZF gives horizontal intensity (H), declination (D), vertical (Z) and total field (F).
original returns the data as they are stored. Yearly data use XYZ and HDZ.
Zipped downloads: full details
The link generator above writes links for zipped downloads, with the template for any download shown below. One request returns one zip file, and you can ask for several observatories at once. Zipped downloads are also the only route that offers the WDC format (minute and hourly means) and, for most cadences, IAGA-2002.
https://wdcapi.bgs.ac.uk/data/zipped-data/{cadence}/{observatories}?start_dt={start}&end_dt={end}&file_format={format}
Example - three observatories in WDC format
Hourly means for Eskdalemuir, Lerwick and Hartland for 2020:
https://wdcapi.bgs.ac.uk/data/zipped-data/hour/esk,ler,had?start_dt=2020-01-01&end_dt=2021-01-01&file_format=wdc&orientation=original
Download this example
Your browser may show the commas as %2C. They mean the same thing.
Particular months instead of dates
Minute, hourly and daily data can also be selected with years and months. Use one method or the other in a request, not both.
https://wdcapi.bgs.ac.uk/data/zipped-data/hour/esk?years=2020&months=1,2,3&file_format=csv
This requests January, February and March 2020. Leave months out to get whole years.
For several years, separate them with dashes and give one group of months per year,
for example years=2020-2021&months=1,2,3-1,10,11,12. Monthly and yearly cadences accept years only.
Which formats does each cadence offer?
Show all parameters
cadencesecond, minute, hour, day, month, year or k.observatoriesmonth and year you can enter ALL for every observatory.start_dt, end_dtyears, monthsfile_formatorientationoriginal (default), XYZF or HDZF; XYZ or HDZ for yearly data. Not offered for K-index or one-second data.group_byday (default, as in IAGA-2002) or month for one file per month.excludeALL.Large requests
The size of a single download is limited. If a request is too large the server replies with 413 Request Entity Too Large. The fix is to split the period into smaller pieces and download them one after another. For ten years of minute data from multiple observatories, one year at a time works well.
https://wdcapi.bgs.ac.uk/data/zipped-data/minute/esk,ler,had?start_dt=2010-01-01&end_dt=2011-01-01&file_format=wdc
https://wdcapi.bgs.ac.uk/data/zipped-data/minute/esk,ler,had?start_dt=2011-01-01&end_dt=2012-01-01&file_format=wdc
https://wdcapi.bgs.ac.uk/data/zipped-data/minute/esk,ler,had?start_dt=2012-01-01&end_dt=2013-01-01&file_format=wdc
Keep a note of what you requested and check that each request finished. A successful reply doesn't guarantee that every date holds observations, because coverage varies between observatories and periods. The API documentation has more details on limits; for example, minute means are limited to 10 total years of data per download. The scripts below show how to loop over years.
Check what data exist
Before a big download, you can see which months have data. The availability endpoint lists, for each observatory and year, the months we hold for a chosen cadence.
https://wdcapi.bgs.ac.uk/data/data-availability/hour?obs_code=esk&start_dt=2020-01-01&end_dt=2021-01-01&formatting=simple-json
Check Eskdalemuir for 2020
Swap hour for second, minute, day, month, year or k. obs_code takes one or more codes;
leave it out, along with the dates, and you get every observatory for all time,
The formatting option is simple-json (default) or nested-json.
The response describes availability only and contains no magnetic data.
A trimmed example of a response:
{
"bsl": {
"2021": { "months": [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12] },
"2025": { "months": [] }
}
}
An empty list means no data for that year. Here September 2021 is missing.
Text endpoints
These return data directly in the response as CSV, JSON or COV-JSON instead of a zip, so a program can read them immediately. They cover one observatory at a time, and split large requests into pages, and are not designed for bulk downloads across many observatories, but are useful for scraping large volumes of targetted data. They don't offer WDC, IAGA-2002 or XML; use zipped downloads for those.
https://wdcapi.bgs.ac.uk/data/text-data/cadence/observatory?start_dt=start&end_dt=end&file_format=format&months_per_page=n&page=p
Try one month of minute data
https://wdcapi.bgs.ac.uk/data/text-data/minute/esk?start_dt=2010-01-01&end_dt=2010-02-01&file_format=json&orientation=original&page=1&months_per_page=1
View in browser
How paging works
Every response includes page and total pages, plus a data list. Ask for ten years (2010-01-01 to 2020-01-01) with months_per_page=1 and total pages is 120, so you collect everything by requesting page=1, page=2, and so on up to page=120.
Always read total pages from the response rather than assuming it.
For day, hour, minute and K-index requests you must include months_per_page. The most each cadence allows:
Monthly and yearly requests don't use months_per_page. If there's nothing for your dates you get a 202 reply with an empty data list.
Python: collect every page into a table
import requests
import pandas as pd
url = "https://wdcapi.bgs.ac.uk/data/text-data/minute/esk"
params = {
"start_dt": "2010-01-01",
"end_dt": "2020-01-01",
"file_format": "json",
"orientation": "original",
"months_per_page": 1,
"page": 1,
}
rows = []
while True:
r = requests.get(url, params=params, timeout=120)
r.raise_for_status()
body = r.json()
rows.extend(body["data"])
if params["page"] >= body["total pages"]:
break
params["page"] += 1
df = pd.DataFrame(rows)
print(df.head())
What a response looks like
{
"iaga-code": "...",
"latitude": ...,
"longitude": ...,
"data-quality": "...",
"converted from": "...",
"page": 1,
"total pages": 120,
"data": [
{ "X": ..., "Y": ..., "Z": ..., "datetime": "..." }
]
}
The names of the data columns depend on the orientation you ask for.
One-second data
One-second data are available as JSON for a single observatory, with pages measured in days (days_per_page, 1 or 2, default 2):
https://wdcapi.bgs.ac.uk/data/text-data/second/kir?start_dt=2024-01-01&end_dt=2024-01-03&page=1
The “Larger requests via API” section of the FAQs on the World Data Centre data portal also has a ready-made scraping script.
Download with curl or Python
Scripts are the way to repeat downloads, loop over years, or fetch many observatories. Each example below saves a zip file.
curl is a command-line tool for web requests, available on most Linux and macOS systems and installable on Windows. Put the link in quotes so your shell treats it as one argument.
curl -fL -o esk-hourly-2020.zip "https://wdcapi.bgs.ac.uk/data/zipped-data/hour/esk?start_dt=2020-01-01&end_dt=2021-01-01&file_format=wdc&orientation=original"
-f reports HTTP errors as failures, -L follows redirects and -o names the file to save.
A 202 “no data” reply is not an error to curl, so if the saved file is empty, this is why.
This uses only Python's standard library.
import zipfile
from urllib.request import urlretrieve
url = (
"https://wdcapi.bgs.ac.uk/data/zipped-data/hour/esk"
"?start_dt=2020-01-01"
"&end_dt=2021-01-01"
"&file_format=wdc"
"&orientation=original"
)
filename = "esk-hourly-2020.zip"
urlretrieve(url, filename)
if zipfile.is_zipfile(filename):
print("Downloaded", filename)
else:
# No data for these criteria: the server sent a short message instead of a zip.
print(open(filename).read())
This needs the requests package (pip install requests) and fetches two years of minute data, one year per request.
import requests
from pathlib import Path
from zipfile import is_zipfile
from io import BytesIO
url = "https://wdcapi.bgs.ac.uk/data/zipped-data/minute/esk"
# Save files in a dedicated folder
output_dir = Path.cwd() / "esk_data"
output_dir.mkdir(parents=True, exist_ok=True)
print("Saving files to:", output_dir.resolve())
for year in range(2010, 2012):
try:
r = requests.get(
url,
params={"years": year, "file_format": "csv"},
timeout=600
)
print(f"\nYear: {year}")
print("Status:", r.status_code)
print("Content type:", r.headers.get("Content-Type"))
print("Downloaded bytes:", len(r.content))
if r.status_code == 200:
if is_zipfile(BytesIO(r.content)):
file_path = output_dir / f"esk_minute_{year}.zip"
file_path.write_bytes(r.content)
print("Saved:", file_path.resolve())
else:
print("Response is not a valid ZIP file.")
print(r.text)
elif r.status_code == 202:
print("Request accepted, but data is not yet available in this response.")
print(r.text)
else:
print("Request failed:", r.text)
except requests.RequestException as e:
print(f"Error downloading {year}: {e}")
For scripts you rely on, add retries and check that each zip opens before you use it.
HAPI server
We also have a HAPI server. Our server follows version 3.2 of the
Heliophysics API (HAPI) specification,
a standard way for software to discover time series, inspect their parameters and fetch data for a time range.
Tools that read HAPI servers, such as Autoplot or the hapiclient packages for Python, IDL and MATLAB, can be pointed at https://wdcapi.bgs.ac.uk/hapi.
/hapi/capabilitiescsv, json)./hapi/about/hapi/catalog/hapi/info/hapi/dataDataset names
Each dataset is named observatory/cadence/orientation, so this is Eskdalemuir minute means in XYZ:
esk/PT1M/xyz
Cadence
PT1M minute · PT1H hourly · P1D daily · P1M monthly · P1Y yearly · PT3H K-index
Orientation
xyz · hdz · original (as stored) · k (three-hourly K-index only)
Inspect a dataset
https://wdcapi.bgs.ac.uk/hapi/info?dataset=esk/PT1M/original
Other examples: esk/PT1M/xyz, esk/PT1M/hdz and esk/PT3H/k. Check the catalogue for the datasets that exist.
Fetch data
One day of Eskdalemuir minute data as CSV, then the same as JSON:
https://wdcapi.bgs.ac.uk/hapi/data?dataset=esk/PT1M/original&format=csv&start=2020-01-01T00:00:00Z&stop=2020-01-02T00:00:00Z
https://wdcapi.bgs.ac.uk/hapi/data?dataset=esk/PT1M/original&format=json&start=2020-01-01T00:00:00Z&stop=2020-01-02T00:00:00Z
Open the CSV example
format is csv (default) or json. Add include=header to receive the dataset description with the data, or parameters= to choose columns. Times are UTC and the stop time marks the end of the period.
Request limits
A single HAPI data request can cover at most:
The maxRequestDuration value in a dataset's /hapi/info response is the definitive limit for that dataset. For longer periods, make several requests.
Use it from Python
import pandas as pd
url = ("https://wdcapi.bgs.ac.uk/hapi/data"
"?dataset=esk/PT1M/xyz&format=csv"
"&start=2020-01-01T00:00:00Z&stop=2020-01-02T00:00:00Z")
df = pd.read_csv(url, header=None,
names=["time", "x", "y", "z", "original_format"],
parse_dates=["time"])
print(df.head())
# pip install hapiclient
from hapiclient import hapi
server = "https://wdcapi.bgs.ac.uk/hapi"
dataset = "esk/PT1M/xyz"
start, stop = "2020-01-01T00:00:00Z", "2020-01-02T00:00:00Z"
data, meta = hapi(server, dataset, "", start, stop) # "" means all parameters
Other data and metadata
The same API serves several other datasets. Each of these is a link you can paste into your browser.
Monthly means in BGS format
Our database of observatory monthly means, compiled from definitive hourly data and near-definitive minute data, joined into one series per observatory in a format designed after IAGA-2002.
https://wdcapi.bgs.ac.uk/monthly-means?obs_code=esk
Leave out obs_code to download every observatory (this may take a few minutes), or add include_plot=true for PNG plots comparing each observatory with field models.
curl -X 'GET' \
'https://wdcapi.bgs.ac.uk/monthly-means' \
-H 'accept: application/json'
There's also a browsable version on the Observatory Monthly Means page.
Annual means and jump information
https://wdcapi.bgs.ac.uk/data/annual-means-catalogue
https://wdcapi.bgs.ac.uk/data/annual-means-discontinuities?obs_code=esk
Jumps come from changes of site or instrument. Values are old minus new, and no corrections have been applied. To make earlier annual means consistent with later ones, for example when calculating secular variation, subtract the jump from the earlier values.
Global geomagnetic survey data
https://wdcapi.bgs.ac.uk/global-geomagnetic-survey?start_dt=2020-01-01&end_dt=2021-01-01&file_format=csv
Formats are json, csv and geo-json. Without dates you get the last five years. Limit the area with north-latitude, south-latitude, west-longitude and east-longitude, and choose measurement types with include-one-off, include-repeat-station, include-observatory, include-aeromag, include-marine-vector, include-marine-scalar and include-satellite. By default only one-off and repeat-station results are included.
Observatory metadata and locations
https://wdcapi.bgs.ac.uk/metadata/observatory-metadata/esk
Instruments, locations, institutes and contacts for one observatory. Leave off the code to get every observatory, which is a much larger response.
https://wdcapi.bgs.ac.uk/metadata/location?observatory=esk
Latitude, longitude, elevation and opening and closing dates, including past moves.
GeoJSON for maps
https://wdcapi.bgs.ac.uk/metadata/wdc-geojson/
Observatory locations for mapping software. Note that this version lists coordinates as [latitude, longitude], whereas the EPOS version (/metadata/epos-geojson/) uses [longitude, latitude].
Versions that add IGRF-14 field values (/metadata/wdc-geojson-igrf) or quasi-dipole coordinates (/metadata/wdc-geojson-qd-coords) need a datetime parameter.
Yearbooks
https://wdcapi.bgs.ac.uk/metadata/yearbooks?observatory=esk
Links to digitised yearbooks. INTERMAGNET only version is also available at /metadata/intermagnet-yearbooks.
If something goes wrong
The server tells you what happened with a status code.
Common questions
Which format should I choose?
What's the difference between zipped/text endpoints and the HAPI server?
Can I download several observatories at once?
esk,ler,had. For monthly and yearly means you can enter ALL. Text endpoints and HAPI are one at a time.Where can I find every parameter?
Still stuck? Email wdcgeomag@bgs.ac.uk and include the link you used.
Acknowledging the data
Observatory data are the work of the teams that run each observatory. If you use them in a publication, please acknowledge them. Ready-made acknowledgement text and contact details for any observatory are available at:
https://wdcapi.bgs.ac.uk/acknowledgements/esk
See Eskdalemuir example