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.

EndpointReturns
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:

EndpointQuery paramsExample
/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

EndpointReturns
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
A note on taxon identity. GBIF taxa with a positive 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.