Back to top

Kaiterra API

Version: 2020-11-13

Overview

The Kaiterra API allows your programs to access the air quality data reported by your Laser Egg, Sensedge, Square, or other device.

How to get started:

  1. Get your API key at dashboard.kaiterra.com. Once you’ve created an account, go to the Profile page (under the gear icon in the upper right), then the Developer section. Click Generate a new key.

  2. If you’re a Postman user, try out the API by using Import -> Link, and paste this link: https://www.getpostman.com/collections/c7baa037ade5b51fa190 You’ll need to use the Desktop version of Postman for our API. Note that you’ll need to create a Postman environment with a variable called api-key (set to your Kaiterra API key) to try the examples in the collection.

  3. Download some Python sample code from GitHub.

  4. Find the Unique Device IDs (UDIDs) for your Kaiterra devices in the device’s settings (Sensedge) or in the Kaiterra mobile app.

  5. Don’t have any Kaiterra devices yet? Feel free to use our test devices from this documentation – the ones with IDs beginning with “00000000-” – to get familiar with the API:

    1. 00000000-0031-0101-0000-00007e57c0de is a test Sensedge

    2. 00000000-0001-0101-0000-00007e57c0de is a test Laser Egg

Auth

All requests to the API must be authenticated. Get your API key by signing up at dashboard.kaiterra.com.

We support the following kinds of authentication:

  • URL-based key: When issuing an HTTP request, the client includes the key in a URL parameter called key (this is how e.g. Google Maps works). All requests must use HTTPS, so the key is safe from eavesdropping. This method is simple, but it’s only suitable for clients that are running on trusted devices, such as a server you control, or a researcher’s workstation. These keys must NOT be embedded directly into web pages, or iOS or Android apps.

If authentication information is required but not present, or if it is not accepted, HTTP 401 is returned (401 is called “Unauthorized”, but it should really be called “Unauthenticated”).

Parameters

  • Unique Device IDs (UDIDs) for Kaiterra devices are 128-bit UUIDs. They’re case-insensitive and may be submitted with or without intermediate dashes (46048117-86a7-488e-9c58-708f3470ee11 and 4604811786a7488e9c58708f3470ee11 are both OK).

    • Note: there are no permissions on devices, so anyone with access to the API can request data for any device for which they know the UDID. However, UDIDs are 128 bits long, so they have far more entropy than any password that a user account may have, which makes them much harder to guess.
  • All dates/times follow RFC3339 (a refinement of ISO8601), which looks like 2016-12-07T05:32:16Z. Milliseconds are accepted but are ignored (truncated). All times must be in UTC; any that aren’t will be rejected with 400 bad request.

  • The aqi query string parameter, if present, causes APIs returning pollutant data to precalculate corresponding air quality index values according to the air quality index of the given country or region. Codes are the 2-character codes from ISO3166. Supported values are cn, in, and us.

Request Headers

The following headers are technically optional but are a good idea:

  • Accept-Encoding: gzip is supported; clients are encouraged to use it.

  • Content-Encoding: if there is a request body, this header denotes its encoding. Always use UTF-8.

Sensor Reading Data Format

Note: this section describes the new "series-major" time series data format, available on the unified /devices endpoint since June 2020, which always includes units and is easier to parse. While the existing /lasereggs and /sensedges endpoints will continue to function as long as this API is supported, they are now deprecated, and all clients are encouraged to use this new format instead.

Data on various pollutants or other parameters (like temperature and humidity) are returned first as an array of parameters, with a series of data points under each parameter:

{
    "data": [
        {
            "param": "rpm25c",
            "units": "µg/m³",
            "source": "km100",
            "span": 60,
            "points": [
                {
                    "ts": "2020-06-17T03:40:00Z",
                    "value": 120
                }
            ]
        },
        {
            "param": "rtemp",
            "units": "%",
            "span": 60,
            "points": [
                {
                    "ts": "2020-06-17T03:40:00Z",
                    "value": 62
                }
            ]
        }
    ]
}

Each series under data has the following properties:

  • param: The parameter code. These are listed below.

  • units: The units under which the parameter is expressed. These are listed below.

  • source: (Present for sensor modules) The module that captured the parameter reading. For instance, km102 identifies the KM-102 sensor module.

  • span: The sampling interval, in seconds, over which this measurement was taken. For example, 60 refers to a sample taken over the span of one minute, and 3600 designates hourly averages.

  • points: The actual array of data points for this series. Even if the series has only one data point, this is still an array.

PARAMETER CODES

Code Meaning
rco Carbon monoxide
rco2 Carbon dioxide
rhcho Formaldehyde
rpm25c PM2.5 (µg/m³), post-calibration
rpm10c PM10 (µg/m³), post-calibration
rhumid Relative humidity
rtemp Temperature
rtvoc Total Volatile Organic Compounds (TVOC)

UNIT CODES

Code Meaning
ppm Parts per million (volumetric concentration)
ppb Parts per billion
µg/m³ Micrograms per cubic meter (mass concentration)
mg/m³ Milligrams per cubic meter
C Degrees Celsius
F Degrees Fahrenheit
x Count of something, such as readings in a sampling interval
% Percentage, as with relative humidity

Multi-Region

The Kaiterra cloud is split into multiple regions. The exact same Kaiterra API is published in each region, under these domains:

In most cases, code written against the Kaiterra API before the multi-region split will continue to work unmodified. There are a few exceptions, which are detailed below.

Home Region and HTTP Redirects

Data for a particular device is stored in exactly one region. The region where a device’s data is stored is called the device’s home region.

To query data for a device, you must make the request against the Kaiterra API in the device’s home region. If you make the request against the API in any other region, you will receive an HTTP 301 (Moved Permanently) response, with the response’s Location header indicating where the query should be retried from now on.

For HTTP verbs other than GET, you’ll receive HTTP 308 (Permanent Redirect) instead. This is because, as RFC 7538 states, “This status code is similar to 301 (Moved Permanently) … except that it does not allow changing the request method from POST to GET.”

For example:

$ curl -v "https://api.kaiterra.cn/v1/lasereggs/00000000-0001-0101-0000-00007e57c0de?key=$KAITERRA_APIV1_URL_KEY"
> GET /v1/lasereggs/00000000-0001-0101-0000-00007e57c0de?key=0123456789abcdef-YOUR-API-KEY HTTP/2
> Host: api.kaiterra.cn
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/2 301
< location: https://api.eur.kaiterra.com/v1/lasereggs/00000000-0001-0101-0000-00007e57c0de?key=0123456789abcdef-YOUR-API-KEY
< date: Wed, 13 May 2020 07:21:32 GMT
< content-length: 0
< vary: Origin
<
* Connection #0 to host api.kaiterra.cn left intact

Since HTTP 301/308 are part of the HTTP standard, most HTTP libraries will follow the redirects automatically.

Redirect vs. Proxy

Why does the Kaiterra API redirect the client, instead of proxying the call (making the call to the correct region on behalf of the client)? There are at least two reasons for this:

  1. In a strict sense, the resource has indeed permanently moved – it is no longer available at the domain under which it was requested. This is precisely what response code 301 is for.

  2. Internet connections crossing in and out of Mainland China are not nearly as reliable as connections that do not need to cross this boundary. It’s far better that the client make the call to the device’s home region directly, especially if both the client and device are outside (or inside) Mainland China.

The one exception to this is the /batch endpoint, which is described below.

Redirects and the /batch Endpoint

The /batch API endpoint, which allows the client to gather data from multiple devices in a single HTTP request, continues to function exactly as before. However, this includes the possibility of sub-requests returning HTTP 301 or 308 if the device’s home region is elsewhere. This behavior is problematic for clients not written to deal with it.

As a concession, only for requests for the most recent data for a device (/lasereggs/{id}, /sensedges/{id}), the Kaiterra API does proxy those requests to the device’s home region on behalf of the client.

However, there is a nonzero chance that these calls will fail. So, ideally, the client would detect these redirects and itself make the /batch request directly against the other region.

Redirects and Root Certificates

The SSL certificates for api.kaiterra.com and api.kaiterra.cn use the ISRG Root X1 certificate as their root. While this certificate is already trusted by all modern environments, some older Android devices do not trust it.

The deprecated api.origins-china.cn domain uses an IdentTrust DST root. If your code runs in an environment without the ISRG Root X1 certificate, connects to api.origins-china.cn, and receives a redirect to api.kaiterra.com or .cn, then the SSL handshake will fail. In this case, you will need to either add the ISRG Root X1 certificate to your device’s trusted root certificate store, or supply it as a trusted root certificate at connection time. Most SSL libraries support this.

Devices

Data and metadata for all types of Kaiterra devices are available under the /devices endpoint.

All Devices

Latest sensor reading
GET/devices/{id}/top

Retrieves the last sensor reading uploaded by the device.

Example URI

GET https://api.kaiterra.com/v1/devices/00000000-0031-0101-0000-00007e57c0de/top
URI Parameters
HideShow
id
string (required) Example: 00000000-0031-0101-0000-00007e57c0de

Unique ID of the device whose details are being requested.

Response  200
HideShow

The device’s most recent sensor reading is returned.

Body
{
  "data": [
    {
      "param": "rco2",
      "units": "ppm",
      "span": 60,
      "points": [
        {
          "ts": "2020-06-17T06:40:00Z",
          "value": 1673
        }
      ]
    },
    {
      "param": "rhumid",
      "source": "km102",
      "units": "%",
      "span": 60,
      "points": [
        {
          "ts": "2020-06-17T06:40:00Z",
          "value": 55.81
        }
      ]
    },
    {
      "param": "rpm10c",
      "source": "km100",
      "units": "µg/m³",
      "span": 60,
      "points": [
        {
          "ts": "2020-06-17T06:40:00Z",
          "value": 125
        }
      ]
    },
    {
      "param": "rpm25c",
      "source": "km100",
      "units": "µg/m³",
      "span": 60,
      "points": [
        {
          "ts": "2020-06-17T06:40:00Z",
          "value": 169
        }
      ]
    },
    {
      "param": "rtemp",
      "source": "km102",
      "units": "C",
      "span": 60,
      "points": [
        {
          "ts": "2020-06-17T06:40:00Z",
          "value": 26.25
        }
      ]
    },
    {
      "param": "rtvoc",
      "source": "km102",
      "units": "ppb",
      "span": 60,
      "points": [
        {
          "ts": "2020-06-17T06:40:00Z",
          "value": 397
        }
      ]
    }
  ]
}

Devices - Deprecated

While these endpoints will continue to work as long as the Kaiterra API is supported, their use is discouraged.

All pollutant measurements have default units of micrograms per cubic meter (µg/m³). If the units are otherwise, they are called out below, or specified in parentheses after the pollutant name, like co (mg/m3) or nox (ppb).

Property names are:

Laser Egg, Square:

  • pm25: PM2.5 (µg/m³), post-calibration

  • pm10: PM10 (µg/m³), post-calibration

  • humidity: relative humidity in % (0-100)

  • temp: temperature in Celsius

  • rtvoc: TVOC measurement, in parts per billion (ppb)

  • st03.rtvoc: TVOC measurement, in parts per billion (ppb)

Sensedge:

Available Sensedge measurement properties depend on which modules are inserted. Properties are prefixed with the model number of the module.

  • km100.rpm25c: PM2.5 (µg/m³), post-calibration

  • km100.rpm10c: PM10 (µg/m³), post-calibration

  • km102.rhumid: relative humidity in % (0-100)

  • km102.rtemp: temperature in Celsius

  • km102.rtvoc (ppb): TVOC measurement, in parts per billion (ppb)

  • rco2 (ppm): CO2 measurement from the unit’s built-in sensor, in parts per million (ppm)

Laser Egg, Square

Latest sensor reading and metadata
GET/lasereggs/{id}

Example URI

GET https://api.kaiterra.com/v1/lasereggs/00000000-0001-0101-0000-00007e57c0de
URI Parameters
HideShow
id
string (required) Example: 00000000-0001-0101-0000-00007e57c0de

Unique ID of the device whose details are being requested.

Response  200
HideShow

The device’s most recent sensor reading is returned. If the device has been registered by the app but has never reported any data, then the data element is omitted from the response.

Body
{
  "id": "00000000-0001-0101-0000-00007e57c0de",
  "info.aqi": {
    "ts": "2016-10-27T10:01:42Z",
    "data": {
      "pm25": 34.5,
      "pm10": 12.1
    }
  }
}
Response  404
HideShow

Indicates that a Laser Egg or Square with the given UDID has not yet been registered. This error is also returned if the UDID is not a Laser Egg or Square.

Sensedges

Latest sensor reading and metadata
GET/sensedges/{id}

Example URI

GET https://api.kaiterra.com/v1/sensedges/00000000-0031-0001-0000-00007e57c0de
URI Parameters
HideShow
id
string (required) Example: 00000000-0031-0001-0000-00007e57c0de

Unique ID of the device whose details are being requested.

Response  200
HideShow

Only the most recent data from the Sensedge’s sensors is returned. If the device exists but has never reported any data, then the latest element is omitted from the response.

Body
{
        "id": "00000000-0031-0001-0000-00007e57c0de",
        "latest": {
            "ts": "2018-07-24T02:39:18Z",
            "km100.rpm25c": 278,
            "km100.rpm10c": 125,
            "km102.rhumid": 56.36,
            "km102.rtemp": 10.76,
            "km102.rtvoc (ppb)": 402.1,
            "rco2 (ppm)": 1673,
        }
    }
Response  404
HideShow

Indicates that a Sensedge with the given UDID has not yet been registered.

Other

Batch Requests

Submit a Batch Request
POST/batch{?include_headers}

Instead of making multiple HTTP requests, clients can issue one batch request with a special JSON formatted body. The format is nearly identical to Facebook’s batch request API, except that there is no support for dependent requests and other advanced features.

Authentication parameters, such as the key URL parameter, must be included in URL or headers of the POST batch request. Each sub-request inherits the auth context of the POST batch request; any auth parameters on the sub-requests will be ignored. Therefore, it’s possible that some sub-requests will succeed, and others will fail with 403 Forbidden.

While the JSON objects in the batch response will be in the same order as the objects in the batch request, the relative order in which the server fulfills each request is undefined. For example, in a batch request that contains a PATCH and a GET request on the same resource, the result of the GET request may or may not reflect the changes made by the PATCH request.

Example URI

POST https://api.kaiterra.com/v1/batch?include_headers=false
URI Parameters
HideShow
include_headers
boolean (required) Example: false

Default: false. Whether to include the response headers for individual responses.

Request
HideShow

The batch request body is a JSON array of request objects. Each object must have a method and relative_url property; other properties are optional.

  • method (string) - The HTTP method to use.

  • relative_url (string) - The URL to request, relative to the Kaiterra API’s base URL.

  • headers (json, optional) - A JSON array of header description objects, each of which has a name and value object.

  • body (string, optional) - The request body for this request. JSON objects must be JSON-encoded before being placed in this string.

Headers
Content-Type: application/json
Body
[
  {
    "method": "GET",
    "relative_url": "/devices/00000000-0001-0101-0000-00007e57c0de/top"
  },
  {
    "method": "GET",
    "relative_url": "/devices/00000000-0031-0001-0000-00007e57c0de/top"
  }
]
Response  200
HideShow

Returns the result of each of the requested operations, with the body returned as a JSON string. Note that HTTP 200 will be returned as long as the batch request itself did not fail processing, even if none of the sub-requests succeeded.

Body
[
  {
    "body": "{\"data\":[{\"param\":\"rhumid\",\"units\":\"%\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":54}]},{\"param\":\"rpm10c\",\"units\":\"µg/m³\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":120}]},{\"param\":\"rpm25c\",\"units\":\"µg/m³\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":191}]},{\"param\":\"rtemp\",\"units\":\"C\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":16}]},{\"param\":\"rtvoc\",\"units\":\"ppb\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":342}]}]}",
    "code": 200
  },
  {
    "body": "{\"data\":[{\"param\":\"rco2\",\"units\":\"ppm\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":1673}]},{\"param\":\"rhumid\",\"source\":\"km102\",\"units\":\"%\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":54.79}]},{\"param\":\"rpm10c\",\"source\":\"km100\",\"units\":\"µg/m³\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":125}]},{\"param\":\"rpm25c\",\"source\":\"km100\",\"units\":\"µg/m³\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":275}]},{\"param\":\"rtemp\",\"source\":\"km102\",\"units\":\"C\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":20.57}]},{\"param\":\"rtvoc\",\"source\":\"km102\",\"units\":\"ppb\",\"span\":60,\"points\":[{\"ts\":\"2020-06-17T07:05:00Z\",\"value\":435.6}]}]}",
    "code": 200
  }
]

Generated by aglio on 09 Jun 2021