NAV
Javascript

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?

Crepe