Skip to content
datastreamappPublic

About

DataStream Public API Documentation

Topics

Resources

Stars

14 stars

Watchers

10 watching

Forks

Latest commit

 

History

111 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 

Repository files navigation

DataStream Logo

DataStream Public API

About

The DataStream Public API provides read-only, programmatic access to all publicly available water quality data hosted on DataStream — dataset metadata, monitoring locations, and observations.

It's a REST API built on OData v4 (standardized as ISO/IEC 20802-2). Every endpoint returns JSON in the same shape, and filtering works the same way across all of them.

  • Read-only — only GET requests are supported
  • Server-side — requests from browser front-ends aren't supported
  • Rate limited — 2 requests/sec, no parallel requests
  • Requires an API key — free, on request

Getting Started

1. Get an API key

An API key gives you direct, programmatic access to all publicly available data hosted on DataStream. Tell us about your project and how you plan to use it — we'll typically respond within 24 hours on business days.

Request an API key ↗︎

2. Make your first request

Send your key in the x-api-key header on every request. This one returns a single dataset's metadata:

curl -G -H 'x-api-key: PRIVATE-API-KEY' \
     https://api.datastream.org/v1/odata/v4/Metadata \
     --data-urlencode "\$select=DOI,DatasetName,Citation,Licence" \
     --data-urlencode "\$top=1"
{
  "value": [
    {
      "DOI": "10.25976/xxxx-xx00",
      "DatasetName": "Sample Dataset A",
      "Citation": "Sample Organization A. 2024. \"Sample Dataset A\" (dataset). 1.0.0. DataStream. https://doi.org/10.25976/xxxx-xx00.",
      "Licence": "https://opendatacommons.org/licenses/by/1-0/"
    }
  ]
}

Results include Citation and Licence — see Attribution/Citation before publishing or redistributing. See Endpoints for the full set of fields and filters.

Important

Your API key is a bearer credential — anyone who has it can use it from anywhere, so it's for your use only and shouldn't be shared. Call the API from a server, script, or notebook you control: a key shipped in front-end code is public. If your key does become public, email team@datastream.org and we'll issue you a new one.

3. Pick your approach

  • Working in R, Python, JavaScript, or the shell? Use one of our modules — they handle pagination and encoding for you.
  • Building your own client? Read the implementation notes before you start, then see Endpoints and URL Parameters.
  • Looking for a specific field or filter? The API reference is generated from the specification and is always current.

Attribution and citation

Many datasets available through this API are published under licences that require attribution. Where attribution applies, the data provider must be credited in any analysis, report, publication, or derived data product shared with external parties. Each citation must include a link to the dataset's DOI in the form https://doi.org/{value}. The licence, recommended citation, and DOI for a given dataset can be retrieved from the /Metadata endpoint.

Modules

We have built modules to wrap around our API to make it easier to use.

Building your own implementation

For those building their own implementation, here are some key things to keep in mind:

  • Querystring parameters must be URL encoded. All languages should have a function to do this.
  • Requests to Observations/Records that you expect a large amount (>1M rows) of data from should be partitioned. We recommend by Monitoring locations and/or activity start year.
  • Each request partition should be paginated over using the Link header or @odata.nextLink within the body of the response.
  • Rate limit yourself (2 reqs/sec) and don't make requests in parallel. This will ensure you don't get 429 Too Many Requests error response.
  • Use HTTP/3
  • Requests from browser front-ends aren't supported. Call the API from your backend and serve the results to your page from there.
  • Only GET requests are supported.

Endpoints

Prefix https://api.datastream.org/v1/odata/v4 to the endpoints below.

Browse the full API reference — every endpoint, field, filter and response, generated from the OpenAPI specification, with request samples in curl, Python and R. The specification itself is machine-readable.

  • GET /Metadata

    • Retrieves the dataset-level metadata for the datasets that meet your query criteria.
    • Select By: DOI, Version, DatasetName, DataStewardEmail, DataCollectionOrganization, DataUploadOrganization, ProgressCode, MaintenanceFrequencyCode, Abstract, DataCollectionInformation, DataProcessing, FundingSources, DataSourceURL, OtherDataSources, Citation, Licence, Disclaimer, TopicCategoryCode, Keywords, CreateTimestamp, PublishedTimestamp, TemporalExtent
    • Filter By: DOI, LocationId, ActivityMediaName, ActivityGroupType, CharacteristicName, MonitoringLocationType, ActivityStartYear, RegionId, Latitude, Longitude, DatasetName, CreateTimestamp
    • TemporalExtent is the ["YYYY-MM-DD", "YYYY-MM-DD"] range of activity start dates covered by the dataset. It is not filterable, but its years can bound ActivityStartYear filters on subsequent /Locations, /Observations, or /Records requests.
  • GET /Locations

    • Retrieves the location information that meets your query criteria.
    • Select By: Id, DOI, ID (Maps to MonitoringLocationID internally), Name, Latitude, Longitude, HorizontalCoordinateReferenceSystem, HorizontalAccuracyMeasure, HorizontalAccuracyUnit, VerticalMeasure, VerticalUnit, MonitoringLocationType
    • Filter By: DOI, LocationId, ActivityMediaName, ActivityGroupType, CharacteristicName, MonitoringLocationType, ActivityStartYear, RegionId, Latitude, Longitude, Name
  • GET /Observations

    • Retrieves the observations that meet your query criteria.
    • Select By: Id,LocationId, DOI, ActivityType, ActivityMediaName, ActivityStartDate, ActivityStartTime, ActivityStartTimeZone, ActivityEndDate, ActivityEndTime, ActivityEndTimeZone, ActivityDepthHeightMeasure, ActivityDepthHeightUnit, SampleCollectionEquipmentName, CharacteristicName, MethodSpeciation, ResultSampleFraction, ResultValue, ResultUnit, ResultValueType, ResultDetectionCondition, ResultDetectionQuantitationLimitUnit, ResultDetectionQuantitationLimitMeasure, ResultDetectionQuantitationLimitType, ResultStatusID, ResultComment, ResultAnalyticalMethodID, ResultAnalyticalMethodContext, ResultAnalyticalMethodName, AnalysisStartDate, AnalysisStartTime, AnalysisStartTimeZone, LaboratoryName, LaboratorySampleID
    • Filter By: DOI, LocationId, ActivityMediaName, ActivityGroupType, CharacteristicName, MonitoringLocationType, ActivityStartYear, RegionId
  • GET /Records

    • Retrieves the data in the DataStream schema format with all columns that meet your query criteria.
    • Only intended for streaming data directly into a CSV file. For all other use cases, we recommend using /Observations endpoint.
    • Select By: Id, DOI, DatasetName, MonitoringLocationID, MonitoringLocationName, MonitoringLocationLatitude, MonitoringLocationLongitude, MonitoringLocationHorizontalCoordinateReferenceSystem, MonitoringLocationHorizontalAccuracyMeasure, MonitoringLocationHorizontalAccuracyUnit, MonitoringLocationVerticalMeasure, MonitoringLocationVerticalUnit, MonitoringLocationType, ActivityType, ActivityMediaName, ActivityStartDate, ActivityStartTime, ActivityStartTimeZone, ActivityEndDate, ActivityEndTime, ActivityEndTimeZone, ActivityDepthHeightMeasure, ActivityDepthHeightUnit, SampleCollectionEquipmentName, CharacteristicName, MethodSpeciation, ResultSampleFraction, ResultValue, ResultUnit, ResultValueType, ResultDetectionCondition, ResultDetectionQuantitationLimitMeasure, ResultDetectionQuantitationLimitUnit, ResultDetectionQuantitationLimitType, ResultStatusID, ResultComment, ResultAnalyticalMethodID, ResultAnalyticalMethodContext, ResultAnalyticalMethodName, AnalysisStartDate, AnalysisStartTime, AnalysisStartTimeZone, LaboratoryName, LaboratorySampleID
    • Filter By: DOI, LocationId, ActivityMediaName, ActivityGroupType, CharacteristicName, MonitoringLocationType, ActivityStartYear, RegionId

Body Object

{
  "@data.context": "odata/v4/Records/$links/Metadata(Id=@DatasetId)"
  "value":[
      ...
  ]
}

URL Parameters

OData accepts certain query parameters. The ones supported by this API are listed below; unrecognized query parameters return 400 Bad Request. Custom ordering isn't supported — results are always ordered by Id.

  • $select
    • Fields to be selected are entered comma delimited.
    • Example: $select=DatasetName,Abstract
    • Default: All columns available.
  • $filter
    • Available operators:
      • eq: equal to
      • ne: not equal to
      • gt: greater than
      • ge: greater than or equal to
      • lt: less than
      • le: less than or equal to
      • in: matches any value in a list
      • and: combines conditions, all of which must match
    • lte and gte are deprecated. Use le and ge instead.
    • gt, ge, lt and le work only on year, coordinate and timestamp fields (ActivityStartYear, Latitude, Longitude, CreateTimestamp); on any other field they return 400 Bad Request. The API reference lists the operators each field accepts.
    • Grouping: $filter=CharacteristicName eq 'Dissolved oxygen saturation' or $filter=DOI eq '10.25976/{suffix}' where {suffix} is replaced with a value.
    • Temporal: $filter=CreateTimestamp gt '2020-03-23' and CreateTimestamp lt '2020-03-25'
    • Spatial: $filter=RegionId eq 'hub.atlantic'
      • RegionId Values (these values are subject to change):
        • Partner Hubs: hub.{atlantic,greatlakes,lakewinnipeg,mackenzie,pacific}
        • Countries: admin.2.{ca}
        • Provinces/Territories/States: admin.4.ca.{ab,bc,mb,nb,nl,ns,nt,nu,on,pe,qc,sk,yt}
      • Bounding box $filter=Longitude gt '-102.01' and Longitude lt '-88.99' and Latitude gt '49' and Latitude lt '60'
  • $top
    • Maximum: 10000
    • Example: $top=10
  • $skiptoken
    • Return the next items after the skipped token
    • Example: $skiptoken=Id:1234
    • For pagination, use Link header or @odata.nextLink in the JSON response body
  • $count
    • Return only the count for the request. When the value is large enough it becomes an estimate (~0.0005% accurate)
    • Example: $count=true
    • Default: false

When building an integration with any API, it's important to encode all query string parameters.

Performance Tips

  • Using $select to request only the parameters you need will decrease the amount of data needed to be transferred.

Examples

Metadata

Get all datasets in Mackenzie and Lake Winnipeg DataStream hubs

curl -G -H 'x-api-key: PRIVATE-API-KEY' \
     https://api.datastream.org/v1/odata/v4/Metadata \
     --data-urlencode "\$select=DOI,DatasetName,Licence,Citation,Version" \
     --data-urlencode "\$filter=RegionId in ('hub.mackenzie', 'hub.lakewinnipeg')"

Get the citation and licence for a dataset:

curl -G -H 'x-api-key: PRIVATE-API-KEY' \
     https://api.datastream.org/v1/odata/v4/Metadata \
     --data-urlencode "\$select=DOI,DatasetName,Licence,Citation,Version" \
     --data-urlencode "\$filter=DOI eq '10.25976/xxxx-xx00'"

Sample Response

{
    "value": [
        {
            "Id": "000000ab-000a-00a0-0aba-0ab00abc0a0a",
            "DOI": "10.25976/xxxx-xx00",
            "Version": "1.0.0",
            "DatasetName": "Sample Dataset A",
            "DataStewardEmail": "example@example.com",
            "DataCollectionOrganization": "Sample Organization A",
            "DataUploadOrganization": "Sample Organization B",
            "ProgressCode": "onGoing",
            "MaintenanceFrequencyCode": "asNeeded",
            "Abstract": "Sample Description",
            "DataCollectionInformation": "Sample Data Collection Information",
            "DataProcessing": "Sample Data Processing Information",
            "FundingSources": "Sample Funder A",
            "DataSourceURL": null,
            "OtherDataSources": "",
            "Citation": "Sample Organization A. 2024. \"Sample Dataset A\" (dataset). 1.0.0. DataStream. https://doi.org/10.25976/xxxx-xx00.",
            "Licence": "https://opendatacommons.org/licenses/by/1-0/",
            "Disclaimer": "",
            "TopicCategoryCode": [
                "inlandWaters"
            ],
            "Keywords": [
                "Water quality",
                "temperature"
            ],
            "CreateTimestamp": "2020-01-01T15:30:00.000Z",
            "PublishedTimestamp": "2024-01-01T15:30:00.000Z",
            "TemporalExtent": ["2020-01-01", "2024-01-01"]
        }
    ],
    "@odata.nextLink": "https://api.datastream.org/v1/odata/v4/Metadata?$skiptoken=Id:000000ab-000a-00a0-0aba-0ab00abc0a0a&$top=1000"
}

Locations

Get Locations from a dataset

curl -G -H 'x-api-key: PRIVATE-API-KEY' \
     https://api.datastream.org/v1/odata/v4/Locations \
     --data-urlencode "\$filter=DOI eq '10.25976/xxxx-xx00'"

Get Locations from multiple datasets

curl -G -H 'x-api-key: PRIVATE-API-KEY' \
     https://api.datastream.org/v1/odata/v4/Locations \
     --data-urlencode "\$filter=DOI in ('10.25976/xxxx-xx00', '10.25976/xxxx-xx00', '10.25976/xxxx-xx00')"

Sample Response

{
    "value": [
        {
            "Id": 99999,
            "DOI": "10.25976/xxxx-xx00",
            "ID": "ABC123",
            "Name": "Sample Location A",
            "Latitude": 51.01,
            "Longitude": -144.01,
            "HorizontalCoordinateReferenceSystem": "WGS84",
            "HorizontalAccuracyMeasure": null,
            "HorizontalAccuracyUnit": null,
            "VerticalMeasure": null,
            "VerticalUnit": null,
            "MonitoringLocationType": "River/Stream"
        }
    ],
    "@odata.nextLink": "https://api.datastream.org/v1/odata/v4/Locations?$skiptoken=Id:99999&$top=1000"
}

Observations

Get Temperature and pH observations from multiple datasets

curl -G -H 'x-api-key: PRIVATE-API-KEY' \
     https://api.datastream.org/v1/odata/v4/Observations \
     --data-urlencode "\$filter=DOI in ('10.25976/xxxx-xx00', '10.25976/xxxx-xx00', '10.25976/xxxx-xx00') and CharacteristicName in ('Temperature, water', 'pH')"

Get all pH observations in Alberta:

curl -G -H 'x-api-key: PRIVATE-API-KEY' \
     https://api.datastream.org/v1/odata/v4/Observations \
     --data-urlencode "\$filter=CharacteristicName eq 'pH' and RegionId eq 'admin.4.ca.ab'"

Sample Response

{
    "value": [
        {
            "Id": 99999,
            "DOI": "10.25976/xxxx-xx00",
            "LocationId": 999,
            "ActivityType": "Field Msr/Obs-Portable Data Logger",
            "ActivityMediaName": "Surface Water",
            "ActivityStartDate": "2020-01-01",
            "ActivityStartTime": "15:30:00",
            "ActivityStartTimeZone": null,
            "ActivityEndDate": null,
            "ActivityEndTime": null,
            "ActivityEndTimeZone": null,
            "ActivityDepthHeightMeasure": -0.25,
            "ActivityDepthHeightUnit": "m",
            "SampleCollectionEquipmentName": null,
            "CharacteristicName": "Dissolved oxygen saturation",
            "MethodSpeciation": null,
            "ResultSampleFraction": null,
            "ResultValue": 97,
            "ResultUnit": "%",
            "ResultValueType": null,
            "ResultDetectionCondition": null,
            "ResultDetectionQuantitationLimitUnit": null,
            "ResultDetectionQuantitationLimitMeasure": null,
            "ResultDetectionQuantitationLimitType": null,
            "ResultStatusID": null,
            "ResultComment": null,
            "ResultAnalyticalMethodID": null,
            "ResultAnalyticalMethodContext": null,
            "ResultAnalyticalMethodName": null,
            "AnalysisStartDate": null,
            "AnalysisStartTime": null,
            "AnalysisStartTimeZone": null,
            "LaboratoryName": null,
            "LaboratorySampleID": null
        }
    ],
    "@odata.nextLink": "https://api.datastream.org/v1/odata/v4/Observations?$skiptoken=Id:99999&$top=1000"
}

Records

Sample Response

{
    "value": [
        {
            "Id": 99999,
            "DOI": "10.25976/xxxx-xx00",
            "DatasetName": "Sample Dataset Name A",
            "MonitoringLocationID": "ABC123",
            "MonitoringLocationName": "Sample Location Name A",
            "MonitoringLocationLatitude": 51.01,
            "MonitoringLocationLongitude": -144.01,
            "MonitoringLocationHorizontalCoordinateReferenceSystem": "WGS84",
            "MonitoringLocationHorizontalAccuracyMeasure": null,
            "MonitoringLocationHorizontalAccuracyUnit": null,
            "MonitoringLocationVerticalMeasure": null,
            "MonitoringLocationVerticalUnit": null,
            "MonitoringLocationType": "River/Stream",
            "ActivityType": "Field Msr/Obs-Portable Data Logger",
            "ActivityMediaName": "Surface Water",
            "ActivityStartDate": "2010-01-01",
            "ActivityStartTime": "15:30:00",
            "ActivityStartTimeZone": null,
            "ActivityEndDate": null,
            "ActivityEndTime": null,
            "ActivityEndTimeZone": null,
            "ActivityDepthHeightMeasure": -0.25,
            "ActivityDepthHeightUnit": "m",
            "SampleCollectionEquipmentName": null,
            "CharacteristicName": "Dissolved oxygen saturation",
            "MethodSpeciation": null,
            "ResultSampleFraction": null,
            "ResultValue": 97,
            "ResultUnit": "%",
            "ResultValueType": null,
            "ResultDetectionCondition": null,
            "ResultDetectionQuantitationLimitMeasure": null,
            "ResultDetectionQuantitationLimitUnit": null,
            "ResultDetectionQuantitationLimitType": null,
            "ResultStatusID": null,
            "ResultComment": null,
            "ResultAnalyticalMethodID": null,
            "ResultAnalyticalMethodContext": null,
            "ResultAnalyticalMethodName": null,
            "AnalysisStartDate": null,
            "AnalysisStartTime": null,
            "AnalysisStartTimeZone": null,
            "LaboratoryName": null,
            "LaboratorySampleID": null
        }
    ],
    "@odata.nextLink": "https://api.datastream.org/v1/odata/v4/Records?$skiptoken=Id:99999&$top=1000"
}

Response Format

{
  "value": [{
    "Id": "UUID",
    ...
  }],
  "@odata.nextLink": "https://api.datastream.org/v1/odata/v4/Observations?$skiptoken=Id:99999&$top=1000"
}

Errors

400 Bad Request

This means your query couldn't be accepted. The response body shows which of two causes applies:

  • A body with an errors list means the API rejected the query: an unknown query parameter, an unknown $select or $filter field, an operator the field doesn't accept, or a malformed filter. An unknown parameter is named in detail. Most other rejections read Event object failed validation, with cause listing what failed; for an unknown field or operator, that includes where (instancePath) and the values allowed there (params.allowedValues).
  • {"message":null} means your request was denied before reaching our service. This happens when the query string is not encoded properly. ie $ -> %24.

401 Unauthorized

This means no API key was found on your request. Send your key in the x-api-key header. If you are sending a key and are still rejected, see 403 Forbidden below.

403 Forbidden

This means either your API key was not recognized, or your request was denied before reaching our service. A non-JSON response body indicates the latter, which has a few common causes:

  • The query string is too long. Keep the whole request URL under roughly 2KB. Long in (...) filter lists are the usual cause — split them across multiple requests. How many values fit depends on their length, so measure the URL rather than counting values.
  • No User-Agent header was sent. Some HTTP clients omit it by default; set one.

Requests to paths outside the documented endpoints may also be denied.

408 or 504 Timeout

This means your request was too complicated and was unable to complete within 30sec. To resolve this issue, retry once. If the issue persists, try lowering the $top and/or adding in narrower filters.

413 Payload Too Large

This means your request result was too large. Lowering $top or only requesting the values you need should resolve this issue.

429 Too Many Requests

This status code indicates that the client has sent too many requests in a given amount of time. To resolve this issue, rate limit yourself (2 reqs/sec) and don't make requests in parallel.

Disclaimer

We are currently in a Beta, changes will happen. We will do our best effort to keep you informed of any breaking changes.

The Gordon Foundation Logo

About

DataStream Public API Documentation

Topics

Resources

Stars

14 stars

Watchers

10 watching

Forks

Releases

Packages

Contributors