Welcome To Jetsam
Jetsam is your gateway to waste tracking, environmental citizen science! Apps and devices in the Jetsam ecosystem all contribute data to our publicly available central repository.
You can use our APIs to create new and engaging ways of contributing and consuming environmental data.
If you haven't tried it yet, download the app and photograph some plastic waste!
Using the API
Code samples will appear in this sidebar, showing examples of what the request should look like. The tabs at the top of the screen allow you to see the same request in various platform SDKs
Jetsam provides a REST API for accessing data stored in the community data repository. Some endpoints are openly available without providing access credentials; other endpoints require either server credentials or user credentials to function.
Integrating Jetsam into your application
The first step to integrating Jetsam into your application is to go to the developer portal and create a new application. Depending on the type of application you create, you will receive either a server token or an OAuth application. The following sections outline how to use both of these to interact with the API.
Some endpoints do not require authentication. These can be directly used without creating an application in the developer portal, but may be subject to additional limitations that a registered application is not. Each endpoint listed in this documentation will describe limitations for non-registered access.
API Versioning
The Jetsam API is versioned; endpoints will take the form of /api/[[version]]/[[resource]]
. The current API
version is v2
.
While changes may be made to a versioned endpoint, all changes to a single version will be done in a way that is considered backwards compatible. The Jetsam API considers a backwards compatible change to be one that only adds fields to the response or optional parameters to the request.
Some endpoints are also available without the version number; if an endpoint can be accessed either with or without a version number, then it is recommended to use the version number. Unversioned endpoints may be altered or changed in incompatible ways without deprecation or notice, and are not considered to be public.
Using a Server Token
A 'server token' is a secret value that allows an automated process to impersonate your user account. What does that mean?
- secret value: The token should not be made public, uploaded to a version control repository, or in any other way leaked
- automated process: This could be a script, a cron job, an IoT device, or even a server endpoint. But not a front end web client.
- impersonate your user account: A server token allows the recipient to act as though they are you, in every way. Scoped server tokens are coming soon.
This means that a server token is a quick way of getting a program up and running with access to the Jetsam API but is completely unsuitable for use in a web application that doesn't have a server process. For that use case, you should use an OAuth2 application with PKCE for authenticating users without a server.
Including a Server Token in requests
GET /api/v2/self HTTP/1.1
Host: app.jetsam.tech
Accept: application/json
Authorization: Bearer [[Your Server Token]]
const result = await fetch('https://app.jetsam.tech/api/v2/self', {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${yourServerToken}`
}
})
To get a server token, you just need to log in to the developer portal and create a new token with a memorable name and a short description. When you return to your list of tokens and applications, press "Show Token" beside your newly created token and copy the value that appears.
Server Tokens are JSON Web Tokens (JWTs) that are signed by the Jetsam API to assert that you are who you say you are. When
used to access the API, you can use them as bearer tokens in an Authorization
header, just like tokens retrieved from
the OAuth flows.
Using an OAuth 2 Application
OAuth2 is a popular way for users to give you limited access to their accounts. A basic understanding of how to use OAuth2 is required for this section, be that through the use of a library such as Node.js' Passport & Laravel's Socialite, or by implementing the OAuth2 client flows yourself from scratch.
Explaining the implementation details of using OAuth2 in your app is beyond the scope of this documentation, but helpful links will be provided. If you have not used OAuth2 before, consider using a Server Token instead.
To start, you should log in to the developer portal and create a new OAuth application. The name and description that you provide for your application will be shown to users when they are asked to accept or reject your request for access, so it is in your best interests to make them recognisable and informative. In addition, the redirect URIs that you list in your OAuth application must be an exact match for the ones provided during the OAuth process, including trailing slashes.
There is no limit to the number of redirect URIs that you can provide, but it is recommended to use different OAuth applications for different environments and apps (e.g. One OAuth application for your development environment, one for your production environment, etc).
The Jetsam API supports a number of different applications with varying requirements. As such, there are two possible sets of endpoints you can use with your OAuth application, depending on what standards you need to conform to. The "Legacy" endpoints conforms to the basic features of the OAuth2 specification, while the OpenID Connect (OIDC) endpoints conform to the OIDC specification and include additional features such as PKCE for public clients
Scopes
Jetsam has a very limited API surface area. Metrics can be created and retrieved by both logged in and logged out users.
Due to the nature of OpenID Connect, you will need to supply the openid
scope in your authentication request to get
an ID token. This means that in almost all situations, you will only be requesting the openid
scope. While the API supports
other scopes, these are not used by public OAuth applications as the endpoitns that use them are restricted to first party devices
and applications.
Legacy Mode
Legacy Mode OAuth2 allows you to perform a basic request for access to an end user's account. Plenty of documentation about OAuth2 flows is available online. The access token you receive as part of a Jetsam OAuth2 flow is a JWT that attests to a user's identity. Check the federated authentication section for information about verifying that a JWT is legitimate.
For the first leg of your Legacy Mode flow, you will need to use the https://app.jetsam.tech/auth/authorize
endpoint to request
permission from users.
Once you have received the code with your redirect URI, you can exchange it for tokens at the https://app.jetsam.tech/auth/token
endpoint.
OpenID Connect
You can find the JSON formatted OpenID configuration document at https://app.jetsam.tech/.well-known/openid-configuration. Many libraries and clients can use this document to automatically configure your OIDC integration. It may also contain useful information for you, such as a quick reference to the various endpoints involved.
The OIDC integration functions similarly to the Legacy Mode integration; the same OAuth2 application can be used for both interchangeably. The major difference between the two is that the access token that you receive from the OIDC integration is not a JWT. It does not contain any information that can be used to attest a user's identity. It is otherwise used in the same way as a JWT you receive from the Legacy Mode integration.
The access token should be used as part of an Authorization
header, just like the Server Token or Legacy Mode JWT.
The URL for that you should send an end user to in order to authenticate, alongside your Client ID, scopes, scope and redirect URI is http://app.jetsam.tech/oidc/auth
. Unlike
the Legacy Mode flow, this endpoint supports PKCE
The URL for requesting tokens with the code retrieved from the previous endpoint is http://app.jetsam.tech/oidc/token
The OIDC userinfo endpoint can be found at http://app.jetsam.tech/oidc/me
. The Jetsam API also provides its own
user introspection endpoint
Federated Authentication
If you want to accept Jetsam issued JWTs as an attestation of a user's identity and access level, you can verify their signatures by using the JSON Web Key Set (JWKS) served from the https://app.jetsam.tech/.well-known/jwks.json endpoint.
The mechanics of verifying a JWT are beyond the scope of this documentation, but should be built into the JWT handling library of your chosen programming language.
The following tokens are verifiable JWTs that contain identity information:
- Server Token
- Legacy OAuth2 Access Token
- OpenID Connect OAuth2 Identity Token
Safe Mode
The Safe Mode error response
{
"errors": {
"general": [
"This resource is operating in \"safe mode\", and is exclusively accepting read-only requests"
]
}
}
There are some rare occasions when we need to perform maintenance on the Jetsam data store, where there is a huge surge in usage that causes the servers to be overwhelmed, or some other incident occurs where we need to turn off the incoming data taps. In situations like this, instead of completely shutting down access, the API will go in to "Safe Mode".
Safe Mode is, in essence, a read only mode. Only idempotent requests will successfully be served (typically GET
, HEAD
and
OPTIONS
requests). Anything that tries to upload data or media will be rejected.
If you attempt to perform an action that is blocked by safe mode, you will receive a 503
response with an error explaining
that Safe Mode is in effect. This isn't a permanent state, and it isn't a failure in your application - please cache the data
locally and retry after a reasonable back off time.
Users
The Jetsam user model is used as a way of linking data collected, photos uploaded, and actions taken to a single entity. Jetsam users don't currently interact directly through the platform, so the available user endpoints only relate to the currently authenticated user.
The user object
Properties
- id UUID
- A unique identifier for this user
- name String
- The nickname that the user provided when they signed up. This may be a real name or a pseudonymous username
- email String
- The email address this user signed up with
- meta Object
- Additional heterogeneous data recorded about this model. Usually blank.
- created_at Timestamp
- The time when this user signed up
Get the authenticated user
GET /api/v2/self HTTP/1.1
Host: app.jetsam.tech
Accept: application/json
Authorization: Bearer [[token]]
const result = await fetch('https://app.jetsam.tech/api/v2/self', {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${bearerToken}`
}
})
Result Body
{
"user": {
"id": "00000000-0000-0000-0000-000000000000",
"name": "Jenry Cranks",
"email": "[email protected]",
"meta": {},
"created_at": "2021-10-15T14:46:45.306Z",
"updated_at": "2021-10-15T14:46:45.306Z"
}
}
GET /api/v2/self
Retrieves information about the authenticated user, including name and email address
Parameters
Not Parameters
Return Value
Returns the user object associated with the user whose token was used as part of the request
Errors
Status | Description | Why? |
---|---|---|
404 | No user is currently logged in | No token was provided, the token was invalid, or the token has expired |
Metrics
"Metrics" are the main focus of the API. A metric represents 3 pieces of data: A location, a timestamp and a value. For the most part, a metric value will be numeric - it could be a temperate, wind speed, or the amount of trash in a given location. Jetsam has a range of metric types built in, but integrating applications can use special custom types to provide data that falls outside the usual set of types.
The metric object
Properties
Example Metric
{
"id": "00000000-0000-0000-0000-000000000000",
"value": 1,
"type": "trash",
"location": {
"longitude": -1.00543,
"latitude": 58.00101
},
"recorded_at": "2022-01-01T12:00:00.000Z",
"meta": {}
}
- id UUID
- A unique identifier for this data point
- value Number String
- The context dependant value of this data point. The type and meaning of this data is decided by the `type` property
- type MetricType
- The real world unit that this metric represents. See the below table for possible values
- location LatLong
- An object containing
latitude
andlongitude
properties - recorded_at Timestamp
- The time at which this metric was recorded
- meta Object
- Additional heterogeneous data recorded about this data point. Common values might include a description of the device that recorded this data point, or a list of surveys that the data was added to at the point of capture.
Supported metric types
- trash Number
- A number of pieces of plastic waste in an associated photograph
- pm2.5 Number
- A PM2.5 reading from a particulate sensor
- co2_ppm Number
- A CO2 reading, in parts per million
- wind_mph Number
- A wind speed recording in miles per hour
- wind_kph Number
- A wind speed recording in kilometres per hour
- temp_c Number
- A temperature reading in celsius
- temp_f Number
- A temperature reading in fahrenheit
- temp_k Number
- A temperature reading in kelvin
- pressure_pa Number
- An atmospheric pressure reading in pascals. Readings in hectopascals should be converted to pascals before being sent to Jetsam
- pressure_mbar Number
- An atmospheric pressure reading in bars
- rain_mm Number
- A rainfall reading in millimetres per hour
- custom_string String
- A custom metric whose value is a string. This should be accompanied by meta information about the metric type
- custom_number Number
- A custom metric whose value is a number. This should be accompanied by meta information about the metric type
Create a metric
POST /api/v2/metrics HTTP/1.1
Host: app.jetsam.tech
Accept: application/json
Content-Type: application/json
Content-Length: 123
Authorization: Bearer [[token]]
Request-Device: 123-custom-device-id
Request-Platform: Temp Sensor 9000
{
"value": 21.5,
"type": "temp_c",
"location": {
"latitude": "50.789113",
"longitude": "-1.029763"
}
}
const result = await fetch('https://app.jetsam.tech/api/v2/metrics', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${bearerToken}`,
'Request-Device': '123-custom-device-id',
'Request-Platform': 'Temp Sensor 9000',
},
body: JSON.stringify({
value: 21.5,
type: 'temp_c',
location: {
latitude: '50.789113',
longitude: '-1.029763'
}
})
})
Result Body
{
"metric": {
"id": "00000000-0000-0000-0000-000000000000",
"value": 21.5,
"type": "temp_c",
"location": {
"longitude": -1.029763,
"latitude": 50.789113
},
"recorded_at": "2022-01-01T12:00:00.000Z",
"meta": {
"device": {
"id": "123-custom-device-id",
"info": null,
"platform": "Temp Sensor 9000"
}
}
}
}
POST /api/v2/metrics
Create a new data reading from a device, commonly referred to as a metric
Parameters
- value Number
- The data that this metric represents
- type MetricType
- The unit for this metric, from the list of supported types
- location Object
-
- location.latitude Number String
- The north/south value of the location. String values will be parsed into numbers. Invalid strings will be converted to
0
- location.longitude Number String
- The east/west value of the location. String values will be parsed into numbers. Invalid strings will be converted to
0
- meta Object Optional
- The data that this metric represents
Return Value
Returns the fully populated metric object that represents the values provided
Errors
Status | Description | Why? |
---|---|---|
422 | XXX is not a supported type | 'XXX' is the value of type that you provided, and it does not match a value from the list of supported types |
422 | A location must be provided for metrics | You have not provided an object for the location parameter, or the value of location does not contain latitude and longitude properties |
Create a batch of metrics
The example payloads are truncated with "..." to save space. See the previous example for a full metric payload
POST /api/v2/metrics HTTP/1.1
Host: app.jetsam.tech
Accept: application/json
Content-Type: application/json
Content-Length: 493
Authorization: Bearer [[token]]
Request-Device: 123-custom-device-id
Request-Platform: Temp Sensor 9000
{
"batch": [
{
"value": 10,
"type": "temp_c",
"..."
}, {
"value": 2,
"type": "rain_mm",
"..."
}, {
"value": 23,
"type": "wind_mph",
"..."
}
]
}
const result = await fetch('https://app.jetsam.tech/api/v2/metrics', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
Authorization: `Bearer ${bearerToken}`,
'Request-Device': '123-custom-device-id',
'Request-Platform': 'Temp Sensor 9000',
},
body: JSON.stringify({
batch: [
{
value: 10,
type: 'temp_c',
// ...
}, {
value: 2,
type: 'rain_mm',
// ...
}, {
value: 23,
type: 'wind_mph',
// ...
},
]
})
})
Result Body
{
"metrics": [
{
"id": "00000000-0000-0000-0000-000000000000",
"value": 10,
"type": "temp_c",
"..."
}, {
"id": "00000000-0000-0000-0000-000000000000",
"value": 2,
"type": "rain_mm",
"..."
}, {
"id": "00000000-0000-0000-0000-000000000000",
"value": 23,
"type": "wind_mph",
"..."
}
]
}
POST /api/v2/metrics
This is the same endpoint used to create a single metric. The variance in payload format determines single vs batch creation
Upload a series of related readings from a device. Due to the fact that the API assigns a recorded_at
value
to the metrics based on when they were received and validated, separately recording related metrics can result
in timestamp drift.
Every metric in a single batch
will have the same recorded_at
value
Parameters
- batch Array<MetricPayload>
- A list of metric values, each of which matches the payload used when creating a single metric. See Create A Metric for the properties that each entry in the list should have
Return Value
Returns a list containing fully populated metric objects for each input value
Errors
Status | Description | Why? |
---|---|---|
422 | XXX is not a supported type | 'XXX' is the value of type that you provided, and it does not match a value from the list of supported types |
422 | A location must be provided for metrics | You have not provided an object for the location parameter, or the value of location does not contain latitude and longitude properties |
Get metrics
This example uses a simple location square by specifying the
point_from
andpoint_to
parameters. Experiment with thewithin
parameter for more complex search areas
GET /api/v2/metrics?point_from=50.847718,-1.116924&point_to=50.779113,-1.019763&types=trash HTTP/1.1
Host: api.jetsam.tech
Accept: application/json
const url = new URL('https://app.jetsam.tech/api/v2/metrics')
const params = new URLSearchParams({
point_from: '50.847718,-1.116924',
point_to: '50.779113,-1.019763',
types: 'trash'
})
url.search = params
const result = await fetch(url, {
headers: {
Accept: 'application/json'
}
})
Response Body
{
"metrics": [
{
"id": "00000000-0000-0000-0000-000000000000",
"value": 2,
"type": "trash",
"location": {
"longitude": -1.029763,
"latitude": 50.789113
},
"recorded_at": "2022-01-01T12:00:00.000Z",
},
{
"id": "00000000-0000-0000-0000-000000000000",
"value": 1,
"type": "trash",
"location": {
"longitude": -1.029763,
"latitude": 50.789113
},
"recorded_at": "2022-01-01T12:00:00.000Z",
},
"..."
]
}
GET /api/v2/metrics
Get individually recorded metrics within a certain area and time. Metric metadata is not returned by this endpoint. If a time range is not specified, the API will return data from the last 30 days. If no metric type is specified, no data will be returned.
You can specify the area to be retrieved in one of two formats:
- Simple square queries (e.g. the viewport of a map such as MapBox or Google Maps) can use the
point_from
andpoint_to
parameters to specify the top left and bottom right of the square respectively - More complex shapes can be provided by encoding each vertex of a polygon into the
within
parameter
Parameters (Query String Only)
- types String
-
A comma seperated list of metric types to retrieve. Check the list of supported types
for a list of valid values. A single type does not need a comma (e.g.
types=trash
), while multiple types should not include spaces between a comma and the next item (e.g.types=trash,wind_mph,rain_mm
) - point_from String Required alongside 'point_to'
-
A string containing a latitude value and a longitude value, seperated by a comma. This value represents the top left of a
square when looking at a standard mercator projection of the globe. The format should exactly match
point_from=lat,long
. This parameter should be accompanied by the "point_to" parameter. Will be ignored if also providing the "within" parameter. - point_to String Required alongside 'point_from'
-
A string containing a latitude value and a longitude value, seperated by a comma. This value represents the bottom right of a
square when looking at a standard mercator projection of the globe. The format should exactly match
point_to=lat,long
. This parameter should be accompanied by the "point_from" parameter. Will be ignored if also providing the "within" parameter. - within String Overrides 'point_from' and 'point_to'
-
A string encoding a series of lat/long pairs that define a polygon to be used for filter metrics. Each lat/long pair
should be formatted as per the point_to & point_from parameters before being joined together with semicolons. The format
should exactly match
within=lat1,long1;lat2,long2;lat3,long3
, and there is currently no upper limit on the number of vertices you can provide - date_from String Optional
- A string containing an ISO 8601 timestamp representing the earliest date from which data should be retrieved. When not provided, data will be retrieved from the date 30 days before the time at which the request is sent
- date_to String Optional
- A string containing an ISO 8601 timestamp representing the latest date from which data should be retrieved. When not provided, data will be retrieved up to the time at which the request is sent
- format String Optional
- The data format being queried. To retrieve the individual data points this field should either be set to "full", or omitted. If omitted, the API default to "full".
Return Value
A list of metric
objects that only contain the id
, value
, type
, location
and recorded_at
properties.
Get aggregate metrics
GET /api/v2/metrics?point_from=50.847718,-1.116924&point_to=50.779113,-1.019763&types=rain_mm,wind_mph&format=marker&aggregate=average HTTP/1.1
Host: api.jetsam.tech
Accept: application/json
const url = new URL('https://app.jetsam.tech/api/v2/metrics')
const params = new URLSearchParams({
point_from: '50.847718,-1.116924',
point_to: '50.779113,-1.019763',
types: 'rain_mm,wind_mph',
format: 'marker',
aggregate: 'average'
})
url.search = params
const result = await fetch(url, {
headers: {
Accept: 'application/json'
}
})
Response Body
{
"metrics": [
{
"value": 2.3,
"type": "rain_mm",
"location": {
"longitude": -1.03,
"latitude": 50.789
}
},
{
"value": 5,
"type": "wind_mph",
"location": {
"longitude": -1.03,
"latitude": 50.789
}
}
]
}
GET /api/v2/metrics
Perform aggregation operations on recorded metrics within a certain area and time. The aggregations are performed over areas equal to 0.001 degrees squared, which translates to approximately 100m2 in the UK. The exact size of the aggregated area will change depending on the location on the planet due to the curvature of the earth.
If you specify multiple types in your query, each type will be aggregated separately
You can specify the area in which aggregations should be performed in one of two formats:
- Simple square queries (e.g. the viewport of a map such as MapBox or Google Maps) can use the
point_from
andpoint_to
parameters to specify the top left and bottom right of the square respectively - More complex shapes can be provided by encoding each vertex of a polygon into the
within
parameter
Parameters (Query String Only)
Performing aggregations uses the same endpoint and set of query string parameters as retrieving a list of individual metrics, with the following exceptions:
- format String
- The data format being queried. To perform aggregations, this must be exactly equal to `marker`
- aggregate Aggregation Optional
- The specific type aggregation to perform. The default value is "count", which will provide a count of the number of recordings in the given area. See below for a list of allowed operations
Supported Aggregations
- count
-
The total number of recordings of the specified type in the given area.
If 3 metrics have been recorded in the given area, the value for that area will be 3 regardless of each
metric's
value
property - sum
- The summation of the metric values in the given area. Each value for all metrics of the specified type in given area will be added together to produce the final value
- min
- The smallest value present for each type in the set of metrics within the given area
- max
- The largest value present for each type in the set of metrics within the given area
- average
- Averages the values of each type in the given area. Divides the sum of matching metric values by the count of matching metrics to produce the final value
Return Value
A list of aggregated metric objects. Aggregated metric objects have the following properties:
- value Number
- The result of performing the aggregation operation over data in a particular grid square
- type String
- The metric type that was aggregated to produce this value
- location LatLong
-
The centroid of the grid square that was aggregated to produce this value. Grid squares may vary in size
due to the curvature of the earth and may not align between metric types in the same query based on how
sparse the data for a particular type is
- location.latitude Number
- The north/south value of the location
- location.longitude Number
- The east/west value of the location