API Reference
All endpoints below are under /api/. Upload endpoints require
a token; everything else (downloads, search, browsing) is public.
Authentication
Add this header to any upload request:
Authorization: Token <your-token-here>
Generate a token for your account with
uv run python manage.py drf_create_token <username>.
Uploading
# Gazetteer (GeoJSON)
curl -X POST http://127.0.0.1:8000/api/gazetteer/upload/ \
-H "Authorization: Token <token>" \
-F "file=@gazetteer.geojson"
# Location aliases (CSV)
curl -X POST http://127.0.0.1:8000/api/gazetteer/aliases/upload/ \
-H "Authorization: Token <token>" \
-F "file=@location_aliases.csv"
# Dataset (safedata_validator JSON export)
curl -X POST http://127.0.0.1:8000/api/datasets/upload/ \
-H "Authorization: Token <token>" \
-F "file=@dataset.json"
Re-uploading a dataset with a zenodo_record_id that already
exists updates that dataset in place (treated as a correction) rather
than creating a duplicate. Uploads are all-or-nothing: if any part of
a file fails, nothing from it is saved.
Downloading
No authentication required.
| Endpoint | Returns |
|---|---|
GET /api/gazetteer/download/ | Current gazetteer as GeoJSON |
GET /api/gazetteer/hash/ | MD5 hash of the current gazetteer file |
GET /api/gazetteer/aliases/download/ | Current aliases as CSV |
GET /api/gazetteer/aliases/hash/ | MD5 hash of the current aliases file |
GET /api/datasets/index/download/ | Index of all published datasets (latest version of each) |
GET /api/datasets/index/hash/ | MD5 hash of the current index |
Downstream tools should check the hash endpoint before re-downloading a file, to avoid unnecessary transfers.
Searching datasets
Six search endpoints, matching the query types supported by the
safedata R package.
All accept GET requests with query parameters, and all
support two shared parameters:
-
ids— restrict results to a specific set ofzenodo_record_ids (repeatable:?ids=1&ids=2). This is how searches can be chained together (an "AND"-like combination). -
most_recent— restrict results to only the latest version of each dataset (presence alone means true:?most_recent).
| Endpoint | Query params | Example |
|---|---|---|
/api/search/text/ | text | ?text=forest |
/api/search/authors/ | name | ?name=Ewers |
/api/search/dates/ | date, match_type (intersect/contain/within) | ?date=2014-06-01,2015-06-01&match_type=contain |
/api/search/fields/ | text, field_type | ?field_type=numeric |
/api/search/taxa/ | name, rank, taxon_id, source (gbif/sequence) | ?name=Formicidae |
/api/search/spatial/ | wkt or location, optional distance (metres) | ?location=A_1&distance=2500 |
Combining an "OR" across two searches is done by the client — issue
two searches and union the results; combining an "AND" is done by
restricting the second search's ids to the first
search's result set.
Record metadata and taxonomy
| Endpoint | Returns |
|---|---|
GET /api/records/<zenodo_record_id>/ | Full metadata for one dataset (authors, worksheets, fields, taxa, locations) |
GET /api/records/<zenodo_record_id>/taxa/ | That dataset's own taxon list, nested where possible. Source type (GBIF/sequence) is auto-detected. |
GET /api/concepts/<zenodo_concept_id>/ | Every published version of a dataset, with availability status (* = current, o = older, x = embargoed/restricted) |
GET /api/taxon_coverage/gbif/ | The complete curated GBIF taxonomic tree across every dataset's latest version |
GET /api/taxon_coverage/sequence/ | The sequence-derived taxonomic tree across every dataset's latest version, merged by matching taxonomic rank/name |
taxon_id are globally comparable — the same ID always
means the same real species, so these are deduplicated across
datasets. GBIF taxa with taxon_id: -1 represent
unmatched/"user"-supplied taxa (e.g. field morphospecies with no
confirmed species-level match) and are never
deduplicated, since -1 carries no identity at all —
they're only distinguishable by name. Sequence-derived taxa have no
stable ID of their own; they're merged across datasets by matching
their full ancestor name path instead.