Protegear API Usage

  • By Ulrich Schreiner
  • 13 min read
  • Tags: 
  • api
  • protegear

The Protegear platform provides a powerful web console which can be used to manage your data and devices. However, if you want to automate your data processing or integrate Protegear with other systems, you can use the Protegear API. This guide will show you how to use the API.

You can also use MQTT as a notification channel to receive real-time updates. This example will show you how to use MQTT to show the current location of a device as the device sends updates. You will also get notifications for every change in your data too, not just the location.

What is the Protegear API?

The Protegear API is a RESTful API which allows you to interact with the Protegear platform. It provides access to all the data and devices in your Protegear account. It is designed to be easy to use and understand, and it is compatible with a wide range of programming languages and tools.

In this example we will use Typescript as a language. We will use the axios library to make HTTP requests to the Protegear API and run the programm with deno. All the code is developed in a linux system; it shouldn't be a problem to run it on a windows system as well but it might require some adjustments.

Authentication

To use the Protegear API, you need to authenticate using an API key. You can easily log into the Protegear console, click on your account in the upper left corner and there you will get the option to copy your API key to the clipboard. If you ever think, your key is compromised, you can regenerate it in the console.

Usage

Please refer to the Protegear API documentation for more information on how to use the API. Every function is tagged with a role which you need to invoke. But you also sometimes need to pass the correct parameters to the function, otherwise some functions will work in ADMIN mode and you will not be able to use them. As a rule of thumb: If a function needs an input parameter for your organization, you should pass it. If a function needs an input parameter for a specific device, you should pass it as well.

The app ...

No, we don't want to develop an app. We will create a command line client which shows your owned devices, their activation state and their (optional) current location. You can select a device and change the activation state of the device. The activation process in protegear is an asynchronous process, so you will only create a job which will be executed later. As soon as the job changes the activation state of the device, you will receive a notification via MQTT.

App main view

This screen shows the main view. Here you see four devices with their IMEI, name, type and a position.

In the lower area you can select the actions for activating or deactivating the device.

This video shows all functions our example provides:

  • show all devices
  • show changes pushed by the server
    You see the device Jane Doe which is renamed with the web console to Grandma
  • activate a device
    The device usc-test-device2 which is red (inactive) is activated and the device switches to green (active).
  • show position updates
    You see the device usc-test-device2 which receives position updates while it is displayed in the list.

You could also trigger a deactivation, but this will not appear immediately because deactivations are exected at the end of the day.

Installation

In this example we will use deno as the runtime environment. You can install deno by following the instructions on the deno website.

Code

There is no need to create all the files. You can find the latest version of the code on github. The rest of this document will only contain snippets of the code which are relevant to the example but not usable as a standalone code.

As soon as you have it running, create a new directory for your code and add some libraries:

$ deno add npm:ink npm:mqtt npm:react npm:openapi-client-axios
Created deno.json configuration file.
Add npm:ink@6.3.0
Add npm:mqtt@5.14.1
Add npm:react@19.1.1
Add npm:openapi-client-axios@7.7.0

This creates the deno.json configuration file and adds the necessary libraries to your project. You also will have a deno.lock file which locks the versions of the libraries.

The openapi-client-axios library is used to generate the client code from the OpenAPI specification. But you do not need to generate anything; you simply create a client with the online swagger configuration and the library then creates the functions on the fly.

To connect to our API, you have to set an environment variable API_KEY with your API key which you can find in the Protegear console. If your key is exported, you can simply create the client with:

const baseURL = "https://protegear.io"
const api_key = Deno.env.get("API_KEY")
const c = new OpenAPIClientAxios(
    {
        definition: `${baseURL}/apidocs.json`,
        axiosConfigDefaults : {
            baseURL,
            headers: {
                common: {
                    'X-GST-Token' : api_key
                }

            }
        }
    }
)

const cl = await c.getClient()

This client can be used to call the Protegear API. The simplest function is the me function which returns data for the authenticated user (the user who has the API key).

const me = await cl.me()
console.log(me)

You should see the data of the user as a json object in the console.

{
  id: '5e012a01-9541-47fe-875c-4f1d72458d8c',
  changed_by: 'John Doe',
  changed: '2025-05-29T14:40:07.764Z',
  name: 'john-doe',
  ...
}

As we want the application to display all of our devices, we will use the getAllDevices function.

const mydevices = await cl.getAllDevices({ org: me.data.organization})
console.log(mydevices.data)

This will return all devices in the organization of the authenticated user.

[
  {
    id: '123123000',
    secondary_id: '',
    iccid: '',
    name: 'Safety Device',
    ...

If you try to query devices from another organization, you will get an error.

error: Uncaught (in promise) AxiosError: Request failed with status code 403

As you can see it is very easy to query all of your devices and use them in your application. But we want to display them, so let's use a library for displaying data in the terminal.

User interface

As this is a simple example, we are not using a full-fledged web framework. Instead we use the great ink library which uses the react library to render the UI.

The app is a small react application which uses the state to store the data.

import React, { useState, useEffect } from 'npm:react';
import { render, Text, Box,useInput, useApp } from 'npm:ink';

import { ProtegearAPI } from './protegear.ts'
import MainMenu  from './MainMenu.tsx'
import DeviceSelect from './DeviceSelect.tsx'

const Example = () => {
    const clientRef = React.useRef(null);
    const [devices, setDevices] = React.useState([]);
    const [selection, setSelection] = React.useState("");
    const [devInfo, setDevInfo] = React.useState({})
...
    React.useEffect ( () => {
        const f = async () => {
            const papi = await ProtegearAPI.create();
            clientRef.current = papi
            papi.devices().then(devs => {
                setDevices(devs)
            })
        }
        f()
    },[]);

The application uses an effect to query all devices at startup and store them in the state. This is done using the useEffect hook from the react library. I won't go into any details about react programming, but this effect is only run once when the component is mounted.

To render them, you can use react syntax:

<Box flexDirection="column" borderStyle="double">
  <Box>
    <Box width="18"><Text>ID</Text></Box>
    <Box width="30"><Text>Name</Text></Box>
    <Box width="22"><Text>Type</Text></Box>
    <Box width="18"><Text>Pos</Text></Box>
  </Box>
  {devices.map(d => {
    const lp = devInfo?.[d.id]?.latestPosition?.position;
    const lpinfo = lp ? `${lp.lat},${lp.lng}`:""
    return (
      <Box key={d.id}>
        <Box width="18">
          <Text
            backgroundColor={d?.configuration?.satellite ? "green":"red"}
            color={d?.configuration?.satellite ? "black":"white"}
          >{d.id}</Text>
        </Box>
        <Box width="30"><Text>{d.name}</Text></Box>
        <Box width="22"><Text>{d.type}</Text></Box>
        <Box width="18"><Text>{lpinfo}</Text></Box>
      </Box>
    )
  })}
</Box>

As the ink library does not support mouse, we have to implement a simple selection menu which displays our supported actions. The main menu will appear below the list of devices. If you select the Activate or Deactivate action, the main menu will be replaced by a list of devices.

Both lists will be implemented by a component; the code is not that complex. We will not show it here, but you can find it in the source code of the application. At the end, the main component will return the following below the device list:

{selection != "" ?
  <DeviceSelect onSelect={onDeviceSelected} devices={devices.filter(selectionFilter)} />
  :
  <MainMenu onQuit={exit} onActivate={activate} onDeactivate={deactivate} />
}

When you activate or deactivate a device, you first select this action and the system stores this action

const activate = () => {
    setSelection("active");
}

const deactivate = () => {
    setSelection("inactive");
}

const selectionFilter = (d: any) => {
    switch (selection) {
        case "active":
            return !d?.configuration?.satellite
        case "inactive":
            return !!d?.configuration?.satellite
        default:
            return false
    }
}

If there is a selection the main menu will be replaced by the device selection menu which only displays the devices that match the selection. The device selection calls the onDeviceSelected function when a device is selected or null, if no device is selected.

So last but not least, we have to activate or deactivate the device:

const onDeviceSelected = async (dev: any) => {
  if (!!dev) {
    selection == "active" && activateDevice(dev, true)
    selection == "inactive" && activateDevice(dev, false)
  }
  setSelection("")
}

const activateDevice = async (dev : any, active : boolean) => {
  try {
    const c = await clientRef.current.planConfigChange(dev.id, {
      "activation": active,
      "description": "Changed by TS example",
      "now": true,
      //"due":"YYYY-MM-DD" // at 00:00 UTC
    })
    return c
  } catch (e) {
    console.error(e.response.data)
  }
}

The onDeviceSelected checks if a device was selected and calls the activateDevice function with the device and the desired state. And here we do the work! We set the desired activation state, a description and in this case the now flag to true so the change will be applied immediately.

NOTE: A deactivation will always be delayed to the end of the day in UTC timezone. As soon as your device is active on one day, you have paid for it; it makes no sense to deactivate it before the end of the day.

As you can see in the comment, you could also add a due date in the form YYYY-MM-DD to set the change to be applied at a later date. This is useful if you want to deactivate a device for a specific period of time, for example, during a vacation or a business trip. In this case you have to set the now to false or remove it from the parameters (as false is the default). Please note that you can only set date's and no timestamps because all satellite platforms use UTC time for their 24h intervals, and they do not provide a way to set durations smaller than 24 hours.

You cannot create a config change on the same date. If you try to create a config change on the same date, the API will return an error.

{
  code: 'errDuplicateJob',
  error: 'job DEACTIVATE already exists at given date: "2025-09-10 00:00:00 +0000 UTC"'
}

You first have to delete the planned change before you can create a new one. You can do this with our API or you can use the Protegear web console to delete the change it is still planned or pending.

Server side updates

You have seen how to use our API to trigger a configuration change. But if you do this the change will be executed asynchronously. This means that the API will return a response immediately, but the change will be executed in the background. This happens if you set the now flag to true or false; the call will always be executed in the background. So if you want to display the status of the device you can simply poll the API regularly. But wait there is a better way: use our MQTT service!

With MQTT you can subscribe to server side events an will get notifications when some data changes. In our case, we are interested in two type of events:

  • the device configuration change
  • the device position change

The configuration of a device changes, when the planned action was executed. But the configuration is also changed if you rename the device in the web console. So we want to subscribe to the device configuration change event.

The position of a device changes, when the device sends a new position to our server. So we want to subscribe to the device position change event too.

MQTT connection

The protegear.ts file contains the code which connects to the MQTT service and subscribes to the events we are interested in.

async connectMQTT ({onDeviceChange,onDeviceInfo} : ServerEvents) : Promise<void> {
  const opts = { username: this.me.id, password: this.apikey}
  const mhost = Deno.env.get("PROTEGEAR_MQTT_HOST") ?? `wss://mqtt.${this.remotehost}`
  const mqttclient = mqtt.connect(mhost, opts);
  mqttclient.on("disconnect", () => {
    //console.log("disconnect")
  })
  mqttclient.on("close", () => {
    //console.log("close")
  })
  mqttclient.on("end", () => {
    //console.log("end")
  })
  mqttclient.on("reconnect", () => {
    //console.log("reconnect")
  })
  mqttclient.on("error", () => {
    //console.log("error")
  })

  mqttclient.on("connect", () => {
    const prefix = `org/${this.me.organization}`
    const subs = [`${prefix}/#`]
    mqttclient.subscribe(subs, e => !!e && console.error("mqtterror:" ,e))
  })

  mqttclient.on("message", (topic: string, message: any) => {
    const regDevice = /^org\/([\S]+)\/device\/([\w-]+)$/
    const regDeviceInfo = /^org\/([\S]+)\/device\/([\w-]+)\/information$/

    // empty message when a topic is deleted, here we are not interested
    if (!!!message) return

    //console.log(topic)
    if (topic.match(regDevice)) {
        onDeviceChange(JSON.parse(message))
    } else if (topic.match(regDeviceInfo)) {
        onDeviceInfo(JSON.parse(message))
    }

  })
  this.mqttclient = mqttclient
}

Here we use the mqtt library to create a websocket connection to our MQTT broker. The username is the user ID of the authenticated user and the password is the API key. In the connect event we subscribe to all topics of the organization; the # character is a wildcard that matches any topic and subtopic. This way we will receive all events from the organization, which are really a lot!

But as we are only interested in two of them, we filter them by the topic name in the message event handler with a regular expression. When the topic matches our regular expression, we call the corresponding callback function.

NOTE: It would be more efficient to only subscribe to the topics we are interested in, instead of subscribing to all topics of the organization. But as this is an example you can use this approach to get started and dump every invocation of a message to the console to see what is happening.

Receive updates from the server

To connect, we have to change the React-effect we used earlier to create the API client. We have to connect to the MQTT broker and subscribe to the topics we are interested in.

  const onDeviceChange = (d : any) => {
    setDevices (devs => devs.map(dev => dev.id == d.id ? d:dev))
  }

  const onDeviceInfo = (di : any) => {
    setDevInfo(old => ({...old, [di.imei]:di}))
  }

  React.useEffect ( () => {
    const f = async () => {
      const papi = await ProtegearAPI.create();
      await papi.connectMQTT({ onDeviceChange, onDeviceInfo })
      clientRef.current = papi
      papi.devices().then(devs => {
        setDevices(devs)
      })
    }
    f()
  },[]);

As you can see, we now call the connectMQTT function and pass the callback functions as arguments. This way we can handle the events from the MQTT broker and update the state accordingly.

Position updates

The Protegear server pushes position updates to the subtopic device/<imei>/information. The payload of the message is a JSON object with the following properties:

type DeviceInformation struct {
	PositionID      string       `json:"position_id"`
	ContactID       string       `json:"contact_id"`
	IMEI            string       `json:"imei"`
	Owner           string       `json:"owner"`
	Changed         time.Time    `json:"changed"`
	Received        time.Time    `json:"received"`
	LatestPosition  *Event       `json:"latestPosition,omitempty"`
	LatestContact   Event        `json:"latestContact"`
	Battery         *BatteryInfo `json:"battery,omitempty"`
	LastMessage     string       `json:"lastMessage"`
	LastMessageTime *time.Time   `json:"lastMessageTime,omitempty"`
	LocationHistory []Event      `json:"locationhistory,omitempty"`
}

The type Event contains a structure of data which is sent by the device. It contains a position, a timestamp and more optional data. This DeviceInformation contains a latestContact which is always filled and a latestPosition which is only filled if the device has sent a position update. So both fields are identical if the latest update was a position update; if the latest update did not contain a valid position, the latestPosition field will not be updated and will contain the old value (possibly null). When accessing the fields, be aware that the pointer values could be empty!

It is important to understand, that not every position update will be published with a MQTT push! Sometimes the server will receive many position updates within a few seconds and only push the latest one. For this case, the field locationhistory can be used to get the latest positions (up to 10). If you want everything else, you have to use the Protegear API.

If your device does not send position updates with such short intervals, this should never happen. But there are cases where devices do not have a online connection and buffer positions. They later send the buffer with the latest position first and older position afterwards. The serverside push of Protegear will update the latestPosition and will only update the locationHistory with the older ones. So you are on the right track if you want to use the latestPostion to always get the latest known position.

In the upper example, we access the locationPosition field to query the position. To be sure that the field is not empty, we check the values with

    const lp = devInfo?.[d.id]?.latestPosition?.position;
    const lpinfo = lp ? `${lp.lat},${lp.lng}`:""

If the position is available, we display it in the list of devices.

Notes about MQTT

You can connect to our MQTT broker using the following credentials:

  • Username: your_userid
  • Password: your_apikey

but this connection is read-only. You cannot publish any events to the broker. If you want to change data, you have to use the Protegear API.

If you write code with an MQTT connection please make sure to handle errors and reconnect if the connection is lost. We do not provide any guarantees about the stability of the MQTT broker. We recommend using a library that handles reconnecting automatically.

Conclusion

We hope that this example will help you to get started with the Protegear API and the serverside push. If you have any questions or need further assistance, please don't hesitate to ask. We are here to help!