Client API

Temp Stick API Documentation

Base URL https://tempstickapi.com/api/v1

Getting Started

The Temp Stick API is intended for developers who are comfortable making HTTP requests, working with JSON responses, and handling common API client behavior such as headers, authentication, compression, and form data.

Base URL#

Code
https://tempstickapi.com/api/v1

Authentication#

Every request must include your account API key in the X-API-Key HTTP header.

HTTP
X-API-Key: YOUR_API_KEY

You can find your API key in your Temp Stick account:

1. Log in to your Temp Stick account.

2. Go to the Account Page.

3. Select the Developers tab.

4. Click Show Key to reveal your API key.

All API clients should identify themselves clearly on every request. Do not send bare minimum requests that only include the API key.

At minimum, include these headers with every request:

HTTP
X-API-Key: YOUR_API_KEY
Accept: application/json
User-Agent: YourAppName/1.0
X-Client-Name: YourAppName
X-Client-Version: 1.0

Recommended header details:

Header Required Description
X-API-Key Yes Your Temp Stick account API key.
Accept Recommended Use application/json.
User-Agent Recommended A descriptive application name and version. Avoid generic defaults such as curl, python-requests, or unnamed HTTP clients in production.
X-Client-Name Recommended The name of your application, service, integration, or company.
X-Client-Version Recommended The version of your application or integration.
Content-Type Required for POST Use multipart/form-data for POST requests that update settings.
Accept-Encoding Optional Your HTTP client may include this automatically. The API may return gzip-compressed data.

Example header set#

Python
headers = {
    "X-API-Key": "YOUR_API_KEY",
    "Accept": "application/json",
    "User-Agent": "MyTempStickApp/1.0",
    "X-Client-Name": "MyTempStickApp",
    "X-Client-Version": "1.0",
}

Avoid requests that only send the API key and no client identity information:

Python
headers = {
    "X-API-Key": "YOUR_API_KEY",
    "Accept": "application/json",
}

Compression#

API responses may be compressed with gzip and returned as JSON.

Most modern HTTP clients automatically decompress gzip responses when configured correctly. Make sure your application supports compressed responses before parsing the JSON.

Temperature Units#

All temperature readings and temperature settings are returned in Celsius.

To convert Celsius to Fahrenheit:

Code
(CELSIUS * 1.8) + 32

PHP conversion helper#

PHP
function celsius_to_fahrenheit($celsius, $round = 1) {
    return round(($celsius * 1.8) + 32, $round);
}

Dates and Times#

All timestamps returned by the API use the UTC time zone.

UTC timestamps may be marked with Z or with the offset +00:00.

Examples:

Code
2026-04-27T15:30:00Z
2026-04-27T15:30:00+00:00

Convert timestamps to the user's local time zone in your application when displaying dates and times.

GET Requests#

Use GET requests to retrieve data from the API.

GET requests should:

  • Include all recommended identifying headers.
  • Not include a request body.
  • Use Accept: application/json.
  • Use a descriptive User-Agent.

PHP GET example#

PHP
<?php

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => 'https://tempstickapi.com/api/v1/sensors/all',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => '', // Automatically decode gzip responses.
    CURLOPT_TIMEOUT => 30,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_HTTPHEADER => [
        'X-API-Key: YOUR_API_KEY',
        'Accept: application/json',
        'User-Agent: MyTempStickApp/1.0',
        'X-Client-Name: MyTempStickApp',
        'X-Client-Version: 1.0',
    ],
]);

$response = curl_exec($curl);

if ($response === false) {
    $error = curl_error($curl);
    curl_close($curl);
    throw new RuntimeException('Temp Stick API request failed: ' . $error);
}

$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

if ($http_code < 200 || $http_code >= 300) {
    throw new RuntimeException('Temp Stick API returned HTTP ' . $http_code . ': ' . $response);
}

echo $response;

?>

JavaScript GET example#

JavaScript
const headers = new Headers();

headers.append("X-API-Key", "YOUR_API_KEY");
headers.append("Accept", "application/json");
headers.append("User-Agent", "MyTempStickApp/1.0");
headers.append("X-Client-Name", "MyTempStickApp");
headers.append("X-Client-Version", "1.0");

const requestOptions = {
  method: "GET",
  headers: headers,
  redirect: "follow"
};

fetch("https://tempstickapi.com/api/v1/sensors/all", requestOptions)
  .then(response => {
    if (!response.ok) {
      throw new Error(`Temp Stick API returned HTTP ${response.status}`);
    }

    return response.json();
  })
  .then(result => console.log(result))
  .catch(error => console.error("Temp Stick API request failed:", error));

> Note: Browser-based JavaScript may restrict setting certain headers, including User-Agent. For production integrations, server-side requests are recommended when you need full control over request headers.

cURL GET example#

Shell
curl --location --request GET 'https://tempstickapi.com/api/v1/sensors/all' \
  --header 'X-API-Key: YOUR_API_KEY' \
  --header 'Accept: application/json' \
  --header 'User-Agent: MyTempStickApp/1.0' \
  --header 'X-Client-Name: MyTempStickApp' \
  --header 'X-Client-Version: 1.0'

Python GET example#

Python
import requests

url = "https://tempstickapi.com/api/v1/sensors/all"

headers = {
    "X-API-Key": "YOUR_API_KEY",
    "Accept": "application/json",
    "User-Agent": "MyTempStickApp/1.0",
    "X-Client-Name": "MyTempStickApp",
    "X-Client-Version": "1.0",
}

response = requests.get(url, headers=headers, timeout=30)
response.raise_for_status()

data = response.json()
print(data)

POST Requests#

Use POST requests when updating sensor settings.

POST requests should:

  • Include all recommended identifying headers.
  • Send data as multipart/form-data.
  • Use field names exactly as documented for the endpoint.
  • Send only supported fields for the endpoint you are calling.

When using libraries such as PHP cURL, Python requests, or JavaScript FormData, the client usually creates the correct multipart/form-data boundary automatically. Do not manually set the boundary unless your HTTP client requires it.

PHP POST example#

PHP
<?php

$curl = curl_init();

curl_setopt_array($curl, [
    CURLOPT_URL => 'https://tempstickapi.com/api/v1/sensor/YOUR_SENSOR_ID',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_ENCODING => '',
    CURLOPT_TIMEOUT => 30,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => [
        'sensor_name' => 'Test',
        'send_interval' => '3600',
        'alert_interval' => '1800',
        'use_alert_interval' => '0',
        'alert_temp_below' => '',
        'alert_temp_above' => '',
        'alert_humidity_below' => '',
        'alert_humidity_above' => '',
        'connection_sensitivity' => '3',
        'use_offset' => '0',
        'temp_offset' => '0',
        'humidity_offset' => '0',
    ],
    CURLOPT_HTTPHEADER => [
        'X-API-Key: YOUR_API_KEY',
        'Accept: application/json',
        'User-Agent: MyTempStickApp/1.0',
        'X-Client-Name: MyTempStickApp',
        'X-Client-Version: 1.0',
    ],
]);

$response = curl_exec($curl);

if ($response === false) {
    $error = curl_error($curl);
    curl_close($curl);
    throw new RuntimeException('Temp Stick API request failed: ' . $error);
}

$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);

if ($http_code < 200 || $http_code >= 300) {
    throw new RuntimeException('Temp Stick API returned HTTP ' . $http_code . ': ' . $response);
}

echo $response;

?>

JavaScript POST example#

JavaScript
const formData = new FormData();

formData.append("sensor_name", "Test");
formData.append("send_interval", "3600");
formData.append("alert_interval", "1800");
formData.append("use_alert_interval", "0");
formData.append("alert_temp_below", "");
formData.append("alert_temp_above", "");
formData.append("alert_humidity_below", "");
formData.append("alert_humidity_above", "");
formData.append("connection_sensitivity", "3");
formData.append("use_offset", "0");
formData.append("temp_offset", "0");
formData.append("humidity_offset", "0");

const headers = new Headers();

headers.append("X-API-Key", "YOUR_API_KEY");
headers.append("Accept", "application/json");
headers.append("User-Agent", "MyTempStickApp/1.0");
headers.append("X-Client-Name", "MyTempStickApp");
headers.append("X-Client-Version", "1.0");

const requestOptions = {
  method: "POST",
  headers: headers,
  body: formData,
  redirect: "follow"
};

fetch("https://tempstickapi.com/api/v1/sensor/YOUR_SENSOR_ID", requestOptions)
  .then(response => {
    if (!response.ok) {
      throw new Error(`Temp Stick API returned HTTP ${response.status}`);
    }

    return response.json();
  })
  .then(result => console.log(result))
  .catch(error => console.error("Temp Stick API request failed:", error));

> Note: When using FormData, do not manually set the Content-Type header in browser JavaScript. The browser will set multipart/form-data with the correct boundary.

cURL POST example#

Shell
curl --location --request POST 'https://tempstickapi.com/api/v1/sensor/YOUR_SENSOR_ID' \
  --header 'X-API-Key: YOUR_API_KEY' \
  --header 'Accept: application/json' \
  --header 'User-Agent: MyTempStickApp/1.0' \
  --header 'X-Client-Name: MyTempStickApp' \
  --header 'X-Client-Version: 1.0' \
  --form 'sensor_name=Test' \
  --form 'send_interval=3600' \
  --form 'alert_interval=1800' \
  --form 'use_alert_interval=0' \
  --form 'alert_temp_below=' \
  --form 'alert_temp_above=' \
  --form 'alert_humidity_below=' \
  --form 'alert_humidity_above=' \
  --form 'connection_sensitivity=3' \
  --form 'use_offset=0' \
  --form 'temp_offset=0' \
  --form 'humidity_offset=0'

Python POST example#

Python
import requests

url = "https://tempstickapi.com/api/v1/sensor/YOUR_SENSOR_ID"

headers = {
    "X-API-Key": "YOUR_API_KEY",
    "Accept": "application/json",
    "User-Agent": "MyTempStickApp/1.0",
    "X-Client-Name": "MyTempStickApp",
    "X-Client-Version": "1.0",
}

data = {
    "sensor_name": "Test",
    "send_interval": "3600",
    "alert_interval": "1800",
    "use_alert_interval": "0",
    "alert_temp_below": "",
    "alert_temp_above": "",
    "alert_humidity_below": "",
    "alert_humidity_above": "",
    "connection_sensitivity": "3",
    "use_offset": "0",
    "temp_offset": "0",
    "humidity_offset": "0",
}

response = requests.post(url, headers=headers, files={}, data=data, timeout=30)
response.raise_for_status()

result = response.json()
print(result)

Common Issues#

406 Not Acceptable#

A 406 Not Acceptable response usually means the request format or headers were rejected before the API request could be processed.

Common causes include:

  • Using a generic or default User-Agent.
  • Sending a GET request with a request body.
  • Sending malformed headers.
  • Including blocked or suspicious keywords in headers.
  • Using a client that does not identify the application clearly.

Recommended fixes:

  • Use a descriptive User-Agent, such as MyTempStickApp/1.0.
  • Include X-Client-Name and X-Client-Version.
  • Do not send a body with GET requests.
  • Remove unnecessary headers.
  • Test the same request in Postman or another API client where headers are easy to inspect.

429 Too Many Requests#

A 429 Too Many Requests response means the client is sending too many requests or the request pattern looks automated without enough client identity.

Recommended fixes:

  • Include all recommended identifying headers on every request.
  • Use a descriptive and stable User-Agent.
  • Include X-Client-Name and X-Client-Version.
  • Avoid retry loops without delays.
  • Add exponential backoff when retrying failed requests.
  • Cache data in your own application when appropriate.
  • Avoid polling endpoints more often than needed.

Recommended retry pattern:

Code
First retry: wait 2 seconds
Second retry: wait 5 seconds
Third retry: wait 15 seconds
Additional retries: wait longer, or stop and alert the user

Testing with Postman#

Postman is recommended when testing or troubleshooting the API because it makes request headers, parameters, and responses easy to inspect.

When creating a request in Postman:

1. Set the method to GET or POST.

2. Enter the full API endpoint URL.

3. Add the recommended headers:

  • X-API-Key
  • Accept
  • User-Agent
  • X-Client-Name
  • X-Client-Version

4. For POST requests, use form-data in the Body tab.

5. Send the request and inspect the response body and status code.

Request Checklist#

Before sending a request to the Temp Stick API, confirm that:

  • The request includes X-API-Key.
  • The request includes a descriptive User-Agent.
  • The request includes X-Client-Name.
  • The request includes X-Client-Version.
  • GET requests do not include a request body.
  • POST requests use multipart/form-data.
  • Your client can handle gzip-compressed responses.
  • Your application treats API timestamps as UTC.
  • Your application treats API temperatures as Celsius.

Endpoints

12 endpoints documented from api\v1.

GET /alerts/:alert_id #

Get Alert

Get settings for a single alert

Group
Alerts
Version
1.0.0
Source
api\v1\index.php

Parameters

Field Type Required Description
alert_id Number Required The ID of your alert

Success Examples

Success ResponseJSON
{
  "type": "success",
  "message": "get alert",
  "data": {
    "id": "alert_id",
    "user_id": "user_id",
    "alert_name": "Connection",
    "reading_type": "connection",
    "reading_condition": "lost",
    "reading_value": "0.00",
    "alert_message": "[sensor_name]: connection [trigger_value]",
    "always_active": "1",
    "sensors_global": "1",
    "enabled": "1",
    "push_notifications": "1",
    "email_text_notifications": "0",
    "connection_sensitivity": "0",
    "sensors": [
      "sensor_id"
    ],
    "contacts": {
      "contact_id": {
        "id": "contact_id",
        "send_email": 1,
        "send_text": 1
      }
    },
    "active_when": {
      "0": {
        "alert_day": "0",
        "alert_time_start": 12,
        "alert_time_end": 24
      },
      "2": {
        "alert_day": "2",
        "alert_time_start": 1,
        "alert_time_end": 13
      }
    }
  }
}
GET /alerts/all #

Get Alerts

Get all your alerts

Group
Alerts
Version
1.0.0
Source
api\v1\index.php

Success Examples

Success ResponseJSON
{
    "type": "success",
    "message": "get alerts",
    "data": [
      {
        "id": "",
        "user_id": "",
        "alert_name": "Temperature",
        "reading_type": "temperature",
        "reading_condition": "above",
        "reading_value": "7.20",
        "alert_message": "[sensor_name]: [trigger_type] [trigger_condition] [trigger_value] ([sensor_value])",
        "always_active": "1",
        "sensors_global": "1",
        "enabled": "1",
        "active_when": [],
        "contacts": [],
        "sensors": [],
      }
    ]
  }
GET /sensor/notifications/:sensorId #

Get Sensor Notifications

Get the last seven days of alerts generated by the sensor

Group
Alerts
Version
1.0.0
Source
api\v1\index.php

Parameters

Field Type Required Description
sensorId String Required

Query Parameters

Field Type Required Description
page Number Required
Default: 0
items_per_page Number Required
Default: 10
Maximum 100 items per page

Success Examples

Success ResponseJSON
{
  "type": "success",
  "message": "get sensor notifications",
  "data": {
    "items": [
      {
        "id": "",
        "user_id": "",
        "alert_id": "",
        "event_type": "temperature",
        "event_message": "above 45 F (71.8 F)",
        "event_data": "{\"alert_condition\":\"above\",\"alert_value\":\"45 F\",\"reading_value\":\"71.8 F\"}",
        "created": "2022-05-10 22:49:41Z"
      }
    ],
    "count": "350"
  }
}
GET /user/notifications #

Get User Notifications

Get the last seven days of alerts generated by all sensors

Group
Alerts
Version
1.0.0
Source
api\v1\index.php

Query Parameters

Field Type Required Description
page Number Required
Default: 0
items_per_page Number Required
Default: 10
Maximum 100 items per page

Success Examples

Success ResponseJSON
{
  "type": "success",
  "message": "get user notifications",
  "data": {
    "items": [
      {
        "id": "",
        "user_id": "",
        "sensor_id": "",
        "alert_id": "",
        "event_type": "temperature",
        "event_message": "above 45 F (69.6 F)",
        "event_data": "{\"alert_condition\":\"above\",\"alert_value\":\"45 F\",\"reading_value\":\"69.6 F\"}",
        "created": "2022-05-11 17:37:21Z",
        "sensor": {}
      }
    ],
    "count": "350"
  }
}
GET /sensor/:sensor_id #

Get Sensor

Get settings for a specific sensor, *note: readings are shown in Celsius*

Group
Sensors
Version
1.0.0
Source
api\v1\index.php

Parameters

Field Type Required Description
sensor_id String Required The ID of the sensor you want to get readings from

Success Examples

Success ResponseJSON
{
  "type": "success",
  "message": "get sensor",
  "data": {
    "version": "",
    "sensor_id": "",
    "sensor_name": "",
    "sensor_mac_addr": "",
    "owner_id": "",
    "type": "DHT",
    "alert_interval": "1800",
    "send_interval": "1800",
    "last_temp": 21,
    "last_humidity": 28.26,
    "last_voltage": 3,
    "battery_pct": 100,
    "wifi_connect_time": 1,
    "rssi": -37,
    "last_checkin": "2022-05-12 19:09:41-00:00Z",
    "next_checkin": "2022-05-12 19:39:41-00:00Z",
    "ssid": "",
    "offline": "0",
    "alerts": [],
    "use_sensor_settings": 0,
    "temp_offset": "0",
    "humidity_offset": "0",
    "alert_temp_below": "",
    "alert_temp_above": "",
    "alert_humidity_below": "",
    "alert_humidity_above": "",
    "connection_sensitivity": "3",
    "use_alert_interval": 0,
    "use_offset": "0",
    "last_messages": [
      {
        "temperature": 21,
        "humidity": 28.26,
        "voltage": "3.00",
        "RSSI": "-37",
        "time_to_connect": "1.0",
        "sensor_time_utc": "2022-05-12 19:09:41"
      }
    ]
  }
}
POST /sensor/:sensor_id #

Update Sensor Settings

Group
Sensors
Version
1.0.0
Source
api\v1\index.php

Parameters

Field Type Required Description
sensor_id String Required

Query Parameters

Field Type Required Description
sensor_name String Required Give your sensor a name
send_interval Number Required
Default: 300, 600, 1800, 3600
In seconds, how often the sensor should wake up and send readings
use_alert_interval Number Required
Default: 0, 1
Set whether to enable the alert_interval setting (sensor will wake up more often if readings are above or below certain values)
alert_interval Number Optional
Default: 300, 600, 1800, 3600
In seconds, how often the sensor should wake up when above or below certain conditions, this number must be less than or equal to send_interval
alert_temp_below Number Optional Set as empty string "" if unused, use alert_interval value if temperature in celsius is less than this number
alert_temp_above Number Optional Set as empty string "" if unused, use alert_interval value if temperature in celsius is above this number
alert_humidity_below Number Optional Set as empty string "" if unused, use alert_interval value if humidity is less than this number
alert_humidity_above Number Optional Set as empty string "" if unused, use alert_interval value if humidity is above this number
connection_sensitivity Number Required
Default: 1-10
How many times a sensor can miss a send_interval reading before triggering an offline alert
use_offset Number Required
Default: 0, 1
Set whether to enable sensor calibration (offset) settings
temp_offset Number Required Number to offset temperature readings (this is normalized to celsius automatically if your account is set to use Fahrenheit)
humidity_offset Number Required Number to offset humidity readings

Success Examples

Success ExampleJSON
{
  "type": "success",
  "message": "update sensor",
  "data": {
    "sensor_name": "sensor_name",
    "send_interval": 1800,
    "alert_interval": 1800,
    "connection_sensitivity": "2",
    "use_offset": "1",
    "temp_offset": "1",
    "humidity_offset": "0",
    "use_sensor_settings": "0",
    "use_alert_interval": 0,
    "alert_temp_below": "",
    "alert_temp_above": "",
    "alert_humidity_below": "",
    "alert_humidity_above": ""
  }
}
GET /sensor/:sensor_id/readings #

Get Readings

Get the readings for a sensor given a specified period, *note: readings are shown in Celsius*

Group
Sensors
Version
1.0.0
Source
api\v1\index.php

Parameters

Field Type Required Description
sensor_id String Required The ID of the sensor you want to get readings from

Query Parameters

Field Type Required Description
offset Number Required The timezone offset in seconds from UTC used to calculate the specified time period
setting String="today, 6_hours, 12_hours, 24_hours, yesterday, last_week, 7_days, this_week, this_month, 30_days, last_month, three_months, custom" Required
Default: today
The time period you want to grab readings from
start String Optional The start date in YYYY-MM-DD format if "custom" is selected
end String Optional The end date in YYYY-MM-DD format if "custom" is selected

Examples

Basic usagePHP
<?php

    $curl = curl_init();

    $example_parameters = [
      'setting' => 'last_week', // get readings for last week
      'offset' => 21600 // America/Denver (UTC offset in seconds)
    ];

    curl_setopt_array($curl, array(
      CURLOPT_URL => 'https://tempstickapi.com/api/v1/sensor/YOUR_SENSOR_ID/readings?setting=last_week&offset=21600',
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_ENCODING => '',
      CURLOPT_TIMEOUT => 0,
      CURLOPT_CUSTOMREQUEST => 'GET',
      CURLOPT_HTTPHEADER => array(
      'X-API-KEY: YOUR_API_KEY'
      ),
    ));

    $response = curl_exec($curl);

    curl_close($curl);
    echo $response;
Custom date rangePHP
<?php

    $curl = curl_init();

    $example_parameters = [
      'setting' => 'custom', // use a start and end date
      'start' => '2022-01-01', // start date range
      'end' => '2022-01-30', // end date range
      'offset' => 21600 // America/Denver (UTC offset in seconds)
    ];

    curl_setopt_array($curl, array(
      CURLOPT_URL => 'https://tempstickapi.com/api/v1/sensor/YOUR_SENSOR_ID/readings?setting=custom&start=2022-01-01&end=2022-01-30&offset=21600',
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_ENCODING => '',
      CURLOPT_TIMEOUT => 0,
      CURLOPT_CUSTOMREQUEST => 'GET',
      CURLOPT_HTTPHEADER => array(
      'X-API-KEY: YOUR_API_KEY'
      ),
    ));

    $response = curl_exec($curl);

    curl_close($curl);
    echo $response;

Success Examples

Success ResponseJSON
{
    "type": "success",
    "message": "get messages",
    "data": {
      "start": "2022-05-11 04:00:00Z",
      "end": "2022-05-12 03:59:59Z",
      "readings": [
        {
        "sensor_time": "2022-05-11 22:26:39Z",
        "temperature": 22.06 (Celsius),
        "humidity": 27.45,
        "offline": 0
        }
      ]
    }
  }
GET /sensors/all #

Get Sensors

Get all the sensors assigned to the account, *note: readings are shown in Celsius*

Group
Sensors
Version
1.0.0
Source
api\v1\index.php

Success Examples

Success ResponseJSON
{
    "type": "success",
    "message": "get sensors",
    "data": {
      "groups": [],
      "items": [
        {
          "version": "",
          "sensor_id": "",
          "sensor_name": "",
          "sensor_mac_addr": "",
          "owner_id": "",
          "type": "",
          "alert_interval": "",
          "send_interval": "",
          "last_temp": 25.4 (Celsius),
          "last_humidity": 21.77,
          "last_voltage": "3.48",
          "battery_pct": 100,
          "wifi_connect_time": "1",
          "rssi": "-57",
          "last_checkin": "2021-10-25 20:04:25",
          "next_checkin": "2021-10-25 21:04:25",
          "ssid": "",
          "offline": "1",
          "group": 0,
          "connection_sensitivity": "3",
          "temp_offset": 0,
          "humidity_offset": 0
        }
      ]
    }
  }
GET /user #

Get Current User

Get your user information

Group
User
Version
1.0.0
Source
api\v1\index.php

Success Examples

Success ResponseJSON
{
  "type": "success",
  "message": "Get user",
  "data": {
    "id": "",
    "email": "",
    "first_name": "",
    "last_name": "",
    "phone": "",
    "contact_email": "",
    "address_1": "",
    "address_2": "",
    "city": "",
    "state": "",
    "zip": "",
    "temp_pref": "F",
    "timezone": "America/New_York",
    "use_sensor_groups": "0",
    "chart_fill": "1",
    "send_reports": "1",
    "daily_reports": "1",
    "weekly_reports": "1",
    "monthly_reports": "1",
    "reports_specific_sensors": "1",
    "level": 10,
    "weekly_report_day": "1",
    "sensor_count": 1
  }
}
GET /user/allowed-timezones #

Get Timezones

Get current list of supported timezones

Group
User
Version
1.0.0
Source
api\v1\index.php

Success Examples

Success ResponseJSON
{
  "type": "success",
  "message": "get timezones",
  "data": {
    "America/New_York": "US Eastern",
    "America/Chicago": "US Central",
    "America/Denver": "US Mountain West",
    "America/Phoenix": "US Mountain (No DST)",
    "America/Los_Angeles": "US Pacific",
    "America/Anchorage": "US Alaska",
    "Pacific/Honolulu": "US Hawaii",
    "America/Glace_Bay": "(GMT-04:00) Atlantic Time (Canada)",
    "America/Argentina/Buenos_Aires": "(GMT-03:00) Buenos Aires",
    "America/Noronha": "(GMT-02:00) Mid-Atlantic",
    "Atlantic/Azores": "(GMT-01:00) Azores",
    "Europe/London": "(GMT) London",
    "Europe/Berlin": "(GMT+01:00) Europe (CET)",
    "Africa/Algiers": "(GMT+01:00) West Africa",
    "Africa/Cairo": "(GMT+02:00) Central Africa",
    "Africa/Addis_Ababa": "(GMT+03:00) East Africa",
    "Europe/Moscow": "(GMT+03:00) Moscow",
    "Asia/Dubai": "(GMT+04:00) Abu Dhabi",
    "Asia/Karachi": "(GMT+05:00) Pakistan",
    "Asia/Calcutta": "(GMT+05:30) Calcutta",
    "Asia/Dhaka": "(GMT+06:00) Dhaka",
    "Asia/Bangkok": "(GMT+07:00) Bangkok",
    "Asia/Shanghai": "(GMT+08:00) Asia",
    "Asia/Tokyo": "(GMT+09:00) Tokyo",
    "Australia/Brisbane": "(GMT+10:00) Brisbane",
    "Asia/Magadan": "(GMT+11:00) Magadan",
    "Pacific/Auckland": "(GMT+12:00) Auckland",
    "Pacific/Tongatapu": "(GMT+13:00) Nuku alofa",
    "Pacific/Kiritimati": "(GMT+14:00) Kiritimati"
  }
}
POST /user/display-preferences #

Update User Display Preferences

Change display preferences including time zone and temperature format

Group
User
Version
1.0.0
Source
api\v1\index.php

Query Parameters

Field Type Required Description
timezone String Required
Default: See /user/allowed-timezones
Time zone of the user, functionally used in determining if an alert should trigger if a time window is set
temp_pref String Required
Default: F, C
Display alerts in fahrenheit or celsius, readings and settings are always done in celsius for consistency
chart_fill Number Optional
Default: 0, 1
Set whether to have the chart normalized on the Y-axis.

Success Examples

Success ResponseJSON
{
  "type": "success",
  "message": "updated display preferences",
  "data": {
    "timezone": "America/New_York",
    "temp_pref": "F",
    "chart_fill": "1"
  }
}
GET /user/email-reports #

Get Email Reports

Get email report settings and any downloadable reports

Group
User
Version
1.0.0
Source
api\v1\index.php

Success Examples

Success ResponseJSON
{
  "type": "success",
  "message": "get email reports",
  "data": {
    "contacts": [],
    "sensors": [],
    "reports": {
      "daily": [
        {
          "report_id": "",
          "type": "daily",
          "file": "daily-2022-05-12.zip",
          "date_start": "2022-05-12 04:00:00",
          "date_end": "2022-05-13 03:59:59",
          "download": "URL_TO_DOWNLOAD"
        }
      ],
      "weekly": [],
      "monthly": []
    }
  }
}

Examples

Examples and Useful Code#

Battery Percentage Calculation#

At times, you may want to calculate the battery percentage of your sensor from the voltage sent by the sensor. This is

most accurate with alkaline batteries which have a steady voltage decline while lithium / rechargeable batteries tend to

hold voltage until failure.

JavaScript
const getBatteryPercentage = voltage => {

  // typical voltage range of sensors is 2.3v to 3.0v
  // (0% to 100%)

  const baseline = 2.30; // lowest allowed voltage
  const remaining_voltage = voltage - baseline;

  let voltagePct = remaining_voltage / 0.007;

  if(voltagePct > 100){ voltagePct = 100; }
  if(voltagePct < 0){ voltagePct = 0; }

  return Math.round(voltagePct);

}

Saving Readings and Alerts to Google Sheets#

This NodeJS script uses the Temp Stick API to fetch data from your sensors and update the readings while observing each

sensor's next check-in timestamp. Readings will be saved to the "Readings" tab of your sheet.

If any alerts are triggered, they will be recorded in the "Alerts" tab of your Google spreadsheet.

Setup#

  • Create a Service Account JSON key to access Google Sheets
  • Create a new Google Sheet (get the ID from the URL)
  • Create the tabs "Readings" and "Alerts"
  • "Readings" should have the following columns: "Sensor", "Temp C", "Temp F", "Humidity", "RSSI", "Time to Connect", "Battery", "Offline", "Timestamp"
  • "Alerts" should have the following columns: "Sensor", "Alert", "Timestamp"

To set up a service account for Google Services, go to this

guide for instructions

on obtaining a Service Key.


Keep in mind:

  • Your Google keyfile JSON is sensitive information, similar to a password to your Google account. Do not expose it publicly.
  • Similarly, your Temp Stick API key should be treated as confidential. It provides access to your Temp Stick account, hence don't include it directly in your code, especially if you're planning to share your code or upload it on platforms like Github. Always ensure your keys are securely stored and accessed.

Note

This is a basic example of integrating Google Sheets with Temp Stick. For a more robust application, you might want

to consider saving your readings and alerts to a dedicated database (such as SQL or MongoDB). Using a database could

provide more flexibility and efficiency than a spreadsheet for data management and retrieval.

This will not run in a web browser and requires NodeJS to use.


NodeJS Javascript

JavaScript
// Import necessary libraries
const {google} = require('googleapis'); // Google's Node.js client library
const axios = require('axios'); // HTTP client
const fs = require('fs'); // Node.js File System module

// Define the path for a JSON file where the application will store data. 
// The application will remember the last 1000 readings per sensor.
const dataFile = __dirname + '/data.json';

// Provide the API key for the Temp Stick service. This is reading a txt file called "temp-stick-api-key" (no extension)
const TEMP_STICK_API_KEY = fs.readFileSync(__dirname + "/temp-stick-api-key");

// Configure the Google JWT (JSON Web Token) authorization object
const auth = new google.auth.JWT({
  // Point to your Google service account file
  keyFile: __dirname + '/config/temp-stick-datalogging.json',
  // Specify the scope that the JWT is allowed to access. 
  // In this case, we're giving it access to Google Sheets.
  scopes: ['https://www.googleapis.com/auth/spreadsheets'],
});

// Initialize the Google Sheets API client
const sheets = google.sheets({version: 'v4', auth});

// Define the ID of the Google Sheets spreadsheet to be accessed. Replace '' with your actual spreadsheet id.
const spreadsheetId = '';

// Create a variable to store historical data. 
let dataHistory = null;

// Check if the file specified by dataFile already exists
if(fs.existsSync(dataFile)) {
  // If it does, read the file and parse its JSON content into the dataHistory variable
  dataHistory = JSON.parse(fs.readFileSync(dataFile));
} else {
  // If it doesn't, initialize dataHistory with an object that has an empty 'alerts' array
  dataHistory = {
    alerts: [],
  };
}

// Define a function to save the current state of dataHistory back into the dataFile
const saveData = () => {
  fs.writeFileSync(dataFile, JSON.stringify(dataHistory));
}

// The getSensors function retrieves sensor data from the Temp Stick API
async function getSensors() {
  try {
    // Send a GET request to the Temp Stick API's 'sensors/all' endpoint
    const response = await axios.get('https://tempstickapi.com/api/v1/sensors/all', {
      headers: {
        'X-API-KEY': TEMP_STICK_API_KEY // Include the Temp Stick API key in the request headers
      },
      timeout: 0, // Do not set a timeout for the request
    });

    const res = response.data; // Extract the data from the response

    // If the API request was successful, return the list of sensor items
    // If not, return false
    if(res.type === 'success') {
      return res.data.items;
    } else {
      return false;
    }

  } catch (error) { // If there's an error in the try block, log it
    console.error(error);
  }
}

// The getAlertNotifications function retrieves alert notifications from the Temp Stick API
async function getAlertNotifications() {
  try {
    // Send a GET request to the Temp Stick API's 'user/notifications' endpoint
    const response = await axios.get('https://tempstickapi.com/api/v1/user/notifications', {
      headers: {
        'X-API-KEY': TEMP_STICK_API_KEY // Include the Temp Stick API key in the request headers
      },
      timeout: 0, // Do not set a timeout for the request
    });

    const res = response.data; // Extract the data from the response

    // If the API request was successful, return the list of notification items
    // If not, return false
    if(res.type === 'success') {
      return res.data.items;
    } else {
      return false;
    }

  } catch (error) { // If there's an error in the try block, log it
    console.error(error);
  }
}

// The getMessages function retrieves messages for a specific sensor from the Temp Stick API
async function getMessages(sensorId) {
  try {
    // Send a GET request to the Temp Stick API's 'sensor/{sensorId}/readings' endpoint
    // The request will include a query string specifying the settings and offset for the readings
    const response = await axios.get(`https://tempstickapi.com/api/v1/sensor/${sensorId}/readings?setting=24_hours&offset=21600`, {
      headers: {
        'X-API-KEY': TEMP_STICK_API_KEY // Include the Temp Stick API key in the request headers
      },
      timeout: 0, // Do not set a timeout for the request
    });

    const res = response.data; // Extract the data from the response

    // If the API request was successful, return the list of readings
    // If not, return false
    if(res.type === 'success') {
      return res.data.readings;
    } else {
      return false;
    }

  } catch (error) { // If there's an error in the try block, log it
    console.error(error);
  }
}

// Function to add alerts to a Google Spreadsheet
async function addAlertsToSheet(alerts) {

  // Initialize an empty array to hold the data
  let data = [];

  // Loop through the alerts array
  for(alert of alerts) {

    // Combine the event type and event message into one string
    let message = alert.event_type + " " + alert.event_message;
    let created = alert.created;

    // Convert the created timestamp into Ontario time
    let ontarioTime = new Date(created).toLocaleString("en-US", {
      timeZone: "America/Toronto",
    });

    // Add a new row of data for each alert
    data.push([
      alert.sensor_id,
      message,
      ontarioTime,
    ])
  }

  // Sort the data array by date
  data = data.sort((a, b) => {
    return new Date(b[2]) - new Date(a[2]);
  });

  const sheetName = 'Alerts'; // replace with your sheet name

  // Get the spreadsheet details
  const spreadsheet = await sheets.spreadsheets.get({
    auth,
    spreadsheetId,
  });

  // Find the sheetId of the sheet with the given name
  const sheet = spreadsheet.data.sheets.find(sheet => sheet.properties.title === sheetName);
  const sheetId = sheet.properties.sheetId;

  // Insert rows into the spreadsheet
  await sheets.spreadsheets.batchUpdate({
    auth,
    spreadsheetId,
    resource: {
      requests: [{
        insertDimension: {
          range: {
            sheetId: sheetId,
            dimension: 'ROWS',
            startIndex: 1,
            endIndex: 1 + data.length
          },
          inheritFromBefore: false
        }
      }]
    }
  });

  // Update the spreadsheet with the new data
  await sheets.spreadsheets.values.update({
    auth,
    spreadsheetId,
    range: sheetName + '!A2', // assuming that your data starts from column A and the header is in the first row
    valueInputOption: 'USER_ENTERED',
    resource: { values: data },
  });

}

// Function to add sensor readings to a Google Spreadsheet
async function addSensorValuesToSheet(newReadings) {

  // Initialize an empty array to hold the data
  let data = [];

  // Loop through the readings array
  for(reading of newReadings) {

    // Grab the necessary values from the reading
    let tempC = reading.temperature;
    let tempF = (tempC * 9/5 + 32).toFixed(1); // Convert the temperature to Fahrenheit
    let humidity = reading.humidity;
    let checkin = reading.sensor_time;

    // Convert the check-in time to Ontario time
    let ontarioTime = new Date(checkin).toLocaleString("en-US", {
      timeZone: "America/Toronto",
    });

    let rssi = reading.rssi;
    let time_to_connect = reading.time_to_connect;
    let battery_pct = getBatteryPercentage(reading.voltage); // Get the battery percentage

    // Add a new row of data for each reading
    data.push([
      reading.sensor_id,
      tempC,
      tempF,
      humidity,
      rssi,
      time_to_connect,
      battery_pct,
      reading.offline,
      ontarioTime,
    ])
  }

  // Sort the data array by date
  data = data.sort((a, b) => {
    return new Date(b[8]) - new Date(a[8]);
  });

  const sheetName = 'Readings'; // replace with your sheet name

  // Insert rows into the spreadsheet
  await sheets.spreadsheets.batchUpdate({
    auth,
    spreadsheetId,
    resource: {
      requests: [{
        insertDimension: {
          range: {
            sheetId: 0,
            dimension: 'ROWS',
            startIndex: 1,
            endIndex: 1 + data.length
          },
          inheritFromBefore: false
        }
      }]
    }
  });

  // Update the spreadsheet with the new data
  await sheets.spreadsheets.values.update({
    auth,
    spreadsheetId,
    range: sheetName + '!A2', // assuming that your data starts from column A and the header is in the first row
    valueInputOption: 'USER_ENTERED',
    resource: { values: data },
  });

}

// Function to process the alerts
const processAlerts = async () => {
  // Fetch alert notifications
  const alerts = await getAlertNotifications();

  // Initialize an empty array for new alerts
  const newAlerts = [];

  // Loop over all alerts
  for(let alert of alerts) {
    // Check if the alert already exists in the history
    let alertExists = false;
    for(let existingAlert of dataHistory.alerts) {
      if(existingAlert.id === alert.id) {
        alertExists = true;
        break;
      }
    }

    // If the alert doesn't exist, add it to the new alerts array
    if (!alertExists) {
      newAlerts.unshift(alert);
    }
  }

  // Add new alerts to the history and limit history size to 1000 entries
  dataHistory.alerts = [...newAlerts, ...dataHistory.alerts];
  if(dataHistory.alerts.length > 1000) {
    dataHistory.alerts = dataHistory.alerts.slice(0, 1000);
  }

  // Add new alerts to the Google Sheet and save data
  await addAlertsToSheet(newAlerts);
  saveData();
};

// Function to process sensor data
const processSensors = async () => {
  // Fetch sensors data
  const sensors = await getSensors();

  // Process alerts after 60 seconds to allow for data to come in
  setTimeout(processAlerts, 60000);

  // If sensors are available
  if (sensors && sensors.length > 0) {
    let nextCheckIn = null;
    const now = new Date();
    const batchUpdate = [];

    // Loop over all sensors
    for (let sensor of sensors) {

      // If there's no history for this sensor, create a new array for it
      if(!dataHistory[sensor.sensor_id]) {
        dataHistory[sensor.sensor_id] = [];
      }

      // Get messages for each sensor
      const readings = await getMessages(sensor.sensor_id);
      const newReadings = [];

      // Loop over all readings
      for(let reading of readings) {
        reading.sensor_id = sensor.sensor_id;
        let readingExists = false;

        // Check if the reading already exists in the history
        for(let existingReading of dataHistory[sensor.sensor_id]) {
          if(existingReading.sensor_time === reading.sensor_time) {
            readingExists = true;
            break;
          }
        }

        // If the reading doesn't exist, add it to the new readings array and batch update array
        if (!readingExists) {
          newReadings.unshift(reading);
          batchUpdate.push(reading);
        }
      }

      // Add new readings to the history and limit history size to 1000 entries
      dataHistory[sensor.sensor_id] = [...newReadings, ...dataHistory[sensor.sensor_id]];
      if(dataHistory[sensor.sensor_id].length > 1000) {
        dataHistory[sensor.sensor_id] = dataHistory[sensor.sensor_id].slice(0, 1000);
      }

      // Calculate the next check-in time
      let sensorNextCheckIn = new Date(sensor.next_checkin + "Z");
      while (sensorNextCheckIn <= now) {
        sensorNextCheckIn = new Date(sensorNextCheckIn.getTime() + 60000);
      }
      if (!nextCheckIn || sensorNextCheckIn < nextCheckIn) {
        nextCheckIn = sensorNextCheckIn;
      }
    }

    // If there are any new readings, add them to the Google Sheet
    if(batchUpdate.length > 0) {
      await addSensorValuesToSheet(batchUpdate);
    }

    // Save the data
    saveData();

    // Calculate sleep time until the next check-in
    if (nextCheckIn) {
      let sleepTime = nextCheckIn.getTime() - now.getTime() + 60 * 1000;
      if(sleepTime < 0) {
        sleepTime = 15000;
      }

      // Delay the next sensor process by sleep time
      if (sleepTime > 0) {
        console.log(`Sleeping for ${sleepTime / 1000} seconds...`);
        setTimeout(processSensors, sleepTime);
      } else {
        // always sleep for 60 seconds although this should never process
        setTimeout(processSensors, 60000);
      }
    } else {
      console.log('No next check-in time. Exiting...');
    }
  } else {
    console.log('No sensors. Exiting...');
  }
};

// Start processing the sensors
processSensors();