Temp Stick API Documentation
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#
https://tempstickapi.com/api/v1Authentication#
Every request must include your account API key in the X-API-Key HTTP header.
X-API-Key: YOUR_API_KEYYou 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.
Required and Recommended Headers#
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:
X-API-Key: YOUR_API_KEY
Accept: application/json
User-Agent: YourAppName/1.0
X-Client-Name: YourAppName
X-Client-Version: 1.0Recommended 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#
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:
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:
(CELSIUS * 1.8) + 32PHP conversion helper#
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:
2026-04-27T15:30:00Z
2026-04-27T15:30:00+00:00Convert 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
$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#
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#
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#
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
$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#
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#
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#
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 asMyTempStickApp/1.0. - Include
X-Client-NameandX-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-NameandX-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:
First retry: wait 2 seconds
Second retry: wait 5 seconds
Third retry: wait 15 seconds
Additional retries: wait longer, or stop and alert the userTesting 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-KeyAcceptUser-AgentX-Client-NameX-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 Alert
Get settings for a single alert
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
alert_id |
Number | Required | The ID of your alert |
Success Examples
{
"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
Get all your alerts
Success Examples
{
"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
Get the last seven days of alerts generated by the sensor
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
{
"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 the last seven days of alerts generated by all sensors
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
{
"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
Get settings for a specific sensor, *note: readings are shown in Celsius*
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
sensor_id |
String | Required | The ID of the sensor you want to get readings from |
Success Examples
{
"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"
}
]
}
}Update Sensor Settings
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
{
"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 Readings
Get the readings for a sensor given a specified period, *note: readings are shown in Celsius*
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
<?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;<?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
{
"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
Get all the sensors assigned to the account, *note: readings are shown in Celsius*
Success Examples
{
"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 Current User
Get your user information
Success Examples
{
"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 Timezones
Get current list of supported timezones
Success Examples
{
"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"
}
}Update User Display Preferences
Change display preferences including time zone and temperature format
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
{
"type": "success",
"message": "updated display preferences",
"data": {
"timezone": "America/New_York",
"temp_pref": "F",
"chart_fill": "1"
}
}Get Email Reports
Get email report settings and any downloadable reports
Success Examples
{
"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.
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
// 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();