Introduction
Welcome to the Thrive API! You can use our API to access Thrive API endpoints, which can get information on various projects, devices, and statuses in our database.
We have language bindings in Javascript. You can view code examples in the dark area to the right, and you can switch the programming language of the examples with the tabs in the top right.
This example API documentation page was created with DocuAPI, a multilingual documentation theme for the static site generator Hugo.
Authentication
Retrieve token
function getJwtToken() {
return localStorage.getItem('token');
}
function saveJwtToken(token) {
localStorage.setItem('token', token);
}
function saveRefreshToken(refreshToken) {
localStorage.setItem('refreshToken', refreshToken);
}
function getRefreshToken() {
return localStorage.getItem('refreshToken');
}
async function loginDevice(deviceKey, apiKey) {
var loginResponse = await fetch(urlBase + '/api/deviceaccount/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
Credentials: {
DeviceKey: deviceKey,
ApiKey: apiKey
}
})
});
if (loginResponse.ok) {
var tokens = await loginResponse.json();
saveJwtToken(tokens.token);
saveRefreshToken(tokens.refreshToken);
} else {
throw new Error("Failed to login");
}
}
The above command returns JSON structured like this:
{
"token": "eyJhbGciOasdf7689IsIasdfas9.eyJodHRwOi8vc2NoZasdfIHRocmlkIHNpdGUiLCJodHRwasdOi8vc2NoZW1hcy54bWxsadfLm9yZy93cy8yxcbeMasDA1LzA1L2lkZW50aXR5L2NsYWltcW3Rlbnasdf87902UiOiIifsaE1NTg3Mjk5ODUsImV4cCI6MTU12ODczMDA0NX0._UxBuvqnBasd7f89yugasS4yL308",
"refreshToken": "REEk+gCZQ1hH21G2adsf7689dsfyCc/KnNZQ="
}
JSON to send would look like this:
{
“Credentials”: {
“DeviceKey”: “bc38f4ab-afc3-46dd-bd20-1af5egde6a97”,
“ApiKey”: “thriveIsAwesome”
}
}
HTTP Request
POST http://example.com/api/deviceaccount/login
Use token
function refresh(jwtToken, refreshToken) {
return fetch(urlBase + '/api/token/refresh', {
method: 'POST',
body: JSON.stringify({
Token: jwtToken,
RefreshToken: refreshToken
}),
headers: {
'Content-Type': 'application/json'
}
});
}
async function fetchWithCredentials(url, options) {
var jwtToken = getJwtToken();
options = options || {};
options.headers = options.headers || {};
options.headers['Authorization'] = 'Bearer ' + jwtToken;
var response = await fetch(url, options);
if (response.ok) { //all is good, return the response
return response;
}
if (response.status === 401 && response.headers.has('Token-Expired')) {
var refreshToken = getRefreshToken();
var refreshResponse = await refresh(jwtToken, refreshToken);
if (!refreshResponse.ok) {
return response; //failed to refresh so return original 401 response
}
var jsonRefreshResponse = await refreshResponse.json(); //read the json with the new tokens
saveJwtToken(jsonRefreshResponse.token);
saveRefreshToken(jsonRefreshResponse.refreshToken);
return await fetchWithCredentials(url, options); //repeat the original request
} else { //status is not 401 and/or there's no Token-Expired header
return response; //return the original 401 response
}
}
//then you could call it with something like this function
async function onPerformAuthenticatedRequest() {
console.log('');
var response = await fetchWithCredentials( urlBase +'/rest_of_path');
if (response.ok) {
console.log(await response.text());
} else {
console.log(`Request failed with status code: ${response.status}`);
}
}
Once you have recieved the token, now you will want to send it up with your requests.
You should attach it to your headers like so:
Authorization: ‘Bearer token_here’
Revoke token
async function onRevokeClicked() {
console.log('');
var revokeResponse = await revoke();
if (revokeResponse.ok) {
console.log('Refresh token was revoked, when the access token (JWT) expires authenticated requests will start to fail');
} else {
console.log(`Revoke failed with status code: ${revokeResponse.status}`);
}
}
function revoke() {
return fetchWithCredentials(urlBase + '/api/token/revoke', {
method: 'POST'
});
}
Sites
Create Site
The above command returns JSON structured like this:
{
"id": 1,
"siteName": "Plant 1",
"description": "prof plum",
"apikey": null,
"connectionString": null,
"devices": [],
"users": []
}
This endpoint creates a site. If an API key is sent up, then it will come back as “hidden”.
JSON to send would look like this:
{
“Site”:{
“SiteName”:“Plant 2”,
“Description”:“jimbo”,
“APIKey”: “hereIsALongStringAndNumbers08348u92234”
}
}
HTTP Request
POST http://example.com/api/sites
Devices
Get Device By Token
The above command returns JSON structured like this:
{
"id": 1,
"deviceKey": "24662232-7773-44c0-acc1-f1e7ff97765b",
"deviceName": "Thrive device name",
"equipNum": "0001",
"offlineThreshold": null,
"sitesId": 1,
"statusesId": 1,
"active": true,
"sites": null,
"statuses": null
}
This endpoint retrieves a device through authorization of sending your device token.
This endpoing is hit through a url parameter. See example below.
HTTP Request
GET http://example.com/api/device
Get Device By DeviceKey
The above command returns JSON structured like this:
{
"id": 1,
"deviceKey": "24662232-7773-44c0-acc1-f1e7ff97765b",
"deviceName": "Thrive device name",
"equipNum": "0001",
"offlineThreshold": null,
"sitesId": 1,
"statusesId": 1,
"active": true,
"sites": null,
"statuses": null
}
This endpoint retrieves a device with a friendly “key” name.
This endpoing is hit through a url parameter. See example below.
HTTP Request
GET http://example.com/api/device/deviceKey=<guid>
example:
GET https://example.com/api/device?deviceKey=24662232-7773-44c0-acc1-f1e7ff97765b
Create a new Device
The above command returns JSON structured like this:
{
"id": 1,
"deviceKey": "24662232-7773-44c0-acc1-f1e7ff97765b",
"deviceName": "Thrive device name",
"equipNum": "0001",
"offlineThreshold": 3600,
"sitesId": 1,
"statusesId": 1,
"active": true,
"sites": null,
"statuses": null
}
A POST to this endpoint creates a new device. If successful, you will get a url to perform
the GET request in the header in the location section. The return for this was
GET https://localhost:44320/api/Device?deviceKey=24662232-7773-44c0-acc1-f1e7ff97765b
JSON to send would look like this:
{
“Device”:{
“DeviceKey”:“24662232-7773-44c0-acc1-f1e7ff97765b”,
“DeviceName”:“Thrive device name”,
“EquipNum”: “0001”,
“StatusesId”: 1,
“Active”: true,
“OfflineThreshold”: 3600
},
“SiteData”:{
“SiteName”:“Insert_Site_Name_Here”,
“ApiKey”:“Insert_ApiKey_Here”
}
}
Note that OfflineThreshold is in seconds.
HTTP Request
POST http://example.com/api/device
Status
Create/Update Status
/// <summary>
/// Has an INTEGER datatype and represents different forms of machine status
/// Running = 1
/// Down = 2
/// Offline = 3
/// Unknown = 4
/// PoweredOff = 5
/// We will assume, unknown to start. This tells us that device is on and connected.
/// It will go to Offline if it can't connect and there is an offline threshold.
/// </summary>
function statusMachineRunning() {
machine_Status = 1;
statusMachineAction();
}
function statusMachineDown() {
machine_Status = 2;
statusMachineAction();
}
let newStatus = {
MachineStatus: {
Status: machine_Status,
Started_at: "",
Ended_at: "",
FK_downtime_reason_id: ""
}
};
const statusMachineAction = async () => {
try {
const response = await fetchWithCredentials(urlBase + '/api/status/', {
method: 'POST',
body: JSON.stringify(newStatus), // string or object
headers: {
'Content-Type': 'application/json'
}
});
if (response.status === 200) {
console.log("Post of status was successful");
} else {
throw new HttpError(response);
}
} catch (err) {
console.log(err);
}
};
The above command returns only a 200 response.
This endpoint creates or changes the status of a device.
JSON to send would look like this:
{“MachineStatus”:{“Status”:“1”,“Started_at”:“”,“Ended_at”:“”,“FK_downtime_reason_id”:“”}}
HTTP Request
POST http://example.com/api/status
Cycles
Report a cycle
let newCycle = {
MachineCycle: {
Created_at: "",
FK_machine_tag_id: "1",
Value: "42"
}
};
const cycleMachineAction = async () => {
try {
const response = await fetchWithCredentials(urlBase + '/api/cycle/', {
method: 'POST',
body: JSON.stringify(newCycle), // string or object
headers: {
'Content-Type': 'application/json'
}
});
if (response.status === 200) {
console.log("Post of cycle was successful");
} else {
throw new HttpError(response);
}
} catch (err) {
console.log(err);
}
};
The above command returns only a 200 response.
This endpoint adds a cycle to whatever machine the device is tied to.
If no “created_at” time is sent, then it will default to the current time.
JSON to send would look like this:
{“MachineCycle”:{“Created_at”:“”,“FK_machine_tag_id”:“1”,“Value”:“42”}}
HTTP Request
POST http://example.com/api/cycle
Ping
Ping the API for keep-alive functionality
/// <summary>
/// Has an INTEGER datatype and represents SECONDS
/// This is so that we know if the machine needs to check back with homebase to confirm online status.
/// The value is grabbed and assigned from the database using the device key
/// </summary>
var offlineThreshold = 0;
var machine_ID = 0; //Id of the machine in the Machine table
async function ping() {
if (offlineThreshold <= 0 || offlineThreshold === null || machine_ID === 0) {
console.log("there is no offlineThreshold (or machine_ID has not been set), do not start polling");
return false;
} else if (offlineThreshold < 30) {
console.log("setting the offline threshold to the minimum");
offlineThreshold = 30;
}
// https://stackoverflow.com/questions/55522330/fetch-call-every-2-seconds-but-dont-want-requests-to-stack-up/55522735?noredirect=1#comment97751123_55522735
// This promise will resolve when the network call succeeds
// Feel free to make a REST fetch using promises and assign it to networkPromise (remember, a fetch is a promise)
var networkPromise = pingApiAction();
// This promise will resolve when the offlinethreshold(seconds) * 1000 (becuase setTimeout is in miliseconds) have passed
// doing 5/6 of the offlineThreshold so that we have time to run our stored procedure.
var timeOutPromise = new Promise(function (resolve, reject) {
// set Second delay
setTimeout(resolve, offlineThreshold*1000*5/6, 'Timeout Done');
});
Promise.all(
[networkPromise, timeOutPromise]).then(function (values) {
console.log("Atleast the time out time + TTL (Network/server) has passed");
// Repeat
ping();
});
}
const pingApiAction = async () => {
try {
const response = await fetchWithCredentials(urlBase + '/api/ping/', {
method: 'PUT',
body: ""
headers: {
'Content-Type': 'application/json'
}
});
if (response.status === 201 || response.status === 200) {
const myJson = await response.json(); //extract JSON from the http response
// do something with myJson
console.log("Ping was successful");
console.log(myJson);
} else {
throw new HttpError(response);
}
} catch (err) {
console.log(err);
}
};
The above command should return the following JSON
This endpoint allows the device to participate in keep alive functionality. Thrive users can set an “offline threshold”. This value is returned in the GET machine endpoint.
No JSON is needed, only the header.
The API will the device is offline if there is an offlinethreshold set and more time has passed than when the device last checked in plus the threshold.
HTTP Request
PUT http://example.com/api/ping
Device Offline/Online best practices
If your device loses internet access, we recommend that you store your cycles that you have missed along with their time stamps. Then, once the device comes back online, send your cycles to the API to retroactively update the missed entries.
Additionally, when you come back online, we recommend that you send up a status to reestabilish the status of the machine the device is tied to.
CSTM Sargent
GET Device
When you GET on the device endpoint (the token/authenticated one), you will get a return of the following:
{
"device": {
"id": 1003,
"deviceKey": "822dfbb1-e514-4a55-aad3-f28555aa0672",
"deviceName": "Device Number 5",
"equipNum": null,
"offlineThreshold": 30,
"refreshToken": null,
"sitesId": 1003,
"statusesId": 3,
"active": true,
"sites": null,
"statuses": null
},
"machine": {
"id": 22,
"active": true,
"machineName": "Device Number 5",
"machineIpAddress": null,
"inputSelector": "1",
"fkMachineTypeId": 1,
"fkMachineGroupId": 1,
"deviceKey": "APIDevice_1003",
"equipNum": null,
"offlineThreshold": 3600,
"apideviceId": 1003,
"equipNumNavigation": null,
"fkMachineGroup": null,
"fkMachineType": null,
"cstmMachineCycleData": [],
"cstmMachineStatusLog": [],
"cstmMachineStatuses": []
},
"machineHrByHr": {
"secByCycle": 252
}
}
POST Device
A new device is created on the Thrive side as well as the API.
The above command returns JSON structured like this:
{
"device": {
"id": 1005,
"deviceKey": "9842110f-22da-400f-9871-4517081e6879",
"deviceName": "Device Number 15",
"equipNum": null,
"offlineThreshold": null,
"refreshToken": null,
"sitesId": 1003,
"statusesId": 1,
"active": true,
"sites": null,
"statuses": null
},
"machine": {
"id": 24,
"active": true,
"machineName": "Device Number 15",
"machineIpAddress": null,
"inputSelector": "1",
"fkMachineTypeId": 1,
"fkMachineGroupId": 1,
"deviceKey": "APIDevice_1005",
"equipNum": null,
"offlineThreshold": null,
"apideviceId": 1005,
"equipNumNavigation": null,
"fkMachineGroup": null,
"fkMachineType": null,
"cstmMachineCycleData": [],
"cstmMachineStatusLog": [],
"cstmMachineStatuses": []
}
}
Status
These also update the Thrive tables.
Cycle
These also update the Thrive tables.
Errors
The Kittn API uses the following error codes:
Error Code | Meaning |
---|---|
400 | Bad Request – Your request sucks |
401 | Unauthorized – Your credentials are wrong |
403 | Forbidden – You do not have access to this material |
404 | Not Found – The specified resource could not be found |
409 | Conflict – The resource already exists or conflicts with primary keys |
418 | I’m a teapot |
500 | Internal Server Error – We had a problem with our server. Try again later. |
503 | Service Unavailable – We’re temporarially offline for maintanance. Please try again later. |
A crepe for your troubles?