# Ruuvi Developer Documentation

Lifecycle: Beta. This documentation collects the most important resources for developers into one place

This new documentation is work in process and may still have errors and inconsistencies. Each page has a lifecycle description under title to let users know the maturity of components.

* Proposal: Suggested feature, collecting feedback. Not implemented in any way.
* Alpha: Implementation exists to try out the feature. Might get cancelled, might have breaking changes. Not guaranteed to be available.
* Beta: Implementation is available, although on hardware there might be a waiting list. Breaking changes are avoided, but possible if something critical is found. Intended to move into production.
* In production: Mature feature which can be used as a part of your business.
* Deprecated: Feature will be removed in a future release. If you're using this feature, please migrate to a feature which is in production or contact Ruuvi team as soon as possible to discuss your needs in the future.
* Obsolete: Feature which used to be available, but has been removed. Documentation is maintained only for a reference.


# RuuviTag B

Lifecycle: In production


# RuuviTag Pro

Lifecycle: in Beta


# Ruuvi Air


# Ruuvi Gateway

Lifecycle: In Production


# Ruuvi DevShield

DevShield User Manual

The development shield allows easy access to the RuuviTag's [test points](https://lab.ruuvi.com/\[\[~36]]).

This enables easy debugging with a logic analyzer while developing drivers for sensors onboard or attaching external sensors to RuuviTag.

<figure><img src="/files/XcD3rUttuYbGoREeO4MZ" alt=""><figcaption></figcaption></figure>

We recommend attaching RuuviTag to the developer shield with a single zip tie. Use the holes marked in red.

It's highly recommended to keep the development board unpowered while adjusting the connections to avoid any short circuits.

### Do I need one?

If you're interested only in making a connection to RuuviTag's SWD *(Serial Wire Debug)* port for debugging purposes, this can also be done without a DevShield. See [RuuviTag's pinout](https://lab.ruuvi.com/\[\[~36]]) for more info.

You can use [Segger's J-Link RTT](https://www.segger.com/products/debug-probes/j-link/technology/real-time-transfer/rtt-viewer/) viewer and read the log messages in code through USB, or you can attach a USB-serial adapter to pins on the board and redirect the log messages to the USB port. Using SEGGER JLINK is detailed [here](https://blog.ruuvi.com/debugging-ruuvitag-fbb6a42371c). To get started, just set up the pins and RTT/UART as a log backend in *project/sdk\_application\_config.h* or *project/board/config/sdk\_board\_config.h*.

One interesting possibility is to activate GPIO pins during tasks in the program and then check those pins with a logic analyzer. This helps you to visualize where the program is spending most of its time and allows you to optimize parts of the code that matter the most.

When your code is ready, don't forget to check the power consumption with [Nordic Power Profiler Kit](https://www.nordicsemi.com/eng/Products/Power-Profiler-Kit). Example on using power profiler can be found [here](https://blog.ruuvi.com/power-profiling-718418ab9445). Once you're happy with the application, you can [release the program binaries](https://blog.ruuvi.com/preparing-binaries-2b76ab734a7e) to wild.


# TMP 117 External Sensor


# Ruuvi Connector System

Lifecycle: in production

Ruuvi Connector is a standardised expansion connector and cable system that takes sensor prototyping to the next level. Compatible connectors will be found on many upcoming Ruuvi products.

Ruuvi Connector cables have 8 pins (6 signals + VDD + GND). This allows using not just simple I2C sensors but also more powerful SPI sensors with interrupt signals.

Ruuvi Port is also compatible with 4-pin (2 signals + VDD + GND) prototyping systems such as Seeed Studio Grove and Sparkfun Qwiic.

![Ruuvi Connector cables](/files/-LvnpGPfL78dtdawCIsx)

### Many ways to use Ruuvi Connector system

1. Connect compatible external boards and sensors to your Ruuvi products
2. Design your own expansion boards
3. Feed your battery powered Ruuvi product from an external power source
4. Add a Ruuvi Connector compatible connector on your own product and make it compatible with all the external Ruuvi / Grove / Qwiic sensors.


# Ruuvi Connector Kit


# Expansion boards

Lifecycle: proposal

To make Ruuvi Connector System as flexible as possible, several adapter boards and standardised circuit boards will be made available.

### Standard expansion board dimensions

1. Small
2. Middle
3. Big

### External Ruuvi sensor boards

Various external Ruuvi Connector System compatible sensors are coming.

### Adapter boards

With Grove and Qwiic adapter boards you can connect almost any available sensor boards.


# Cables

Lifecycle: proposal

This page will have specs of the cables.


# Connectors

Lifecycle: Proposal

Details of the supported connectors.


# 1.2.12

Lifecycle: Obsolote

Ruuvi Firmware 1.2.12 was the first firmware to be continuously produced for RuuviTags shipped from Ruuvi Shop. The factory firmware was updated on 2019-12 and no new tags with this firmware is produced anymore, but you might still find the FW on some tags.

You can check the firmware version with a NFC read, if you have this firmware please update to 2.5.9 with instructions at lab.ruuvi.com/dfu.

| Feature                      | Status                                                                                                                                                                                        |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **NFC**                      | Supported, NFC read may hang in rare cases and reboot is required.                                                                                                                            |
| Record 0: Text, "id"         | "ID: " + XX:XX:XX:XX:XX:XX:XX:XX representing tag ID, 8 bytes.                                                                                                                                |
| Record 1: Text, "ad"         | "MAC: " + XX:XX:XX:XX:XX:XX representing tag Bluetooth address, 6 bytes.                                                                                                                      |
| Record 2: Text, "sw"         | "SW: Ruuvi 1.2.12"                                                                                                                                                                            |
| Record 3: Text, "dt"         | "Data:"                                                                                                                                                                                       |
| **Bluetooth**                | Non-connectable, non-scannable advertisements are supported.                                                                                                                                  |
| URL advertising (format 4)   | Supported, 2 Hz advertising. Green led blinks.                                                                                                                                                |
| RAWv1 advertising (format 3) | Supported, 1 Hz advertising. Red led blinks.                                                                                                                                                  |
| **Button**                   | One software button + reset button supported.                                                                                                                                                 |
| "B" short press              | Chage between advertising mode 1, 2                                                                                                                                                           |
| "B" pressed on boot          | Enter bootloader                                                                                                                                                                              |
| "R" sort press               | Reboot                                                                                                                                                                                        |
| Hold down "B", press "R"     | Enter bootloader                                                                                                                                                                              |
| **Sensors**                  | **A**cceleration, **H**umidity, **T**emperature, **P**ressure, **V**oltage                                                                                                                    |
| BME280 (H, T, P)             | Supported, continuous mode, 1 Hz, 16x IIR low-pass filtering, \~60 seconds step response                                                                                                      |
| LIS2DH12 (A)                 | Supported, continuous mode, 1 Hz, no filtering, \~0.5 Hz bandwidth.                                                                                                                           |
| nRF52 (T, V)                 | Supported, measures temperature only if BME280 is not present on board. Battery voltage measurement is not synchronized to loading of device which may cause noisy readings or missed droops. |
| **Battery life**             |                                                                                                                                                                                               |
| URL mode                     | 2 years expected in room temperature                                                                                                                                                          |
| RAW mode                     | 3 years expected in room temperature                                                                                                                                                          |

![Firmware flowchart](/files/-M9ccu7C8G3tfM8FrsuS)


# 2.5.9


# 3.X

Lifecycle: Alpha. Last updated 2020-09-10

The 3.x firmwares are the bleeding edge versions, which means that they have new features but also they cannot be considered stable. Always confirm with Ruuvi team before starting to work on top of 3.x to avoid any surprises in development, or fork the project to yourself to maintain a stable version.

The firmware supports nrF52832 and nRF52811 officially, and it runs on nRF52840. Firmware is divided into one main repository which contains subrepositories. The reasoning for the separation is to support several different firmware and board variants and to allow all the projects contribute fixes to a common codebase.

The main features of the firmware are based around sensor functionality: The firmware beacons sensor data and keeps a short internal buffer of historic values. The historic values can be read over Bluetooth GATT connection. Certain settings can also be configured via GATT connection.


# 3.x Sensors

Lifecycle: in production

Each firmware targets a board with knowledge of what sensors there might be, and firmware manages missing sensors and overlapping data providers transparently for the user.

For example a board might have high-precision temperature sensor TMP117, temperature and humidity sensors SHTC3, pressure sensor DPS310, combined temperature, pressure and humidity sensor BME280 and accelerometer LIS2DH12 or accelerometer LIS2DW12.

At boot the board checks each sensor at the pinout where it might be, and if sensor is found it will be initialized into state stored to flash. If there isn't a state stored to flash, the sensor will be booted into default settings and defaults are stored to flash.

When application requires temperature, humidity, pressure and acceleration data, the sensors found at boot are queried in the order of initialization until all requested data is available or no more sensors remain for checking.

<div align="center"><img src="/files/-MBKo3HI8wk9ZRR9TbEu" alt="Sensor data flow"></div>

Some sensors, such as accelerometer, need to be constantly on to generate interrupts on events or to maintain digital signal processing to improve the quality of measurements. Others can be powered off until a new sample is needed. Generally sensors which are read rarely should be in a single-shot mode and sensors which are read at high frequency should be in continuous mode.

## Test checklist

| Event | Test                                           | Version | CI / Manual                                                                                                              |
| ----- | ---------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
| Boot  | Sensor configuration is loaded from flash.     | 3.29+   | [CI](https://github.com/ruuvi/ruuvi.firmware.c/blob/9d8a7a862be9ad6ac6ab3676c55f873f6ba2d0a7/test/test_app_sensor.c#L63) |
| Boot  | Sensor data is read in initialization priority | 3.29+   |                                                                                                                          |
| Read  | Single-shot sensors are powered up             | 3.29+   |                                                                                                                          |
| Read  | Single-shot sensors take a new sample          | 3.29+   |                                                                                                                          |
| Read  | Single-shot sensors are powered down           | 3.29+   |                                                                                                                          |


# 3.x Heartbeat

Lifecycle: in production

The sensors periodically send their data through all channels they can: this is known as sensor heartbeat. The sensors try broadcast the data, if GATT is connected the sensor data is sent via GATT and NFC data field is updated to include the latest data.

On GATT the data is sent via Nordic UART Service TX Characteristic notifications. Bluetooth advertisements have the data encoded in Manufacturer Specific payload. NFC has the data as a binary in field utf-8 field with language "dt".

The heartbeat also feeds the watchdog of the application, if the data updates stop for any reason the application assumes that it has crashed and reboots.


# Device Firmware Update (DFU)

Lifecycle: In production

There are two different options on how to update RuuviTag firmware.

### OTA (Over the Air)

### **Ruuvi firmware version 1.x, 2.x**

Ruuvitag firmware version up until 2.x are built on top of Nordic SDK 12.3 / Softdevice s132 v3.1.1. The bootloader on these RuuviTags uses debug-mode which skips hardware and firmware version checks.

The keyfile to sign the RuuviTag updates is published in [ruuvitag\_fw](https://github.com/ruuvi/ruuvitag_fw/tree/master/keys) repository.

| Command to generate DFU package for RuuviTags up to firmware version 2                                                                                                                   |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nrfutil pkg generate --debug-mode --application _build/ruuvi_firmware.hex --hw-version 3 --sd-req 0x91 --key-file ~/git/ruuvitag_fw/keys/ruuvi_open_private.pem ruuvi_firmware_dfu.zip` |

To upload package to RuuviTag, press button "B" and tap "R" to reset the tag. Bootloader checks if button "B" is pressed at boot and enters the bootloader mode.

### **Ruuvi firmware version 3.x**

Ruuvi firmware 3 builds on SDK15.3 and Softdevice s132 v6.1.1. There are special edition RuuviTags with nRF52811 that do not have bootloader at all due to size constraints.

The bootloader is a production version and it enforces version checks. On RuuviTag B1 ... B7.1 the hardware version is 0xB0, regardless of which sensors are installed.

| Command to generate DFU package for RuuviTags with firmware version 3                                                                                                          |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `nrfutil pkg generate --application _build/nrf52832_xxaa.hex --application-version 1 --hw-version 0xB0 --sd-req 0xB7 --key-file ruuvi_open_private.pem ${BINNAME}_dfu_app.zip` |

In bootloader mode the tag advertises itself with name "RuuviBoot" and it provides [BLE DFU Service](https://infocenter.nordicsemi.com/topic/com.nordic.infocenter.sdk5.v12.3.0/group__nrf__ble__dfu.html?cp=7_5_8_6_8_4_0). Nordic Semiconductor provides libraries for [iOS](https://github.com/NordicSemiconductor/IOS-Pods-DFU-Library) and [Android](https://github.com/NordicSemiconductor/Android-DFU-Library) to interface with the service.

### SWD (Serial Wire Debug)

If you're developing your own firmware or need to flash hunderds or more of RuuviTags, you should consider using a wired programmer for higher programming speed. To create your own hex image for flashing, you need 4 parts:

* Softdevice
* Bootloader
* Bootloader settings
* Application

The softdevice is given by Nordic Semiconductor or other company which provides the radio protocol.

You can use Ruuvi's bootloader available at <https://github.com/ruuvi/ruuvi.nrf5_sdk15_bootloader.c>, but you should at least change the public key used for verifying the firmware images to ensure that you can keep the full control of your tags.

Settings are generated with nrfutil, which is part of [Nordic Command Line Tools](https://www.nordicsemi.com/Software-and-tools/Development-Tools/nRF-Command-Line-Tools). You'll also need nrfjprog and mergehex from the command line tool package.

Application is the .hex file you have created.

| Commands to generate application settings and merge them into one package                                                                                             |
| --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `nrfutil settings generate --family NRF52 --application _build/nrf52832_xxaa.hex --application-version 1 --bootloader-version 1 --bl-settings-version 1 settings.hex` |
| `mergehex -m ../../../../nRF5_SDK_15.3.0_59ac345/components/softdevice/s132/hex/s132_nrf52_6.1.1_softdevice.hex $BOOTLOADER settings.hex -o sbc.hex`                  |
| `mergehex -m sbc.hex _build/nrf52832_xxaa.hex -o packet.hex`                                                                                                          |

| Commands to flash the package to RuuviTag |
| ----------------------------------------- |
| `nrfjprog --eraseall`                     |
| `nrfjprog --program packet.hex`           |
| `nrfjprog --reset`                        |

If your firmware is secret or you wish to avoid duplications by users, you should enable the read protection bit .

`nrfjprog -f nrf52 --rbp ALL`

If you have enabled the read protection, you must run `nrfjprog --recover` to use the programming interface again. This completely erases the tag.

**Always power cycle the tag after using a wired programmer. Otherwise the debug interface might remain active and consume excess current.**


# Air 1st Stage Bootloader

The 1st stage bootloader is minimal, immutable firmware which initializes the board, provides a factory recovery option and starts the more sophisticated second stage bootloader.

The bootloader is shipped with two cryptographic keys: Development and Production \[TODO link public keys]. Development key is shared to the world, and anyone can create their own firmware variants with development key. Production keys are internal to Ruuvi only for the security of users.

By default, Ruuvi Air ships with only production key enabled. To enable the development key, factory reset must be executed. After factory reset, development keys remain enabled until production key is used for the first time. Development keys can be re-enabled with a factory reset.

The flowchart of operation is below, legend:

White: Ready, Green: Being tested, Blue: TODO. As of Air fw v0.1.5

<figure><img src="/files/XVGd1adESKFKKV0erclA" alt="Air 1st stage bootloader. Press and hold button for at least 10 seconds while connecting power to run factory reset"><figcaption></figcaption></figure>

The first stage bootloader executes quickly, the button has to be pressed at powerup or reboot for the button to be registered. To run a factory reset, press the button for at least 10 seconds until bottom leds start alternating green-led blink sequence.

To enter firmware loader for uploading firmware, release the button before 10 seconds have passed.

Source code is available at <https://github.com/ruuvi/ruuvi.air.b0>


# Air 2nd Stage Bootloader

The Ruuvi Air second stage bootloader is based on [MCUboot](https://docs.mcuboot.com/) and it handles cryptographic security of installed firmware images as well as validates any updates to be installed.

There are two slots for second stage bootloader, which allows for updating the second stage over the air without risking bricking the OTA update capablity. The first stage is responsible for selecting the instance to run.

The second stage bootloader is responsible for installing update to main application or firmware loading application as well as selecting which one to start.

The source code is available at <https://github.com/ruuvi/ruuvi.air.mcuboot>


# Air Firmware Loader

The firmware loader is a self-contained minimal program which allows for receiving firmware update images over the air. The received images are stored in flash memory and after a reboot the second stage bootloader validates and installs the image.

Generally the firmware loader should not be used in normal operation of Ruuvi Air, it is a backup option for recovery update in case a firmware update breaks the main application somehow.

If both main application and firmware loader are broken, a factory reset can be executed by holding down button on Ruuvi Air for at least 15 seconds. After factory reset, the device is back to initial state and can receive the updates.

The source code is available at <https://github.com/ruuvi/ruuvi.air.fw_loader>


# Air Main Firmware

The main Ruuvi Air application. Ruuvi Air firmware initializes the sensors onboard and starts to advertise the sensor data. Additionally, the Ruuvi Air logs the environmental data for 10 days at 5 minute interval. This stored data persists across short power offs, but data can be logged only while device is powered.

The boot up takes \~6 seconds to start blinking the main led in blue, and around 10 seconds from that before device is ready to measure. The device calibration will improve over the first hour of operation and the accuracy specification of datasheet is matched within one hour.

Additionally the Ruuvi Air will learn and adapt to it's environment, this adaptation process takes one week. To ensure most accurate results, the environment needs clean air at least once in a rolling one week period - e.g. through ventilation of home or office left empty for the weekend.

The device LED will indicate the air quality through it's color. The main led brightness can be adjusted with short press of the device button, the brightness cycles in medium-low-off-bright-medium cycle.

Firmware updates can be done in Ruuvi Station app. It is possible to enter dedicated firmware loader by pressing and holding the button for 5 seconds - once main led turns off the press is complete.

If the entire device is stuck, a factory reset is possible by pressing and holding the button for 15 seconds. When factory reset is started, bottom leds will blink alternately green-red and button can be released.

<figure><img src="/files/V6mSnrEfJzhcZYHcbOVS" alt=""><figcaption></figcaption></figure>

Ruuvi Air code is available at <https://github.com/ruuvi/ruuvi.air.main>


# Ruuvi Indoor Air Quality Score (IAQS)

The Ruuvi Indoor Air Quality Score (IAQS) is designed to provide a quick and repeatable overview of the healthiness of current indoor air conditions. The index does not account for temperature or humidity, as these are more closely related to comfort and structural maintenance than to direct health effects. The IAQS considers CO₂ and particulate matter (PM₂.₅), as both can be measured as absolute values and their effects on health and well-being are well studied. The index is calculated by measuring each component’s deviation from its ideal value and using geometric distance to compute a combined “total difference” from the ideal.

<table><thead><tr><th width="122">Grade</th><th width="176">IAQS</th><th width="191">CO₂ (ppm)</th><th>PM₂.₅ (μg/m³)</th></tr></thead><tbody><tr><td>Excellent</td><td>90 ≤ IAQS ≤ 100</td><td>CO₂ ≤ 600</td><td>PM₂.₅ ≤ 6</td></tr><tr><td>Good</td><td>80 ≤ IAQS &#x3C; 90</td><td>600 &#x3C; CO₂ ≤ 800</td><td>6 &#x3C; PM₂.₅ ≤ 12</td></tr><tr><td>Fair</td><td>50 ≤ IAQS &#x3C; 80</td><td>800 &#x3C; CO₂ ≤ 1400</td><td>12 &#x3C; PM₂.₅ ≤ 30</td></tr><tr><td>Poor</td><td>10 ≤ IAQS &#x3C; 50</td><td>1400 &#x3C; CO₂ ≤ 2100</td><td>30 &#x3C; PM₂.₅ ≤ 55</td></tr><tr><td>Very Poor</td><td>0 ≤ IAQS &#x3C; 10</td><td>2100 &#x3C; CO₂</td><td>55 &#x3C; PM₂.₅</td></tr></tbody></table>

Expressed graphically, the IAQS maps CO₂ and PM₂.₅ as shown below:

<figure><img src="/files/u6Jzw76pQKSHkGi3gCQa" alt=""><figcaption></figcaption></figure>

The IAQS is calculated using the following formula:

```
const AQI_MAX    = 100;

const PM25_MAX   = 60,  PM25_MIN = 0;
const PM25_SCALE = AQI_MAX / (PM25_MAX - PM25_MIN);   // ≈ 1.6667

const CO2_MAX    = 2300, CO2_MIN = 420;
const CO2_SCALE  = AQI_MAX / (CO2_MAX - CO2_MIN);     // ≈ 0.05319

function clamp(x, lo, hi){ return Math.min(Math.max(x, lo), hi); }

function calc_aqi(pm25, co2) {
  if(isNaN(pm25) || isNaN(co2)) { return NaN; }

  pm25 = clamp(pm25, PM25_MIN, PM25_MAX);
  co2  = clamp(co2,  CO2_MIN,  CO2_MAX);

  const dx = (pm25 - PM25_MIN) * PM25_SCALE; // 0..100
  const dy = (co2  - CO2_MIN)  * CO2_SCALE;  // 0..100

  const r  = Math.hypot(dx, dy);             // sqrt(dx*dx + dy*dy)
  return clamp(AQI_MAX - r, 0, AQI_MAX);
}
```

Ruuvi Station apps and Ruuvi Air display the index grade based on the value rounded to the nearest integer. For example, a value of 89.6 is rounded to 90 and displayed as turquoise (“Excellent”), even though the raw value is slightly below the threshold. History graphs also use the rounded color classification; for instance, a sequence of values 89.4, 89.5, and 89.6 transitions from green to turquoise at 89.5.


# GW Getting Started

## Quickstart

1. Turn on Ruuvi Gateway by powering it with USB-C.

   \- **Quickstart using Ethernet**: Connect Gateway to your router with Ethernet cable, wait one minute, and you're connected using default settings.\
   \- **Quickstart using Wi-Fi**: Activate WPS on your router by pressing the WPS button (make sure that the WPS is active on 2.4 GHz Wi-Fi).

   \- **Note:** Ethernet cable must not be connected before the configuration is complete if you do not want to use default settings.
2. Start configuration by connecting to Wi-Fi hotspot "Configure Ruuvi Gateway XXXX".

   \- If the browser doesn't open automatically, type <http://10.10.0.1> into the address bar.
3. Follow the steps in the configurator to complete the setup with your preferred settings.
4. The Gateway is now ready to use and will send data to Ruuvi Cloud or a third-party server according to your configured settings.

## LEDs

Ruuvi Gateway has 4 LEDs: Green, Red, Ethernet green, and Ethernet yellow.

The green LED is on when receiving data from Bluetooth sensors and communicating with the cloud server.

If the red and/or green LEDs are blinking, it means that something is wrong. For more details, see[GW ESP32 LED](/ruuvi-gateway-firmware/gw-esp32-firmware/gw-esp32-led).

Ethernet Yellow turns on when a cable connection is detected and blinks on data transfer. Green led turns on if Gateway operates at a faster 100 MBit/s speed instead of legacy 10 MBit/s. Most modern routers will have the green LED on at all times.

## Button

A short press of the button (for example with a paperclip) re-enables the Gateway's configuration mode.

Keeping the button pressed down for 7+ seconds triggers a factory reset which erases all user-configured settings and enables configuration mode (activates Wi-Fi hotspot). After pressing the button, you need to wait until the red LED flashes at a rate of 2.5 Hz, then release the button. After that, the Gateway will restart with the active Wi-Fi access point.

For more details, [GW ESP32 Configuration](https://github.com/ruuvi/docs/blob/master/ruuvi-gateway-firmware/broken-reference/README.md) page is a good place to start. If you're interested in using Ruuvi Gateway with your own server, review [GW ESP32 HTTP Client](/ruuvi-gateway-firmware/gw-esp32-firmware/gw-esp32-http-client) and [GW ESP 32 MQTT Client](/ruuvi-gateway-firmware/gw-esp32-firmware/gw-esp32-mqtt-client) pages for details on how data is sent to the Internet.

## Using the Gateway with your own server

If you want to use the Gateway with your own server, you can connect via MQTT or HTTP(S). The data formats are detailed on the following pages:

* [HTTP: Time-stamped data from Bluetooth-sensors](/ruuvi-gateway-firmware/data-formats/http-time-stamped-data-from-bluetooth-sensors)
* [HTTP: Data from Bluetooth-sensors without timestamps](/ruuvi-gateway-firmware/data-formats/http-data-from-bluetooth-sensors-without-timestamps)
* [MQTT: Time-stamped data from Bluetooth-sensors](/ruuvi-gateway-firmware/data-formats/mqtt-time-stamped-data-from-bluetooth-sensors)
* [MQTT: Data from Bluetooth-sensors without timestamps](/ruuvi-gateway-firmware/data-formats/mqtt-data-from-bluetooth-sensors-without-timestamps)
* [HTTP: Gateway status](/ruuvi-gateway-firmware/data-formats/http-gateway-status)

## Using the Gateway with Ruuvi Station app

Please check the [Ruuvi Station instruction page](https://docs.ruuvi.com/ruuvi-station-app/use-with-ruuvi-gateway-ruuvi-network).


# GW Web-UI

## User interface

Ruuvi Gateway sets up a WiFi hotspot and a webserver that provides the user interface for configuration over HTTP. The user interface can be accessed in any major browser. To connect to the user interface connect to the hotspot "**Configure Ruuvi Gateway XXXX**" (it does not require a password). Once connected, open your browser and enter **<http://10.10.0.1>** . After the initial configuration is complete, you will be able to access the gateway from the LAN in the same way.

* [Greeting window](/ruuvi-gateway-firmware/gateway-html-pages/greeting-window)
* [Internet connection settings](/ruuvi-gateway-firmware/gateway-html-pages/internet-connection-settings)
  * [Connection via Wi-Fi](/ruuvi-gateway-firmware/gateway-html-pages/internet-connection-settings/connection-via-wi-fi)
  * [Connection via Ethernet](/ruuvi-gateway-firmware/gateway-html-pages/internet-connection-settings/connection-via-ethernet)
* [Software update](/ruuvi-gateway-firmware/gateway-html-pages/software-update)
* [Automatic configuration download](/ruuvi-gateway-firmware/gateway-html-pages/automatic-configuration-download)
* [Automatic updates](/ruuvi-gateway-firmware/gateway-html-pages/automatic-updates)
* [Access Settings from LAN](/ruuvi-gateway-firmware/gateway-html-pages/access-settings-from-lan)
* [Cloud Options](/ruuvi-gateway-firmware/gateway-html-pages/cloud-options)
  * [Backend: HTTP(s)](/ruuvi-gateway-firmware/gateway-html-pages/cloud-options/backend-http-s)
  * [Backend: MQTT(s)](/ruuvi-gateway-firmware/gateway-html-pages/cloud-options/backend-mqtt-s)
  * [Backend: Statistics](/ruuvi-gateway-firmware/gateway-html-pages/cloud-options/backend-statistics)
* [Time Synchronisation Options](/ruuvi-gateway-firmware/gateway-html-pages/time-synchronisation-options)
* [Bluetooth Scanning Settings](/ruuvi-gateway-firmware/gateway-html-pages/bluetooth-scanning-settings)
* [Configuration completion](/ruuvi-gateway-firmware/gateway-html-pages/configuration-completion)
* [Authentication when accessing from LAN](/ruuvi-gateway-firmware/gateway-html-pages/auth.html)


# Greeting window

<figure><img src="/files/G4u0IUsAT45yAC8miNVs" alt=""><figcaption></figcaption></figure>


# Internet connection settings

[Connection via Wi-Fi](/ruuvi-gateway-firmware/gateway-html-pages/internet-connection-settings/connection-via-wi-fi)

[Connection via Ethernet](/ruuvi-gateway-firmware/gateway-html-pages/internet-connection-settings/connection-via-ethernet)


# Connection via Wi-Fi

<figure><img src="/files/yn0uJtRvPvLV7zlI08mW" alt=""><figcaption></figcaption></figure>

Select the desired Wi-Fi and enter the password:

<figure><img src="/files/jwkYkDEuyBiN2H11SHzy" alt=""><figcaption></figcaption></figure>

If your router supports WPS (Wi-Fi Protected Setup), you can connect to a Wi-Fi hotspot without entering a password. Enable the checkbox "**Use WPS to connect to Wi-Fi network**", press the "**Next**" button and then press the WPS button on your router.

<figure><img src="/files/qsOIfGoFFKmS5D4TzD5J" alt=""><figcaption></figcaption></figure>

In case Wi-Fi is hidden, you can enter WiFi SSID manually - this option is available under "Advanced settings":

<figure><img src="/files/G2Gz1kN9LvwWszz35RHM" alt=""><figcaption></figcaption></figure>


# Connection via Ethernet

<figure><img src="/files/6KrvRX3CYbsYwDBfLa42" alt=""><figcaption></figcaption></figure>

By default DHCP is enabled, so you don't need to configure anything else:

<figure><img src="/files/2r7LJUpo6mfgmRCL9ZBk" alt=""><figcaption></figcaption></figure>

But you can always set the network settings manually, if necessary:

<figure><img src="/files/tVKNLLogJylICXZE5lrL" alt=""><figcaption></figcaption></figure>

In the next step, Ruuvi Gateway will ask you to connect the Ethernet cable (but that's okay if it's already connected):

<figure><img src="/files/CTxHvIXWN02xYvTz7rCZ" alt=""><figcaption></figcaption></figure>

if the Ethernet cable has not been connected in 30 seconds, an error message will be displayed. In this case, you can go back and try again.

<figure><img src="/files/gqvrwgJpqD5i5gI7UkEp" alt=""><figcaption></figcaption></figure>


# Software update

In this step, Ruuvi Gateway checks for software updates.

<figure><img src="/files/jg7Zji3cD2jHDNHRBvqL" alt=""><figcaption></figcaption></figure>

If a newer version is available, the gateway will offer to update the firmware:

<figure><img src="/files/eYak6NhTxPrl5dcQiLs9" alt=""><figcaption></figcaption></figure>

You can set up your own server for firmware updates, and you can configure it's address in the "**Advanced settings**" section:

<figure><img src="/files/XB4lwHQ8BU6MFXiFxwwq" alt=""><figcaption></figcaption></figure>

You can also install earlier or beta versions of the software by providing the URL to the required firmware. If you have built your own firmware, you can run HTTP server and provide the URL to your firmware binaries (See [GW Install custom firmware](/ruuvi-gateway-firmware/gw-install-custom-firmware)).

<figure><img src="/files/s3V1DRcHNru7JFrj5vbw" alt=""><figcaption></figcaption></figure>

Here is an example of the software update process:

<figure><img src="/files/9Co3MORts16KziMtWnLp" alt=""><figcaption></figcaption></figure>

After the software update is completed, Ruuvi Gateway will be restarted in a few seconds, and the following page will be displayed:

<figure><img src="/files/FEJpJJ0XdYwoA3kSg9Ro" alt=""><figcaption></figcaption></figure>


# Automatic configuration download

Ruuvi Gateway can automatically download its configuration from a remote server, you can enable this feature on this page:

<figure><img src="/files/ba65vHcgbBUoLGaHOQWD" alt=""><figcaption></figcaption></figure>

You need to specify the base URL from where gw\_cfg.json with the Gateway settings can be downloaded.

If you specify a folder name as the base URL (any URL that does not end with '.json'), it will first attempt to read the configuration file for the Gateway from \<GW\_MAC>.json (e.g. AABBCCDDEEFF.json). If this file fails, it will attempt to read the overall configuration from gw\_cfg.json.

If there are several Ruuvi Gateways in the network requesting configuration from a remote server, in some cases it may be convenient to request a file with a fixed name, but the content will be unique for each Ruuvi Gateway. In this case, the configuration file can also be generated dynamically by the server, where the server extracts the Gateway's MAC address from the HTTP request header named 'ruuvi\_gw\_mac' in the format XX:XX:XX:XX:XX:XX and generates a configuration specific to this Gateway based on the MAC address.

After entering the base URL, press the Check button to validate the URL and check that the configuration file exists. Next, after pressing the Download button, the new configuration will be downloaded, which completes the configuration process. After that, Ruuvi Gateway will periodically check for configuration updates and download them. The polling period is set in the configuration file ([Gateway configuration](/ruuvi-gateway-firmware/data-formats/gateway-configuration)), which is downloaded from the server.

<figure><img src="/files/xeWJoUS6BSCRw1pT9YOf" alt=""><figcaption></figcaption></figure>

The gateway configuration file on the remote server must contain at least the following attributes:

* **remote\_cfg\_use:** true
* **remote\_cfg\_url**: URL
* **remote\_cfg\_refresh\_interval\_minutes**: a period of checking for an updated configuration (in minutes)
* **remote\_cfg\_auth\_type**: authentication type ('**no**' if authentication is not required)

Example of minimal gw\_cfg.json:

```json
{
  "remote_cfg_use": true, 
  "remote_cfg_url": "http://192.168.1.101:7000/", 
  "remote_cfg_refresh_interval_minutes": 10,
  "remote_cfg_auth_type": "no" 
}
```

All configuration attributes not specified in the configuration file will retain their previous value after the new configuration is loaded from the remote server.

Configuration downloads with basic HTTP authentication are also supported:

<figure><img src="/files/NDl1cyWu2G6FcFEY4kKp" alt=""><figcaption></figcaption></figure>

Example of corresponding minimal gw\_cfg.json:

```json
{
  "remote_cfg_use": true, 
  "remote_cfg_url": "http://192.168.1.101:7000/", 
  "remote_cfg_refresh_interval_minutes": 10,
  "remote_cfg_auth_type": "basic",
  "remote_cfg_auth_basic_user": "user1",
  "remote_cfg_auth_basic_pass": "password1"
}
```

Or Bearer authentication (using a token):

<figure><img src="/files/K0taOQrev0Bid2ViPykK" alt=""><figcaption></figcaption></figure>

Example of corresponding minimal gw\_cfg.json:

```json
{
  "remote_cfg_use": true, 
  "remote_cfg_url": "http://192.168.1.101:7000/", 
  "remote_cfg_refresh_interval_minutes": 10,
  "remote_cfg_auth_type": "bearer",
  "remote_cfg_auth_bearer_token": "my_secret_token_123"
}
```

It also supports client authentication via SSL by enabling the upload of a client certificate and its associated private key, ensuring secure and verified client-server communication:

<figure><img src="/files/aaXDZuoeYHJacFn5Vhn3" alt=""><figcaption></figcaption></figure>

You can use a server SSL Certificate if you want to be independent of public Certificate Authorities (CAs) or if you have deployed a self-signed certificate on the HTTPS server, giving you greater control and customization over your security infrastructure:

<figure><img src="/files/wxrk4tly3zkelLggWnqS" alt=""><figcaption></figcaption></figure>

You can also trigger a forced configuration download via API: [Configuration download from a remote server via API](/ruuvi-gateway-firmware/examples/configuration-download-from-a-remote-server-via-api)


# Automatic updates

### Regular updates

On this page, you can configure automatic software updates.

<figure><img src="/files/PhhI1jHlaDlE1PExSN9V" alt=""><figcaption></figcaption></figure>

Under the "Advanced Settings", you can configure a schedule for installing updates - select weekdays and preferred timeslot:

<figure><img src="/files/GZ9IGNU1pZTmmc0Amop3" alt=""><figcaption></figcaption></figure>

### Beta testing of new releases

If you would like to participate in beta testing the new releases, then you can choose the following option:

<figure><img src="/files/FKFrg9dqby8Y4yQgQtx0" alt=""><figcaption></figcaption></figure>

### Disabling automatic updates

Also, you can disable automatic software updating by switching to "Manual updates only" mode:

<figure><img src="/files/34bSRFs6IGxFAQGnoRgi" alt=""><figcaption></figcaption></figure>


# Access Settings from LAN

On this page, you can set access rules to Ruuvi Gateway from the local network. By default, the access is password protected using the default password (unique device ID which is printed on the bottom of Ruuvi Gateway).

<figure><img src="/files/qvzQLzJYpPm3ZXG8Wlh8" alt=""><figcaption></figcaption></figure>

If you wish, you can set your own username and password:

<figure><img src="/files/TE1d5YN4xbhkvze0flTx" alt=""><figcaption></figcaption></figure>

Or disable access by username/password:

<figure><img src="/files/B9CSziims94jQdtwQQFm" alt=""><figcaption></figcaption></figure>

You can also allow access without a password, but this is not recommended as it is not very secure:

<figure><img src="/files/N9EqayhiZnE1tkLOtjdh" alt=""><figcaption></figcaption></figure>

It is also possible to access Ruuvi Gateway from LAN using bearer authentication with an API key (token). You can enable read-only access (to retrieve data in [Polling mode](/ruuvi-gateway-firmware/examples/polling-mode) via the /history endpoint) or read-write access (to read any data or change the configuration):

<figure><img src="/files/c6PDoOGxrXJCR2laSh9Y" alt=""><figcaption></figcaption></figure>


# Cloud Options

By default, Ruuvi Gateway sends accumulated messages from Bluetooth sensors [HTTP: Time-stamped data from Bluetooth-sensors](/ruuvi-gateway-firmware/data-formats/http-time-stamped-data-from-bluetooth-sensors) and some statistics [HTTP: Gateway status](/ruuvi-gateway-firmware/data-formats/http-gateway-status) to Ruuvi Cloud:

<figure><img src="/files/9uzDX9EWlC8BzmlspWsI" alt=""><figcaption></figcaption></figure>

But you can change the cloud options under the "Advanced Settings":

<figure><img src="/files/A1x5Jm6qDKt5rqgAM033" alt=""><figcaption></figcaption></figure>

On the next page, you can configure data transfer to HTTP(S) or MQTT(s) backend:

* [Backend: HTTP(s)](/ruuvi-gateway-firmware/gateway-html-pages/cloud-options/backend-http-s)
* [Backend: MQTT(s)](/ruuvi-gateway-firmware/gateway-html-pages/cloud-options/backend-mqtt-s)
* [Backend: Statistics](/ruuvi-gateway-firmware/gateway-html-pages/cloud-options/backend-statistics)


# Backend: HTTP(s)

You can enable or disable sending data to Ruuvi Cloud:

<figure><img src="/files/O3LY1N5ymb5sF7PmP8xo" alt=""><figcaption></figcaption></figure>

Or configure to send data to your own server via HTTP/HTTPS (it is possible to send data to both destinations, Ruuvi Cloud and your own server, at the same time):

<figure><img src="/files/IS47BqjPVAPJ5jYWvxKQ" alt=""><figcaption></figcaption></figure>

For your own server, you can choose the data format and decode data from Ruuvi sensors on the Gateway side:

<figure><img src="/files/UeUn8asAfvwEVxVhQHel" alt=""><figcaption></figcaption></figure>

Configure sending interval:

<figure><img src="/files/tMAod1Ikj7nMjoBJ9RJ0" alt=""><figcaption></figcaption></figure>

### Use different types of authentication

#### Configuring Basic Authentication

When using "Basic" authentication, the gateway adds an authorization header to the HTTPS request. This header contains the credentials encoded in Base64 format.

<figure><img src="/files/z5f2aU3ZSvTKNZ2VXTic" alt=""><figcaption></figcaption></figure>

HTTP Header Example:\
`Authorization: Basic dXNlcjE6cGFzczE=`

Explanation:

* `Basic`: Indicates the authentication method.
* `dXNlcjE6cGFzczE=`: Base64 encoded credentials. For example, 'user1:pass1' encodes to 'dXNlcjE6cGFzczE='.

#### Configuring Bearer Authentication

When using "Bearer" authentication, the gateway adds an authorization header with a token to the HTTPS request. This token is a credential used to access APIs securely.

<figure><img src="/files/hGjfnUarYGSaM1TmaEdP" alt=""><figcaption></figcaption></figure>

HTTP Header Example:

`Authorization: Bearer token123`

#### Configuring Token-Based Authentication

Token-based authentication involves securing API requests by sending a token in the HTTP header. The token is a unique identifier that must be included in every request.

<figure><img src="/files/tPuMs1tt9ZPQO0ixEOxp" alt=""><figcaption></figcaption></figure>

HTTP Header Example:

`Authorization: Token token124`

#### Configuring API Key Authentication

API key authentication is a simple method where an API key is used directly as part of the HTTP header to authenticate requests.

<figure><img src="/files/d7O1huyTzRxVc7TgHc86" alt=""><figcaption></figcaption></figure>

HTTP Header Example:

`Authorization: token125`

#### Enhanced Security with SSL Client Authentication

It also supports client authentication via SSL by enabling the upload of a client certificate and its associated private key, ensuring secure and verified client-server communication:

<figure><img src="/files/Rb07ISiKaZTce0a4gXTf" alt=""><figcaption></figcaption></figure>

You can use a server SSL Certificate if you want to be independent of public Certificate Authorities (CAs) or if you have deployed a self-signed certificate on the HTTPS server, giving you greater control and customization over your security infrastructure:

<figure><img src="/files/TcuM5fVF0UXEYkOz0272" alt=""><figcaption></figcaption></figure>


# Backend: MQTT(s)

You can configure relaying data to MQTT server:

<figure><img src="/files/lYvkXHhd8RHBka82xY71" alt=""><figcaption></figcaption></figure>

You can choose the data format - raw data received from Bluetooth sensors or decoded data for Ruuvi sensors:

<figure><img src="/files/ZnJC9dpMIUPmTeRm8GOq" alt=""><figcaption></figcaption></figure>

By default, data received from Bluetooth sensors is retransmitted over MQTT immediately, but if you have a lot of sensors you may want to limit the data flow. In order to do this you can set a sending interval:

<figure><img src="/files/m3baI5J0GTvvGkd90zRs" alt=""><figcaption></figcaption></figure>

It also supports MQTT client authentication via SSL by enabling the upload of a client certificate and its associated private key, ensuring secure and verified client-server communication. You can use a server SSL Certificate if you want to be independent of public Certificate Authorities (CAs) or if you have deployed a self-signed certificate on the HTTPS server, giving you greater control and customization over your security infrastructure:

<figure><img src="/files/xBZL8YJqNTIawWBSUX5K" alt=""><figcaption></figcaption></figure>


# Backend: Statistics

In the Statistics section, you can configure the sending of statistics to Ruuvi Cloud or your own server, or disable the sending of statistics:

<figure><img src="/files/pNDnxLucfiLzJ7BPTvdA" alt=""><figcaption></figcaption></figure>

When using your own server, you and use basic authentication and/or use client authentication via SSL by enabling the upload of a client certificate and its associated private key, ensuring secure and verified client-server communication. Also, you can use a server SSL Certificate if you want to be independent of public Certificate Authorities (CAs) or if you have deployed a self-signed certificate on the HTTPS server, giving you greater control and customization over your security infrastructure:

<figure><img src="/files/AHCufQ7CQnV5zWfvEcd1" alt=""><figcaption></figcaption></figure>


# Time Synchronisation Options

To access **Time Synchronisation Options**, you must first enable access to configure advanced settings on the **Cloud Options** page under **Advanced Settings**:

<figure><img src="/files/A1x5Jm6qDKt5rqgAM033" alt=""><figcaption></figcaption></figure>

If the default set of NTP servers is not accessible, then you can specify the addresses of other NTP servers or use DHCP to obtain a list of NTP servers automatically. If the Ruuvi Gateway does not have access to the Internet and NTP, you can disable time synchronisation, but in this case, relayed messages will not contain timestamps.

<figure><img src="/files/bDqhvevokBIvp8bjVm2x" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/VDPjpfEGoaIsBozHHtCa" alt=""><figcaption></figcaption></figure>


# Bluetooth Scanning Settings

To access **Bluetooth Scanning Settings**, you must first enable access to configure advanced settings on the **Cloud Options** page under **Advanced Settings**:

<figure><img src="/files/A1x5Jm6qDKt5rqgAM033" alt=""><figcaption></figcaption></figure>

By default, Ruuvi Gateway scans only for Ruuvi sensors (filtered by BLE SIG member ID 0x0499)

<figure><img src="/files/AGaHEtoTeg2RZ3PgqwRL" alt=""><figcaption></figcaption></figure>

Also, you can enable Bluetooth long range (also known as Coded PHY). It is a new mode introduced in Bluetooth Version 5.0 to extend the range of Bluetooth devices from 30-100 feet to ranges of 1 kilometer and beyond.

**Note**: Most existing devices only transmit on 1 Mbps PHY. 1 Mbps and Coded PHYs modulations are scanned sequentially, so scanning both PHYs will result in at least 50% packet loss on one of the PHYs.

<figure><img src="/files/WbPMtm63967EqqA8kBWm" alt=""><figcaption></figcaption></figure>

If you want to relay data from more than just Ruuvi sensors, you need to select the "**All (including third party beacons)**" option and adjust the scanned PHYs and Bluetooth channels used:

<figure><img src="/files/PhSZUo0XxB2MxsJVTEnp" alt=""><figcaption></figcaption></figure>

**Listen to channels**. Each enabled BLE channel is scanned sequentially for a minimum of 7000 ms per channel, for a total of 21000 ms if all 3 channels are enabled. At least one channel must be active.

It is possible to filter out the relayed Bluetooth sensors. You can use **whitelist mode** if you only want to share data for specific sensors, or **blacklist mode** if you want to share data from all sensors except the specified list.

<figure><img src="/files/hBiy0AhFrHKLf7DSuSwg" alt=""><figcaption></figcaption></figure>

If you don't see some sensors, try to press on the **Refresh list**. If a sensor is offline, you can add it manually by entering its MAC address and pressing the **Add** button.


# Configuration completion

Once the configuration is complete, the following page will be displayed, where you can see the IP address assigned to the Ruuvi Gateway in the local network:

<figure><img src="/files/PSz9E2cEKqzLau2RUZNP" alt=""><figcaption></figcaption></figure>


# Authentication when accessing from LAN

When accessing Ruuvi Gateway from LAN and the access is password protected, then the authentication page will automatically open:

<figure><img src="/files/f2cLIEWzhOIy9chk4QzQ" alt=""><figcaption></figcaption></figure>

If default settings are used, then you can log in using the following credentials:\
Password: Unique ID (in the format XX:XX:XX:XX:XX:XX:XX:XX) which is printed on the bottom of the Ruuvi Gateway.

In case the remote configuration from LAN is disabled, then the following page will be displayed:

<figure><img src="/files/etiWElNdUts8Zr5sg6Jh" alt=""><figcaption></figcaption></figure>


# GW nRF52811 Firmware


# GW nRF52811 selftest

Lifecycle: Alpha. Last updated 2021-04-15

The nRF52811 on Ruuvi GW will run a self-test at boot to verify it is operational. The self-test will check that data can be received over Bluetooth and the communication with ESP32 is working.

To test communication with the ESP32, nRF52 will query for scan parameters from ESP32 and wait for a reply. Once scan parameters have been received, nRF52 will start scanning.

If no new advertisements have been received within a few scan cycles, the watchdog reboots the nRF52, which requests the scan parameters again.


# GW nRF52811 scanning

nRF52811 scans continuously with 7000 ms scan window and 7000 ms scan window. This allows Ruuvi Gateway to reliably detect slow RuuviTag advertising which is once per 6425 ms.

Each scan is done on one enabled channel at a time, and once every selected channel is scanned next PHY is scanned. For example if the default setting of scanning all primary advertising channels 37, 38 and 39 on long range and 1 MBit / s modulation is enabled, one complete scan takes 3 channels \* 2 PHYs \* 7000 ms for a total of 42 seconds.

Some Bluetooth devices can also advertise on secondary channels, the secondary advertisement works by first advertising information of secondary advertisement on primary channel and then sending secondary advertisement on any channel and modulation. If the primary advertisement was on long range PHY only long range secondary advertisements are scanned, if primary advertisement was on 1 MBit / s PHY secondary advertisement can be either 1 MBit / s or 2 MBit / s.

By default nRF52811 will report only Ruuvi manufacturer specific data, but it's possible to configure the scan to report all seen beacons.

Gateway can be configured via UART to enable/disable channels and PHYs with ruuvi\_endpoint\_ca\_uart commands.


# GW nRF52811 repeating

Lifecycle: proposal. Last updated 2021-04-15

nRF52811 Could listen in to advertisements and repeat them for other devices. For example if there is a sensor broadcasting at Long Range PHY once per 6 seconds, Gateway could repeat the advertisement at faster interval at 1 MBit / s PHY for mobile phones etc devices which aren't explicitly designed to scan for BLE advertisements.


# GW nRF52811 UART communication

## Message format

The host and device (nRF52) communicate with each other via UART using the following message format:

![](/files/-MIZekI1bXHwJRtF9uxB)

Note 1: `LEN` field determines the length of the payload (`CMD` field is not included in `LEN`).\
Thus, the message with `LEN`=0 contains only the `CMD` byte and no `Payload`.

Note 2: CRC16 is calculated for `Header` (`LEN` and `CMD`) and `Payload`.

CRC is CRC-16-CCITT-FALSE, Polynomial Formula: x16+x12+x5+1, Polynomial: 1021

### Defines

| Constant  | Value |
| --------- | ----- |
| STX       | 0xCA  |
| ETX       | 0x0A  |
| DELIMITER | 0x2C  |

## SET sequence

<figure><img src="/files/EH9ScS2eTJYQp3Frp4P5" alt=""><figcaption></figcaption></figure>

### Command format

<table><thead><tr><th width="318">Command</th><th width="75">CMD</th><th width="267">Param</th><th width="128">Value</th></tr></thead><tbody><tr><td><code>RE_CA_UART_SET_FLTR_TAGS</code></td><td>0x05</td><td>Enable/disable BLE tags filter</td><td><code>0...1</code></td></tr><tr><td><code>RE_CA_UART_SET_FLTR_ID</code></td><td>0x06</td><td>Manufacturer ID to be filtered (uint16)</td><td><code>0...65535</code></td></tr><tr><td><code>RE_CA_UART_SET_CODED_PHY</code></td><td>0x07</td><td>Enable/disable BLE coded PHY</td><td><code>0...1</code></td></tr><tr><td><code>RE_CA_UART_SET_SCAN_1MB_PHY</code></td><td>0x08</td><td>Enable/disable BLE scan PHY</td><td><code>0...1</code></td></tr><tr><td><code>RE_CA_UART_SET_EXT_PAYLOAD</code></td><td>0x09</td><td>Enable/disable BLE extended payload</td><td><code>0...1</code></td></tr><tr><td><code>RE_CA_UART_SET_CH_37</code></td><td>0x0A</td><td>Enable/disable BLE channel 37</td><td><code>0...1</code></td></tr><tr><td><code>RE_CA_UART_SET_CH_38</code></td><td>0x0B</td><td>Enable/disable BLE channel 38</td><td><code>0...1</code></td></tr><tr><td><code>RE_CA_UART_SET_CH_39</code></td><td>0x0C</td><td>Enable/disable BLE channel 39</td><td><code>0...1</code></td></tr><tr><td><code>RE_CA_UART_LED_CTRL</code></td><td>0x0E</td><td>Turn on the LED for the specified time interval in milliseconds (uint16)</td><td><code>0...65535</code></td></tr></tbody></table>

<table><thead><tr><th width="231">Command</th><th width="75">CMD</th><th width="158">Param 1</th><th width="291">Param 2</th></tr></thead><tbody><tr><td><code>RE_CA_UART_SET_ALL</code></td><td>0x0F</td><td>Manufacturer ID to be filtered:<br><code>0...65535</code></td><td>Bit-mask (uint8) for all state flags:<br>- bit 0: Filter tags<br>- bit 1: BLE coded PHY<br>- bit 2: BLE scan PHY<br>- bit 3: BLE extended payload<br>- bit 4: BLE chan 37<br>- bit 5: BLE chan 38<br>- bit 6: BLE chan 39</td></tr></tbody></table>

### Ack message

<table><thead><tr><th width="191">Ack</th><th width="77">CMD</th><th width="354">Param 1</th><th width="110">Param 2</th></tr></thead><tbody><tr><td><code>RE_CA_UART_ACK</code></td><td>0x20</td><td>Command to which the ACK responds:<br><code>0x05...0x0C, 0x0F</code></td><td>Ack state:<br><code>0</code> - OK<br><code>1</code> - Error</td></tr></tbody></table>

#### Example: Set the state of channel 37

Command `RE_CA_UART_SET_CH_37`:

<table data-header-hidden><thead><tr><th width="81" align="center">STX</th><th width="73" align="center">LEN</th><th width="74" align="center">CMD</th><th width="84" align="center">State</th><th width="133" align="center">DELIMITER</th><th width="102" align="center">CRC16</th><th width="94" align="center">ETX</th></tr></thead><tbody><tr><td align="center">STX</td><td align="center">LEN</td><td align="center">CMD</td><td align="center">State</td><td align="center">DELIMITER</td><td align="center">CRC16</td><td align="center">ETX</td></tr><tr><td align="center">CA</td><td align="center">02</td><td align="center">0A</td><td align="center">01</td><td align="center">2C</td><td align="center">78B6</td><td align="center">0A</td></tr></tbody></table>

Complete message: `CA 02 0A 01 2C B6 78 0A`

Ack `RE_CA_UART_ACK`:

<table data-header-hidden><thead><tr><th width="84" align="center">STX</th><th width="70" align="center">LEN</th><th width="71" align="center">CMD</th><th width="114" align="center">Command</th><th width="82" align="center">DELIMITER</th><th width="64" align="center">Ack</th><th width="85" align="center">DELIMITER</th><th width="85" align="center">CRC16</th><th align="center">ETX</th></tr></thead><tbody><tr><td align="center">STX</td><td align="center">LEN</td><td align="center">CMD</td><td align="center">Command</td><td align="center">DELIMITER</td><td align="center">Ack</td><td align="center">DELIMITER</td><td align="center">CRC16</td><td align="center">ETX</td></tr><tr><td align="center">CA</td><td align="center">04</td><td align="center">20</td><td align="center">0A</td><td align="center">2C</td><td align="center">00</td><td align="center">2C</td><td align="center">7EE7</td><td align="center">0A</td></tr></tbody></table>

Complete message: `CA 04 20 0A 2C 00 2C E7 7E 0A`

#### Example: Set all configuration

Command `RE_CA_UART_SET_ALL:`

<table><thead><tr><th width="80" align="center">STX</th><th width="66" align="center">LEN</th><th width="72" align="center">CMD</th><th width="77">Filter ID</th><th width="83">DELIMITER</th><th width="103">State bit-mask</th><th width="85">DELIMITER</th><th width="88">CRC16</th><th width="64">ETX</th></tr></thead><tbody><tr><td align="center">CA</td><td align="center">05</td><td align="center">0F</td><td>0499</td><td>2C</td><td>7D</td><td>2C</td><td>6121</td><td>0A</td></tr></tbody></table>

Complete message: `CA 05 0F 99 04 2C 7D 2C 21 61 0A`

Ack `RE_CA_UART_ACK:`

<table><thead><tr><th width="82">STX</th><th width="70">LEN</th><th width="73">CMD</th><th width="112">Command</th><th width="84">DELIMITER</th><th width="64">Ack</th><th width="83">DELIMITER</th><th width="88">CRC16</th><th width="65">ETX</th></tr></thead><tbody><tr><td>CA</td><td>04</td><td>20</td><td>0F</td><td>2C</td><td>00</td><td>2C</td><td>C2A2</td><td>0A</td></tr></tbody></table>

Complete message: `CA 04 20 0F 2C 00 2C A2 C2 0A`

## GET sequence

<figure><img src="/files/FKnsZKLiKQD1ek45ke2e" alt=""><figcaption></figcaption></figure>

### Request message

<table><thead><tr><th width="302">Request</th><th width="90">CMD</th><th width="250" align="center">Param</th></tr></thead><tbody><tr><td><code>RE_CA_UART_GET_DEVICE_ID</code></td><td>0x18</td><td align="center">-</td></tr></tbody></table>

### Response message

<table><thead><tr><th width="249">Response</th><th width="74">CMD</th><th>Param 1</th><th>Param 2</th></tr></thead><tbody><tr><td><code>RE_CA_UART_DEVICE_ID</code></td><td>0x11</td><td>Device ID<br>(8 bytes)</td><td>MAC Address<br>(6 bytes)</td></tr></tbody></table>

#### Example: Request DEVICE\_ID

Command :

<table><thead><tr><th width="87">STX</th><th width="81">LEN</th><th width="87">CMD</th><th width="98">CRC16</th><th width="74">ETX</th></tr></thead><tbody><tr><td>CA</td><td>00</td><td>18</td><td>8E36</td><td>0A</td></tr></tbody></table>

Complete request message: `CA 00 18 36 8E 0A`

Response:

<table><thead><tr><th width="80">STX</th><th width="67">LEN</th><th width="72">CMD</th><th width="127">Device ID</th><th width="71" align="center">DELIMITER</th><th width="110">MAC Addr</th><th width="69">DELIMITER</th><th width="73">CRC16</th><th width="67">ETX</th></tr></thead><tbody><tr><td>CA</td><td>10</td><td>11</td><td>0x38E11A5878A79840</td><td align="center">2C</td><td>2C:9C:8E:2D:25:C8</td><td>2C</td><td>677F</td><td>0A</td></tr></tbody></table>

Complete response message: `CA 10 11 40 98 A7 78 58 1A E1 38 2C C8 25 2D 8E 9C 2C 2C 7F 67 0A`

## REPORT sequence

<figure><img src="/files/0Ltpb0lGZr0AesPLFTjR" alt=""><figcaption></figcaption></figure>

### Report message

<table><thead><tr><th width="244">Report</th><th width="74">CMD</th><th width="102">Param 1</th><th>Param 2</th><th>Param 3</th></tr></thead><tbody><tr><td><code>RE_CA_UART_ADV_RPRT</code></td><td>0x10</td><td>MAC address<br>(6 bytes)</td><td>Advertisement:<br><code>Variable length (MAX 31 Bytes)</code></td><td>RSSI in dB:<br><code>-128...127</code></td></tr></tbody></table>

Note: The length of the Advertisement is determined by the LEN field in the Header.\
Adv\_Len := LEN - 10

#### Example of report

<table><thead><tr><th width="80" align="center">STX</th><th width="67">LEN</th><th width="71">CMD</th><th width="87">MAC Addr</th><th width="71">DELIMITER</th><th width="130">Adv</th><th width="70">DELIMITER</th><th width="70">RSSI</th><th width="69">DELIMITER</th><th width="68">CRC16</th><th width="64">ETX</th></tr></thead><tbody><tr><td align="center">CA</td><td>29</td><td>10</td><td>06:AD:E0:B9:A5:C6</td><td>2C</td><td>02 01 06 1B FF 99 04 05 14 64 47 25 C4 41 00 34 00 00 04 1C A9 36 11 01 58 C6 A5 B9 E0 AD 06</td><td>2C</td><td>D9</td><td>2C</td><td>0811</td><td>0A</td></tr></tbody></table>

Complete report message: `CA 29 10 C6 A5 B9 E0 AD 06 2C 02 01 06 1B FF 99 04 05 14 64 47 25 C4 41 00 34 00 00 04 1C A9 36 11 01 58 C6 A5 B9 E0 AD 06 2C D9 2C 11 08 0A`

## `CONFIGURATION REQUEST sequence`

<figure><img src="/files/948r0jTPpLDtnEViCsiR" alt=""><figcaption></figcaption></figure>

### CFG Request message

<table><thead><tr><th width="249">Request</th><th width="249">CMD</th><th align="center">Param</th></tr></thead><tbody><tr><td>RE_CA_UART_GET_ALL</td><td>0x19</td><td align="center">-</td></tr></tbody></table>

#### Example: Configuration request on the device side

<table><thead><tr><th width="85" align="center">STX</th><th width="70" align="center">LEN</th><th width="82" align="center">CMD</th><th width="170" align="center">CRC16</th><th width="171" align="center">ETX</th></tr></thead><tbody><tr><td align="center">CA</td><td align="center">00</td><td align="center">19</td><td align="center">9E17</td><td align="center">0A</td></tr></tbody></table>

Complete configuration request message: `CA 00 19 17 9E 0A`


# GW ESP32 Firmware

Lifecycle: Beta. Last updated 2021-04-15

##


# GW ESP32 WiFi Hotspot

The Gateway provides a WiFi hotspot for configuration. The hotspot has SSID with the name "**Configure Ruuvi Gateway ABCD**" where ABCD is the last 2 bytes of the gateway WiFi MAC address. No password is required to connect to the gateway's hotspot.

The WiFi hotspot is only active if the gateway is not configured or within one minute after pressing the Configuration button. After the configuration of the gateway is complete, WiFi credentials are stored to flash and the hotspot is turned off. If the Internet connection is lost later, connection loss is indicated by "Red LED" but the hotspot is not turned back on unless the user enters configuration mode by pressing the Configuration button.

The gateway can be partially reconfigured over LAN (network configuration can't be changed in this case). Also following pages are available through LAN connection: /metrics and /history.

{% hint style="info" %}
If Ethernet cable is connected to an unconfigured gateway, then after one minute the gateway will automatically be set to the default configuration with Ethernet connection mode and the Wi-Fi hotspot will be deactivated. After that, the user can activate the hotspot with a short press on the "Configure" button or reset the configuration with a long press on the "Configure" button.
{% endhint %}

## API

The gateway is reachable at <http://10.10.0.1> once a client has connected to the hotspot.

### Encrypted data format

Sensitive data (containing passwords) is transmitted in encrypted form. The format for transmitting encrypted data is as follows (JSON):

<table><thead><tr><th width="168">Attribute name</th><th>Description</th></tr></thead><tbody><tr><td><strong>encrypted</strong></td><td>Encrypted data, encoded as <strong>Base64</strong></td></tr><tr><td><strong>iv</strong></td><td>AES-256 initialization vector, encoded as <strong>Base64</strong></td></tr><tr><td><strong>hash</strong></td><td>SHA-256 hash of unencrypted data, encoded as <strong>Base64</strong></td></tr></tbody></table>

Example of encrypted data:

{% code overflow="wrap" %}

```json
{"encrypted":"hbLi8K0CHELrvI0/4gO7n3E5KY5wtbmUSwM67oY6ZhzZNPIGBhHLE8CYeDuVL+FiU/K5vAO1EC+uEm/gw4LkaSE1sZbsGUGyDFelnEOlhQYQf6rGxlQYu+9xrzPhUv97tezkrPPesSy0tgea3QAmuQRPX628X0AP0OxUxE4kBh8guYKIXPYOf9EH0fUskvt/98B8p630mu66miiQfsONsxFWVX7GcUN5u1soYKdZXd9HQGVACEgl6oZ2Vxqf4JmtNgdsmWxYTmIOi1ySq4DtqtL5vfdzN1BMep0cHkgGAeMBQt6i9H5mhBZ8PnJzjvFcuolwsRG3rQTX9wRCXikjag==","iv":"I+CylR3OlmEWr127RC084w==","hash":"ADUJpequznSV9HyqkMLrj2NPhViJQa6YDdbkEDExajk="}
```

{% endcode %}

The data is encrypted using **AES-256-CBC**. **ECDH** protocol is used to establish a shared secret between the client and the Gateway, then **SHA-256** hash is calculated for the shared secret to generate AES encryption key.

To establish a shared secret, the client must perform **`GET`**` `` ``/ruuvi.json ` request and pass its public encryption key in the HTTP header "**ruuvi\_ecdh\_pub\_key**" (Base64 encoded):

```
GET /ruuvi.json?_=1661226324528 HTTP/1.1
Host: 10.10.0.1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:103.0) Gecko/20100101 Firefox/103.0
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
ruuvi_ecdh_pub_key: BPtMzls7i0PyOu75EPzw2OaTJscwGnrnOW7mK58pfJ6Egg9/tV5cJFVz6euHGp7x4ZXj5s0hTiotrtE0qJLczMA=
X-Requested-With: XMLHttpRequest
Connection: keep-alive
Referer: http://10.10.0.1/
```

In response, the Gateway returns its public encryption key in the HTTP header "**ruuvi\_ecdh\_pub\_key**" (Base64 encoded):

```
HTTP/1.1 200 OK
Server: Ruuvi Gateway
Date: Tue, 23 Aug 2022 03:45:27 GMT
Content-type: application/json; charset=utf-8
Content-Length: 1514
ruuvi_ecdh_pub_key: BGJXeeiqH7JPCdO3wRZgSdrcJb2EtSAVUQwr/RfeJmGxIrXuw8mTW2517RCn8pgfBwDnRWCnL9JYdvQkbf+goZQ=
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
Pragma: no-cache
```

The client needs to compute a shared secret using its private encryption key and the Gateway's public key, then calculate SHA-256 for the shared secret to get the AES encryption key (***AES\_K***).

To encrypt the data before sending it, the client must randomly generate a 16-byte initialization vector (***IV***) for AES encryption, then encrypt the data using parameters ***IV*** and ***AES\_K***. The data is validated using a SHA-256 hash, which is passed as part of JSON. All values in JSON are encoded by Base64.

To decrypt the data received from the Gateway, the client needs to decode all values in JSON from Base64 encoding, then get the ***IV*** from the "**iv**" attribute of the received JSON. After that, decrypt the data in the "**encrypted**" attribute using the ***IV*** and ***AES\_K***. And finally, calculate the SHA-256 hash for the decrypted data and check if it matches the "**hash**" attribute in JSON.

These API calls are available:

## Requesting gateway configuration and performing encryption keys exchange

<mark style="color:blue;">`GET`</mark> `http://10.10.0.1/ruuvi.json`

#### Headers

| Name                                                    | Type   | Description                                            |
| ------------------------------------------------------- | ------ | ------------------------------------------------------ |
| ruuvi\_ecdh\_pub\_key<mark style="color:red;">\*</mark> | String | The client's public encryption key in Base64 encoding. |

{% tabs %}
{% tab title="200: OK Encrypted gateway configuration." %}
The Gateway's public encryption key is passed in HTTP header "**ruuvi\_ecdh\_pub\_key**" in Base64 encoding. The data is sent in an encrypted form (see [#undefined](#undefined "mention")).

Here is unencrypted data:

```json
{
        "fw_ver":       "v1.12.2",
        "nrf52_fw_ver": "v0.7.2",
        "gw_mac":       "C8:25:2D:8E:9C:2C",
        "wifi_sta_config":      {
                "ssid": ""
        },
        "wifi_ap_config":       {
                "channel":      1
        },
        "use_eth":      true,
        "eth_dhcp":     true,
        "eth_static_ip":        "",
        "eth_netmask":  "",
        "eth_gw":       "",
        "eth_dns1":     "",
        "eth_dns2":     "",
        "remote_cfg_use":       false,
        "remote_cfg_url":       "",
        "remote_cfg_auth_type": "no",
        "remote_cfg_refresh_interval_minutes":  0,
        "use_http":     true,
        "http_url":     "https://network.ruuvi.com/record",
        "http_user":    "",
        "use_http_stat":        true,
        "http_stat_url":        "https://network.ruuvi.com/status",
        "http_stat_user":       "",
        "use_mqtt":     false,
        "mqtt_transport":       "TCP",
        "mqtt_server":  "test.mosquitto.org",
        "mqtt_port":    1883,
        "mqtt_prefix":  "ruuvi/C8:25:2D:8E:9C:2C/",
        "mqtt_client_id":       "C8:25:2D:8E:9C:2C",
        "mqtt_user":    "",
        "lan_auth_type":        "lan_auth_default",
        "lan_auth_user":        "Admin",
        "lan_auth_api_key_use": false,
        "auto_update_cycle":    "regular",
        "auto_update_weekdays_bitmask": 127,
        "auto_update_interval_from":    0,
        "auto_update_interval_to":      24,
        "auto_update_tz_offset_hours":  3,
        "ntp_use":      true,
        "ntp_use_dhcp": false,
        "ntp_server1":  "time.google.com",
        "ntp_server2":  "time.cloudflare.com",
        "ntp_server3":  "time.nist.gov",
        "ntp_server4":  "pool.ntp.org",
        "company_id":   1177,
        "company_use_filtering":        true,
        "scan_coded_phy":       false,
        "scan_1mbit_phy":       true,
        "scan_extended_payload":        true,
        "scan_channel_37":      true,
        "scan_channel_38":      true,
        "scan_channel_39":      true,
        "coordinates":  ""
}
```

{% endtab %}
{% endtabs %}

## Save network configuration

<mark style="color:green;">`POST`</mark> `http://10.10.0.1/ruuvi.json`

The same endpoint "/ruuvi.json" is used for saving the network configuration and gateway configuration. If the passed JSON data contains the "use\_eth" attribute, then the network part of the configuration will be updated. Otherwise, the gateway configuration will be set.

The data must be sent in an encrypted form (see [#encrypted-data-format](#encrypted-data-format "mention")). Here is the unencrypted data:

#### Request Body

| Name                                       | Type   | Description                                                                                |
| ------------------------------------------ | ------ | ------------------------------------------------------------------------------------------ |
| use\_eth<mark style="color:red;">\*</mark> | Bool   | Use Ethernet if True, otherwise use Wi-Fi                                                  |
| eth\_dhcp                                  | Bool   | Use DHCP for Ethernet connection                                                           |
| eth\_static\_ip                            | String | Static IP address (when Ethernet is used and DHCP disabled)                                |
| eth\_netmask                               | String | Netmask (when Ethernet is used and DHCP disabled)                                          |
| eth\_gw                                    | String | IP address of gateway (when Ethernet is used and DHCP disabled)                            |
| eth\_dns1                                  | String | IP address of DNS1 (when Ethernet is used and DHCP disabled)                               |
| eth\_dns2                                  | String | IP address of DNS2 (when Ethernet is used and DHCP disabled)                               |
| wifi\_ap\_config                           | Object | Set parameters for Wi-Fi AP. The attribute "channel" allows configuring the Wi-Fi channel. |

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
    // Response
}
```

{% endtab %}
{% endtabs %}

Example:

```json
{"use_eth":true,"eth_dhcp":true}
```

Example:

```json
{"use_eth":false,"wifi_ap_config":{"channel":11}}
```

## Save gateway configuration

<mark style="color:green;">`POST`</mark> `http://10.10.0.1/ruuvi.json`

The same endpoint "/ruuvi.json" is used for saving the network configuration and gateway configuration. If the passed JSON data contains the "use\_eth" attribute, then the network part of the configuration will be updated. Otherwise, the gateway configuration will be set.

The data must be sent in an encrypted form (see [#encrypted-data-format](#encrypted-data-format "mention")). Here is the unencrypted data:

#### Request Body

| Name                             | Type   | Description                                                                                          |
| -------------------------------- | ------ | ---------------------------------------------------------------------------------------------------- |
| remote\_cfg\_use                 | Bool   | If true, then download the configuration from the remote server, specified in "**remote\_cfg\_url**" |
| remote\_cfg\_url                 | String | URL of the remote server to download the configuration                                               |
| remote\_cfg\_auth\_type          | String | Authentication type: "no", "basic" or "bearer"                                                       |
| use\_http                        | Bool   | If true, then send data to the cloud via HTTP(S)                                                     |
| http\_url                        | String | URL of the cloud server                                                                              |
| http\_user                       | String | Username                                                                                             |
| http\_pass                       | String | Password                                                                                             |
| use\_http\_stat                  | Bool   | If true, then send statistics to the cloud via HTTP(S)                                               |
| http\_stat\_url                  | String | URL of the cloud statistrics server                                                                  |
| http\_stat\_user                 | String | Username                                                                                             |
| http\_stat\_pass                 | String | Password                                                                                             |
| use\_mqtt                        | Bool   | If true, then send data to the cloud via MQTT(S)                                                     |
| mqtt\_transport                  | String | MQTT transport: "TCP", "SSL", "WS" or "WSS"                                                          |
| mqtt\_server                     | String | IP address of MQTT server                                                                            |
| mqtt\_port                       | Int    | TCP/IP port of MQTT server                                                                           |
| mqtt\_prefix                     | String | MQTT prefix                                                                                          |
| mqtt\_client\_id                 | String | MQTT client ID                                                                                       |
| mqtt\_user                       | String | Username for MQTT authentication                                                                     |
| mqtt\_pass                       | String | Password for MQTT authentication                                                                     |
| lan\_auth\_api\_key              | String | Bearer key for authentication when accessing the Gateway from LAN                                    |
| company\_use\_filtering          | Bool   |                                                                                                      |
| scan\_coded\_phy                 | Bool   |                                                                                                      |
| scan\_1mbit\_phy                 | Bool   |                                                                                                      |
| scan\_extended\_payload          | Bool   |                                                                                                      |
| scan\_channel\_37                | Bool   |                                                                                                      |
| scan\_channel\_38                | Bool   |                                                                                                      |
| scan\_channel\_39                | Bool   |                                                                                                      |
| auto\_update\_cycle              | String | "regular", "beta" or "manual"                                                                        |
| auto\_update\_weekdays\_bitmask  | Int    |                                                                                                      |
| auto\_update\_interval\_from     | Int    |                                                                                                      |
| auto\_update\_interval\_to       | Int    |                                                                                                      |
| auto\_update\_tz\_offset\_hours  | Int    |                                                                                                      |
| ntp\_use                         | Bool   |                                                                                                      |
| ntp\_use\_dhcp                   | Bool   |                                                                                                      |
| ntp\_server1                     | String |                                                                                                      |
| ntp\_server2                     | String |                                                                                                      |
| ntp\_server3                     | String |                                                                                                      |
| ntp\_server4                     | String |                                                                                                      |
| remote\_cfg\_auth\_basic\_user   | String | Username for "basic" authentication                                                                  |
| remote\_cfg\_auth\_basic\_pass   | String | Password for "basic" authentication                                                                  |
| remote\_cfg\_auth\_bearer\_token | String | Bearer token for bearer authentication                                                               |

{% tabs %}
{% tab title="200: OK " %}

```javascript
{
    // Response
}
```

{% endtab %}
{% endtabs %}

Example:

```json
{
  "remote_cfg_use":false,
  "remote_cfg_url":"",
  "remote_cfg_auth_type":"no",
  "use_http":true,
  "http_url":"https://network.ruuvi.com/record",
  "http_user":"",
  "http_pass":"",
  "use_http_stat":true,
  "http_stat_url":"https://network.ruuvi.com/status",
  "http_stat_user":"",
  "http_stat_pass":"",
  "use_mqtt":false,
  "mqtt_transport":"TCP",
  "mqtt_server":"test.mosquitto.org",
  "mqtt_port":1883,
  "mqtt_prefix":"ruuvi/C8:25:2D:8E:9C:2C/",
  "mqtt_client_id":"C8:25:2D:8E:9C:2C",
  "mqtt_user":"",
  "mqtt_pass":"",
  "lan_auth_api_key":"",
  "company_use_filtering":true,
  "scan_coded_phy":false,
  "scan_1mbit_phy":true,
  "scan_extended_payload":true,
  "scan_channel_37":true,
  "scan_channel_38":true,
  "scan_channel_39":true,
  "auto_update_cycle":"manual",
  "auto_update_weekdays_bitmask":127,
  "auto_update_interval_from":0,
  "auto_update_interval_to":24,
  "auto_update_tz_offset_hours":3,
  "ntp_use":true,
  "ntp_use_dhcp":false,
  "ntp_server1":"time.google.com",
  "ntp_server2":"time.cloudflare.com",
  "ntp_server3":"time.nist.gov",
  "ntp_server4":"pool.ntp.org"
}
```

## status.json

<mark style="color:blue;">`GET`</mark> `http://10.10.0.1/status.json`

This URL can be polled to get the Wi-Fi/Ethernet connection status of the gateway.

{% tabs %}
{% tab title="200 Status of the network connection if it" %}

```json
{}
```

{% endtab %}

{% tab title="200: OK Status of current Wi-Fi connection if it" %}

```json
{"ssid":"my_wifi_1","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":0}
```

{% endtab %}

{% tab title="200: OK After an unsuccessful attempt to connect" %}

```json
{"ssid":"my_wifi_1","ip":"0","netmask":"0","gw":"0","urc":1}
```

{% endtab %}

{% tab title="200: OK After the user has disconnected from Wi-Fi" %}

```json
{"ssid":"my_wifi_1","ip":"0","netmask":"0","gw":"0","urc":2}
```

{% endtab %}
{% endtabs %}

## ap.json

<mark style="color:blue;">`GET`</mark> `http://10.10.0.1/ap.json`

Scan for available Wi-Fi access points that the gateway can connect to.

{% tabs %}
{% tab title="200 An array of JSON objects containing nearby Wi-Fi networks " %}

```json
[
{"ssid":"Pantum-AP-A6D49F","chan":11,"rssi":-55,"auth":4},
{"ssid":"a0308","chan":1,"rssi":-56,"auth":3}
]
```

{% endtab %}
{% endtabs %}

## connect.json

<mark style="color:green;">`POST`</mark> `http://10.10.0.1/connect.json`

Connect to a Wi-Fi network or Ethernet.

Data containing authentication information is transmitted in the body of the request in encrypted form.

#### Request Body

| Name                                        | Type   | Description                                           |
| ------------------------------------------- | ------ | ----------------------------------------------------- |
| hash<mark style="color:red;">\*</mark>      | String | SHA256 hash of unencrypted request, encoded as base64 |
| iv<mark style="color:red;">\*</mark>        | String | AES initialization vector, encoded as base64          |
| encrypted<mark style="color:red;">\*</mark> | String | Encrypted request, encoded as base64                  |

{% tabs %}
{% tab title="200 Gateway tries to connect to a given WiFi SSID or Ethernet." %}

```json
{}
```

{% endtab %}

{% tab title="400 If required parameter is missing." %}

```
Example: 

```

{% endtab %}
{% endtabs %}

**Example**:

Unencrypted content of "**/connect.json**" is data in json format:

| Field name | Description                                                                                                                               |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| ssid       | WiFi network SSID or **null** for Ethernet                                                                                                |
| password   | Strings with a Wi-Fi password or **null** for Ethernet. The **null** value is also used for reconnecting to the last saved Wi-Fi network. |
| stub       | A string of spaces, which is chosen so that the json length is always 240 bytes, regardless of SSID length and password length.           |

Example of unencrypted data for connecting to Wi-Fi with the name "my\_ssid1":

```json
{"ssid": "my_ssid1", "password": "12345678", "stub": "                            "}
```

Example of unencrypted data for connecting to Wi-Fi with the name "my\_ssid1" using the saved password (the value "null" is passed to indicate that the saved value of the password is to be used):

```json
{"ssid": "my_ssid1", "password": null, "stub": "                            "}
```

Example of unencrypted data for connecting to Ethernet:

```json
{"ssid": null, "password": null, "stub": "                            "}
```

## connect.json

<mark style="color:red;">`DELETE`</mark> `http://10.10.0.1/connect.json`

Disconnect from WiFi or Ethernet.

{% tabs %}
{% tab title="200 Connection is dropped. " %}

```json
{}
```

{% endtab %}

{% tab title="400 " %}

```
```

{% endtab %}
{% endtabs %}

## metrics

<mark style="color:blue;">`GET`</mark> `http://10.10.0.1/metrics`

Get machine statistics, such as uptime and free memory. Data is in Prometheus format. For more details, please see <https://prometheus.io/docs/instrumenting/exposition\\_formats/> .

{% tabs %}
{% tab title="200 Prometheus text data" %}

```
ruuvigw_received_advertisements 2566 ruuvigw_uptime_us 65447769 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_EXEC"} 205004 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_32BIT"} 211468 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_8BIT"} 136412 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_DMA"} 136412 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID2"} 0 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID3"} 0 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID4"} 0 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID5"} 0 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID6"} 0 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID7"} 0 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_SPIRAM"} 0 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_INTERNAL"} 211468 ruuvigw_heap_free_bytes{capability="MALLOC_CAP_DEFAULT"} 136604 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_EXEC"} 129948 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_32BIT"} 129948 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_8BIT"} 129948 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_DMA"} 129948 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID2"} 0 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID3"} 0 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID4"} 0 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID5"} 0 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID6"} 0 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID7"} 0 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_SPIRAM"} 0 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_INTERNAL"} 129948 ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_DEFAULT"} 129948
```

{% endtab %}
{% endtabs %}

## history

<mark style="color:blue;">`GET`</mark> `http://10.10.0.1/history`

Get history data in json format

#### Path Parameters

| Name | Type    | Description                                  |
| ---- | ------- | -------------------------------------------- |
| time | integer | Read the history for the last N seconds only |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}
{% endtabs %}

See examples of the "history" payload here: [HTTP GET /history (with timestamps) and decoding](/ruuvi-gateway-firmware/data-formats/http-get-history-with-timestamps-and-decoding)


# GW ESP32 Button

## Button functionality

ESP32 has two buttons, one is hardwired reset and one is input to GPIO.

The input button has floating terminal to ESP32 and other terminal is grounded, i.e. ESP32 must activate the internal pull-up resistor and interrupts.

On button press, a timer is started to detect if the button press is a short press or a long press, and LED indicates that the button is pressed.

If the press is less than 5 seconds, the press is considered to be a short press, and the configuration hotspot is enabled once the button is released.

If the press is longer than 5 seconds, all user settings on flash are erased, and the configuration hotspot is enabled once the button is released.

{% hint style="info" %}
If Gateway is connected to the Internet via Ethernet cable, the default action is to connect to Ruuvi Network with default settings and turn the hotspot off. Disconnect Ethernet cable if that is not intentional.
{% endhint %}


# GW ESP32 LED

Lifecycle: proposal

## LED Indications

The LED indications are listed by priority, the first matching condition is indicated.\
Indication code: the letter "R" means the red LED is on, the letter "G" means the green LED is on, and the letter "-" means the LEDs are off (by default, the step length is 100 ms).

<table data-header-hidden><thead><tr><th>State</th><th>Indication</th></tr></thead><tbody><tr><td>Gateway is rebooting</td><td><p>The step is 25 ms:</p><pre><code>"R-R-R-R-"
</code></pre><p>Red blinks with 50 ms period.</p></td></tr><tr><td>Bluetooth-coprocessor (nRF52) failure</td><td>Solid Red</td></tr><tr><td>Flashing Bluetooth-coprocessor (nRF52)</td><td><pre><code>"R---------"
</code></pre><p>Red LED lights up for 100 ms every second.</p></td></tr><tr><td>Configuration erasing has been completed (after a long press of the button)</td><td><pre><code>"RR--RR--"
</code></pre><p>Red LED flashes with 400 ms period.</p></td></tr><tr><td>Configuration hotspot is active.</td><td><pre><code>"RRRRRRRRRRGGGGGGGGGG"
</code></pre><p>Red and green LEDs light up alternately for 1 second.</p></td></tr><tr><td>Configuration hotspot is active and WPS (Wi-Fi Protected Setup) is active.</td><td><code>"RRRRRRRRGRGGGGGGGGGG"</code></td></tr><tr><td>No internet connection or server returns an error code when trying to send data</td><td><pre><code>"R-R-R-R-R-"
</code></pre><p>Red LED lights up 5 times per second.</p></td></tr><tr><td>No data from Bluetooth sensors</td><td><pre><code>"G-G-G-G-G-"
</code></pre><p>Green LED lights up 5 times per second.</p></td></tr><tr><td>No connection to all servers</td><td><pre><code>"RRRRR-----"
</code></pre><p>Red LED lights up for half a second every second.</p></td></tr><tr><td>No connection to some of the servers</td><td><pre><code>"GGGGGGGGG-"
</code></pre><p>Green LED lights up continuously but goes out every second for a short interval of 100 ms.</p></td></tr><tr><td>The data comes from the Bluetooth sensors and is successfully sent to all configured servers</td><td>Solid Green</td></tr></tbody></table>


# GW ESP32 HTTP Client

Lifecycle: Alpha. Last updated 2020-07-03


# GW ESP32 MQTT client

Lifecycle: Alpha

ESP32 can send data of Bluetooth beacons to MQTT broker. Ruuvi does not offer a dedicated broker at this time, but you can use test.mosquitto.org for a quick start.


# GW Data formats

This page contains descriptions of the data formats that are used when communicating with Ruuvi gateway.


# HTTP: Time-stamped data from Bluetooth-sensors

Relaying of accumulated time-stamped data from Bluetooth-sensors to HTTP/HTTPS server.

The JSON data format description is provided in JSON schema format (<https://json-schema.org>), which provides clear human- and machine-readable documentation with examples.

The format of relayed messages is described here <https://docs.ruuvi.com/communication/bluetooth-advertisements>

```json
{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$id": "http://ruuvi.com/schemas/ruuvi_http_data_with_timestamps.schema.json",
  "title": "Time-stamped data from Bluetooth-sensors relayed via HTTP/HTTPS",
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "title": "Data-object containing accumulated data from Bluetooth-sensors for relaying",
      "type": "object",
      "required": [
        "timestamp",
        "nonce",
        "gw_mac",
        "tags"
      ],
      "properties": {
        "coordinates": {
          "type": "string",
          "default": "",
          "title": "GPS-coordinates of Ruuvi Gateway (optional)",
          "examples": [
            ""
          ]
        },
        "timestamp": {
          "type": "string",
          "title": "Timestamp (Unix-time) when the accumulated messages from Bluetooth-sensors was relayed by Gateway",
          "examples": [
            "1653633988"
          ]
        },
        "nonce": {
          "type": "string",
          "title": "Nonce - sequentially incremented number for each message, the initial value of which is set randomly",
          "examples": [
            "2636366621"
          ]
        },
        "gw_mac": {
          "type": "string",
          "title": "MAC-address of Ruuvi Gateway",
          "examples": [
            "C8:25:2D:8E:9C:2C"
          ]
        },
        "tags": {
          "type": "object",
          "title": "Array of records with relayed messages from Bluetooth-sensors",
          "items": {
            "type": "object",
            "patternProperties": {
              "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$": {
                "type": "object",
                "title": "MAC-address of Bluetooth-sensor",
                "required": [
                  "rssi",
                  "timestamp",
                  "data"
                ],
                "properties": {
                  "rssi": {
                    "type": "integer",
                    "title": "RSSI",
                    "examples": [
                      -71
                    ]
                  },
                  "timestamp": {
                    "type": "string",
                    "title": "Timestamp (Unix-time) when the message from Bluetooth-sensor was received by Gateway",
                    "examples": [
                      "1653633986"
                    ]
                  },
                  "data": {
                    "type": "string",
                    "title": "Relayed message from Bluetooth-sensor in hex encoding",
                    "examples": [
                      "0201061BFF99040513C85714C7CC00240008041CAB76F41C3CC6A5B9E0AD06"
                    ]
                  }
                }
              }
            },
            "minItems": 0,
            "uniqueItems": true
          }
        }
      }
    }
  },
  "examples": [
    {
      "data": {
        "coordinates": "",
        "timestamp": "1653633988",
        "nonce": "2636366621",
        "gw_mac": "C8:25:2D:8E:9C:2C",
        "tags": {
          "C6:A5:B9:E0:AD:06": {
            "rssi": -71,
            "timestamp": "1653633986",
            "data": "0201061BFF99040513C85714C7CC00240008041CAB76F41C3CC6A5B9E0AD06"
          },
          "E3:75:CF:37:4E:23": {
            "rssi": -72,
            "timestamp": "1653633986",
            "data": "0201061BFF99040514565D7CC7850008003C03E4A9F6741CC3E375CF374E23"
          }
        }
      }
    }
  ]
}

```


# HTTP: Data from Bluetooth-sensors without timestamps

Relaying of accumulated Bluetooth data (without timestamps) to HTTP/HTTPS server.

The JSON data format description is provided in JSON schema format (<https://json-schema.org>), which provides clear human- and machine-readable documentation with examples.

The format of relayed messages is described here <https://docs.ruuvi.com/communication/bluetooth-advertisements>

```json
{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$id": "http://ruuvi.com/schemas/ruuvi_http_data_without_timestamps.schema.json",
  "title": "Data from Bluetooth-sensors (without timestamps) relayed via HTTP/HTTPS",
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "title": "Data-object containing accumulated data from Bluetooth-sensors for relaying",
      "type": "object",
      "required": [
        "nonce",
        "gw_mac",
        "tags"
      ],
      "properties": {
        "coordinates": {
          "type": "string",
          "default": "",
          "title": "GPS-coordinates of Ruuvi Gateway (optional)",
          "examples": [
            ""
          ]
        },
        "nonce": {
          "type": "string",
          "title": "Nonce - sequentially incremented number for each message, the initial value of which is set randomly",
          "examples": [
            "2636366621"
          ]
        },
        "gw_mac": {
          "type": "string",
          "title": "MAC-address of Ruuvi Gateway",
          "examples": [
            "C8:25:2D:8E:9C:2C"
          ]
        },
        "tags": {
          "type": "object",
          "title": "Array of records with relayed messages from Bluetooth-sensors",
          "items": {
            "type": "object",
            "patternProperties": {
              "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$": {
                "type": "object",
                "title": "MAC-address of Bluetooth-sensor",
                "required": [
                  "rssi",
                  "counter",
                  "data"
                ],
                "properties": {
                  "rssi": {
                    "type": "integer",
                    "title": "RSSI",
                    "examples": [
                      -71
                    ]
                  },
                  "counter": {
                    "type": "string",
                    "title": "Counter of messages from Bluetooth-sensors that were received by Gateway",
                    "examples": [
                      "363"
                    ]
                  },
                  "data": {
                    "type": "string",
                    "title": "Relayed message from Bluetooth-sensor in hex encoding",
                    "examples": [
                      "0201061BFF99040513C85714C7CC00240008041CAB76F41C3CC6A5B9E0AD06"
                    ]
                  }
                }
              }
            },
            "minItems": 0,
            "uniqueItems": true
          }
        }
      }
    }
  },
  "examples": [
    {
      "data": {
        "coordinates": "",
        "nonce": "2896361039",
        "gw_mac": "C8:25:2D:8E:9C:2C",
        "tags": {
          "E3:75:CF:37:4E:23": {
            "rssi": -50,
            "counter": "363",
            "data": "0201061BFF990405158A5B05C6810004004403DCAB767A45BDE375CF374E23"
          },
          "F4:1F:0C:28:CB:D6": {
            "rssi": -32,
            "counter": "365",
            "data": "0201061BFF990405166455D5C6DD0008FFF803F0ADB60F2A92F41F0C28CBD6"
          }
        }
      }
    }
  ]
}

```


# MQTT: Time-stamped data from Bluetooth-sensors

Immediate relaying of Bluetooth data (with timestamps) to MQTT server.

The JSON data format description is provided in JSON schema format (<https://json-schema.org>), which provides clear human- and machine-readable documentation with examples.

The format of relayed messages is described here <https://docs.ruuvi.com/communication/bluetooth-advertisements>

```json
{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$id": "http://ruuvi.com/schemas/ruuvi_mqtt_data_with_timestamps.schema.json",
  "title": "Time-stamped data from Bluetooth-sensors relayed via MQTT",
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "gw_mac": {
      "title": "MAC-address of Ruuvi Gateway",
      "type": "string",
      "pattern": "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$",
      "examples": [
        "C8:25:2D:8E:9C:2C"
      ]
    },
    "rssi": {
      "title": "RSSI",
      "type": "integer",
      "examples": [
        -26
      ]
    },
    "aoa": {
      "title": "???",
      "type": "array",
      "default": [],
      "items": {},
      "examples": [
        []
      ]
    },
    "gwts": {
      "title": "Timestamp (Unix-time) when the message from Bluetooth-sensor was relayed by Gateway",
      "type": "string",
      "examples": [
        "1653668027"
      ]
    },
    "ts": {
      "title": "Timestamp (Unix-time) when the message from Bluetooth-sensor was received by Gateway",
      "type": "string",
      "examples": [
        "1653668027"
      ]
    },
    "data": {
      "title": "Relayed message from Bluetooth-sensor in hex encoding",
      "type": "string",
      "examples": [
        "0201061BFF99040515AE4C6DC6D7000CFFF803F4ADB6AD697FF41F0C28CBD6"
      ]
    },
    "coords": {
      "title": "GPS-coordinates of Ruuvi Gateway (optional)",
      "type": "string",
      "examples": [
        ""
      ]
    }
  },
  "examples": [
    {
      "gw_mac": "C8:25:2D:8E:9C:2C",
      "rssi": -26,
      "aoa": [],
      "gwts": "1653668027",
      "ts": "1653668027",
      "data": "0201061BFF99040515AE4C6DC6D7000CFFF803F4ADB6AD697FF41F0C28CBD6",
      "coords": ""
    }
  ]
}

```


# MQTT: Data from Bluetooth-sensors without timestamps

Immediate relaying of Bluetooth data (without timestamps) to MQTT server.

The JSON data format description is provided in JSON schema format (<https://json-schema.org>), which provides clear human- and machine-readable documentation with examples.

The format of relayed messages is described here <https://docs.ruuvi.com/communication/bluetooth-advertisements>

```json
{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$id": "http://ruuvi.com/schemas/ruuvi_mqtt_data_without_timestamps.schema.json",
  "title": "Data from Bluetooth-sensors (without timestamps) relayed via MQTT",
  "type": "object",
  "required": [
    "data"
  ],
  "properties": {
    "gw_mac": {
      "title": "MAC-address of Ruuvi Gateway",
      "type": "string",
      "pattern": "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$",
      "examples": [
        "C8:25:2D:8E:9C:2C"
      ]
    },
    "rssi": {
      "title": "RSSI",
      "type": "integer",
      "examples": [
        -26
      ]
    },
    "aoa": {
      "title": "???",
      "type": "array",
      "default": [],
      "items": {},
      "examples": [
        []
      ]
    },
    "cnt": {
      "title": "Counter of messages from Bluetooth-sensors that were received by Gateway",
      "type": "string",
      "examples": [
        "338"
      ]
    },
    "data": {
      "title": "Relayed message from Bluetooth-sensor in hex encoding",
      "type": "string",
      "examples": [
        "0201061BFF99040515AE4C6DC6D7000CFFF803F4ADB6AD697FF41F0C28CBD6"
      ]
    },
    "coords": {
      "title": "GPS-coordinates of Ruuvi Gateway (optional)",
      "type": "string",
      "examples": [
        ""
      ]
    }
  },
  "examples": [
    {
      "gw_mac": "C8:25:2D:8E:9C:2C",
      "rssi": -25,
      "aoa": [],
      "cnt": "338",
      "data": "0201061BFF990405166455D5C6DE0008FFF403F0AE760F2A8BF41F0C28CBD6",
      "coords": ""
    }
  ]
}

```


# HTTP GET /history (with timestamps) and decoding

Format of time-stamped data returned in response to HTTP request GET /history

The JSON data format description is provided in JSON schema format (<https://json-schema.org>), which provides clear human- and machine-readable documentation with examples.

The format of relayed raw data from Bluetooth sensors is described here <https://docs.ruuvi.com/communication/bluetooth-advertisements>

Ruuvi Gateway supports the decoding of some raw data formats. To disable decoding, an additional parameter must be passed in the request: **GET /history?decode=false**

Decoding of data format 5 (RAWv2) is currently supported: [Data format 5 (RAWv2)](/communication/bluetooth-advertisements/data-format-5-rawv2)

```json
{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$id": "http://ruuvi.com/schemas/ruuvi_history.schema.json",
  "type": "object",
  "title": "Accumulated time-stamped data from Bluetooth sensors returned in response to HTTP GET /history",
  "additionalProperties": false,
  "required": [
    "data"
  ],
  "properties": {
    "data": {
      "type": "object",
      "title": "Data-object containing accumulated data from Bluetooth-sensors",
      "additionalProperties": false,
      "required": [
        "coordinates",
        "timestamp",
        "gw_mac",
        "tags"
      ],
      "properties": {
        "coordinates": {
          "type": "string",
          "title": "GPS-coordinates of Ruuvi Gateway (optional)",
          "examples": [
            ""
          ]
        },
        "timestamp": {
          "type": "string",
          "title": "Timestamp (Unix-time) when this JSON is generated by Gateway",
          "examples": [
            "1665742208"
          ]
        },
        "gw_mac": {
          "type": "string",
          "title": "MAC-address of Ruuvi Gateway",
          "examples": [
            "C8:25:2D:8E:9C:2C"
          ]
        },
        "tags": {
          "type": "object",
          "title": "Object containing messages from Bluetooth sensors, with MAC addresses as keys",
          "patternProperties": {
            "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$": {
              "type": "object",
              "title": "MAC-address of Bluetooth-sensor",
              "additionalProperties": false,
              "required": [
                "rssi",
                "timestamp",
                "data"
              ],
              "properties": {
                "rssi": {
                  "type": "integer",
                  "title": "RSSI",
                  "examples": [
                    -71
                  ]
                },
                "timestamp": {
                  "type": "string",
                  "title": "Timestamp (Unix-time) when the message from Bluetooth-sensor was received by Gateway",
                  "examples": [
                    "1653633986"
                  ]
                },
                "data": {
                  "type": "string",
                  "title": "Relayed message from Bluetooth-sensor in hex encoding",
                  "examples": [
                    "0201061BFF99040513C85714C7CC00240008041CAB76F41C3CC6A5B9E0AD06"
                  ]
                },
                "dataFormat": {
                  "type": "integer",
                  "title": "Data format",
                  "examples": [
                    5,
                    6
                  ]
                },
                "temperature": {
                  "type": "number",
                  "title": "Temperature in C",
                  "examples": [
                    28,
                    28.0,
                    28.745
                  ]
                },
                "humidity": {
                  "type": "number",
                  "title": "Humidity in percentage",
                  "examples": [
                    45,
                    45.3050
                  ]
                },
                "pressure": {
                  "type": "integer",
                  "title": "Pressure in Pa",
                  "examples": [
                    150812
                  ]
                },
                "accelX": {
                  "type": "number",
                  "title": "AccelX",
                  "examples": [
                    0.012,
                    -0.012
                  ]
                },
                "accelY": {
                  "type": "number",
                  "title": "AccelY",
                  "examples": [
                    0.012,
                    -0.012
                  ]
                },
                "accelZ": {
                  "type": "number",
                  "title": "AccelZ",
                  "examples": [
                    0.012,
                    -0.012
                  ]
                },
                "movementCounter": {
                  "type": "integer",
                  "title": "Movement counter",
                  "examples": [
                    0,
                    164
                  ]
                },
                "voltage": {
                  "type": "number",
                  "title": "Battery voltage",
                  "examples": [
                    2.556
                  ]
                },
                "txPower": {
                  "type": "integer",
                  "title": "Tx power",
                  "examples": [
                    -32
                  ]
                },
                "measurementSequenceNumber": {
                  "type": "integer",
                  "title": "Measurement sequence number",
                  "examples": [
                    19807
                  ]
                },
                "id": {
                  "type": "string",
                  "title": "MAC-address of Bluetooth-sensor",
                  "pattern": "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$",
                  "examples": [
                    "F4:1F:0C:28:CB:D6"
                  ]
                }
              }
            }
          },
          "examples": [
            {
              "F4:1F:0C:28:CB:D6": {
                "rssi": -34,
                "timestamp": "1665742208",
                "data": "0201061BFF99040514EE4C08C5370018FFB803E8A7F6E67A8EF41F0C28CBD6"
              },
              "C6:A5:B9:E0:AD:06": {
                "rssi": -33,
                "timestamp": "1665742208",
                "data": "0201061BFF99040514A24454C4FF0030FFFC0420AB76207A89C6A5B9E0AD06"
              },
              "F4:C6:46:2C:3E:B4": {
                "rssi": -46,
                "timestamp": "1687922292",
                "data": "0201061BFF99040515EF481EC6630038001003F08376502051F4C6462C3EB4",
                "dataFormat": 5,
                "temperature": 28.075,
                "humidity": 46.1550,
                "pressure": 150787,
                "accelX": 0.056,
                "accelY": 0.016,
                "accelZ": 1.008,
                "movementCounter": 80,
                "voltage": 2.651,
                "txPower": -32,
                "measurementSequenceNumber": 8273,
                "id": "F4:C6:46:2C:3E:B4"
              }
            }
          ]
        }
      }
    }
  },
  "examples": [
    {
      "data": {
        "coordinates": "",
        "timestamp": "1665742208",
        "gw_mac": "C8:25:2D:8E:9C:2C",
        "tags": {
          "F4:1F:0C:28:CB:D6": {
            "rssi": -34,
            "timestamp": "1665742208",
            "data": "0201061BFF99040514EE4C08C5370018FFB803E8A7F6E67A8EF41F0C28CBD6"
          },
          "C6:A5:B9:E0:AD:06": {
            "rssi": -33,
            "timestamp": "1665742208",
            "data": "0201061BFF99040514A24454C4FF0030FFFC0420AB76207A89C6A5B9E0AD06"
          }
        }
      }
    },
    {
      "data": {
        "coordinates": "",
        "timestamp": "1687954402",
        "gw_mac": "C8:25:2D:8E:9C:2C",
        "tags": {
          "C6:A5:B9:E0:AD:06": {
            "rssi": -39,
            "timestamp": "1687954402",
            "data": "0201061BFF99040514B637A4C5230030FFF8041894F6DA8FC4C6A5B9E0AD06",
            "dataFormat": 5,
            "temperature": 26.510,
            "humidity": 35.6100,
            "pressure": 150467,
            "accelX": 0.048,
            "accelY": -0.008,
            "accelZ": 1.048,
            "movementCounter": 218,
            "voltage": 2.791,
            "txPower": -32,
            "measurementSequenceNumber": 36804,
            "id": "C6:A5:B9:E0:AD:06"
          },
          "F4:1F:0C:28:CB:D6": {
            "rssi": -47,
            "timestamp": "1687954402",
            "data": "0201061BFF99040515E54395C5630004FFB803E47556AD7E2DF41F0C28CBD6",
            "dataFormat": 5,
            "temperature": 28.025,
            "humidity": 43.2525,
            "pressure": 150531,
            "accelX": 0.004,
            "accelY": -0.072,
            "accelZ": 0.996,
            "movementCounter": 173,
            "voltage": 2.538,
            "txPower": -32,
            "measurementSequenceNumber": 32301,
            "id": "F4:1F:0C:28:CB:D6"
          }
        }
      }
    }
  ]
}

```


# HTTP: Gateway status

Data format describing Gateway operation status

The JSON data format description is provided in JSON schema format (<https://json-schema.org>), which provides clear human- and machine-readable documentation with examples:

```json
{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$id": "http://ruuvi.com/schemas/ruuvi_gw_status.schema.json",
  "title": "Status of Gateway",
  "type": "object",
  "required": [
    "DEVICE_ADDR",
    "ESP_FW",
    "NRF_FW",
    "UPTIME",
    "NONCE",
    "CONNECTION",
    "NUM_CONN_LOST",
    "RESET_REASON",
    "RESET_CNT",
    "RESET_INFO",
    "SENSORS_SEEN",
    "ACTIVE_SENSORS",
    "INACTIVE_SENSORS"
  ],
  "properties": {
    "DEVICE_ADDR": {
      "title": "MAC-address of Gateway",
      "type": "string",
      "examples": [
        "C8:25:2D:8E:9C:2C"
      ]
    },
    "ESP_FW": {
      "title": "Firmware version of Gateway",
      "type": "string",
      "examples": [
        "v1.11.2"
      ]
    },
    "NRF_FW": {
      "title": "Firmware version of NRF52 co-processor",
      "type": "string",
      "examples": [
        "v0.7.2"
      ]
    },
    "UPTIME": {
      "title": "Gateway uptime counter (seconds)",
      "type": "string",
      "examples": [
        "11"
      ]
    },
    "NONCE": {
      "title": "Nonce - sequentially incremented number for each message, the initial value of which is set randomly",
      "type": "string",
      "examples": [
        "2821115890"
      ]
    },
    "CONNECTION": {
      "title": "Network connection type",
      "type": "string",
      "pattern": "^(WIFI|ETHERNET)$",
      "examples": [
        "WIFI",
        "ETH"
      ]
    },
    "NUM_CONN_LOST": {
      "title": "Network connection loss counter",
      "type": "string",
      "examples": [
        "0"
      ]
    },
    "RESET_REASON": {
      "title": "ESP32 reset reason",
      "type": "string",
      "examples": [
        "POWER_ON",
        "SW",
        "PANIC",
        "INT_WDT",
        "TASK_WDT",
        "WDT",
        "BROWNOUT"
      ]
    },
    "RESET_CNT": {
      "title": "Number of restarts after power on",
      "type": "string",
      "examples": [
        "1",
        "2",
        "10"
      ]
    },
    "RESET_INFO": {
      "title": "A description of the cause of the software-caused reboot",
      "type": "string",
      "examples": [
        "",
        "Reset by software: Network watchdog",
        "Reset by software: Low memory",
        "Reset by software: Restart the system after firmware update (failed)",
        "Reset by software: Restart the system after firmware update (auto-updating failed)",
        "Reset by software: Restart the system after firmware update (manual updating via WiFi hotspot failed)",
        "Reset by software: Restart the system after firmware update (manual updating via LAN failed)",
        "Reset by software: Restart the system after firmware update (completed successfully)",
        "Reset by software: Restart the system after firmware update (auto-updating completed successfully)",
        "Reset by software: Restart the system after firmware update (manual updating via WiFi hotspot completed successfully",
        "Reset by software: Restart the system after firmware update (manual updating via LAN completed successfully)",
        "Reset by software: Rollback firmware",
        "Reset by software: The CONFIGURE button has been released - restart system",
        "Reset by software: System restart is activated by the Configure button",
        "Reset by panic: Guru Meditation Error: Core  0 panic'ed (InstrFetchProhibited). Exception was unhandled.\r\n\r\nCore  0 register dump:\r\nPC      : 0xffffffff  PS      : 0x00060930  A0      : 0x800e9948  A1      : 0x3ffc22a0  \r\nA2      : 0xffffffff  A3      : 0x3ffafe48  A4      : 0x3ffafe48  A5      : 0x3ffc2344  \r\nA6      : 0x000000a5  A7      : 0x00060023  A8      : 0x800e93c3  A9      : 0x3ffb98a8  \r\nA10     : 0x00000000  A11     : 0x3f40ddb0  A12     : 0x3f40d814  A13     : 0xf6e515dd  \r\nA14     : 0x3ffc2250  A15     : 0x3ffafe48  SAR     : 0x00000004  EXCCAUSE: 0x00000014  \r\nEXCVADDR: 0xfffffffc  LBEG    : 0x400014fd  LEND    : 0x4000150d  LCOUNT  : 0xfffffffe  \r\n\r\nBacktrace:0x7ffffffc:0x3ffc22a0 0x400e9945:0x3ffc22f0 0x400e9a24:0x3ffc2330 0x400d8f24:0x3ffc2370 0x400d55db:0x3ffc23c0 0x4008ae1e:0x3ffc23f0\r\n\r\n\r\nELF file SHA256: f993cfd8dbfd7c9c\r\n\r\nRebooting...\r\n"
      ]
    },
    "SENSORS_SEEN": {
      "title": "Number of Bluetooth-sensors this Gateway has seen",
      "type": "string",
      "examples": [
        "4"
      ]
    },
    "ACTIVE_SENSORS": {
      "type": "array",
      "title": "List of active Bluetooth-sensors",
      "items": {
        "type": "object",
        "title": "Status of active Bluetooth-sensor",
        "required": [
          "MAC",
          "COUNTER"
        ],
        "properties": {
          "MAC": {
            "title": "MAC-address of Bluetooth-sensor",
            "type": "string",
            "pattern": "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$",
            "examples": [
              "E3:75:CF:37:4E:23",
              "C6:A5:B9:E0:AD:06"
            ]
          },
          "COUNTER": {
            "title": "Counter of messages, received from this Bluetooth-sensor",
            "type": "string",
            "examples": [
              "5"
            ]
          }
        }
      },
      "examples": [
        [],
        [
          {
            "MAC": "E3:75:CF:37:4E:23",
            "COUNTER": "5"
          },
          {
            "MAC": "C6:A5:B9:E0:AD:06",
            "COUNTER": "4"
          }
        ]
      ]
    },
    "INACTIVE_SENSORS": {
      "title": "List of inactive Bluetooth-sensors",
      "type": "array",
      "items": {
        "title": "MAC-address of inactive Bluetooth-sensor",
        "type": "string",
        "pattern": "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$",
        "examples": [
          "F4:C6:46:2C:3E:B4"
        ]
      },
      "examples": [
        [],
        [
          "F4:1F:0C:28:CB:D6"
        ],
        [
          "F4:1F:0C:28:CB:D6",
          "F4:C6:46:2C:3E:B4"
        ]
      ]
    }
  },
  "examples": [
    {
      "DEVICE_ADDR": "C8:25:2D:8E:9C:2C",
      "ESP_FW": "v1.11.2",
      "NRF_FW": "v0.7.2",
      "UPTIME": "11",
      "NONCE": "2821115890",
      "CONNECTION": "WIFI",
      "NUM_CONN_LOST": "0",
      "RESET_REASON": "POWER_ON",
      "RESET_CNT":    "1",
      "RESET_INFO":   "",
      "SENSORS_SEEN": "4",
      "ACTIVE_SENSORS": [
        {
          "MAC": "E3:75:CF:37:4E:23",
          "COUNTER": "5"
        },
        {
          "MAC": "C6:A5:B9:E0:AD:06",
          "COUNTER": "4"
        }
      ],
      "INACTIVE_SENSORS": [
        "F4:1F:0C:28:CB:D6",
        "F4:C6:46:2C:3E:B4"
      ]
    },
    {
      "DEVICE_ADDR":  "C8:25:2D:8E:9C:2C",
      "ESP_FW":       "v1.12.4-26-g42d064a-dirty",
      "NRF_FW":       "v1.0.0",
      "UPTIME":       "11",
      "NONCE":        "3375691106",
      "CONNECTION":   "ETHERNET",
      "NUM_CONN_LOST":        "0",
      "RESET_REASON": "PANIC",
      "RESET_CNT":    "3",
      "RESET_INFO":   "Reset by panic: Guru Meditation Error: Core  0 panic'ed (InstrFetchProhibited). Exception was unhandled.\r\n\r\nCore  0 register dump:\r\nPC      : 0xffffffff  PS      : 0x00060930  A0      : 0x800e9948  A1      : 0x3ffc22a0  \r\nA2      : 0xffffffff  A3      : 0x3ffafe48  A4      : 0x3ffafe48  A5      : 0x3ffc2344  \r\nA6      : 0x000000a5  A7      : 0x00060023  A8      : 0x800e93c3  A9      : 0x3ffb98a8  \r\nA10     : 0x00000000  A11     : 0x3f40ddb0  A12     : 0x3f40d814  A13     : 0xf6e515dd  \r\nA14     : 0x3ffc2250  A15     : 0x3ffafe48  SAR     : 0x00000004  EXCCAUSE: 0x00000014  \r\nEXCVADDR: 0xfffffffc  LBEG    : 0x400014fd  LEND    : 0x4000150d  LCOUNT  : 0xfffffffe  \r\n\r\nBacktrace:0x7ffffffc:0x3ffc22a0 0x400e9945:0x3ffc22f0 0x400e9a24:0x3ffc2330 0x400d8f24:0x3ffc2370 0x400d55db:0x3ffc23c0 0x4008ae1e:0x3ffc23f0\r\n\r\n\r\nELF file SHA256: f993cfd8dbfd7c9c\r\n\r\nRebooting...\r\n",
      "SENSORS_SEEN": "0",
      "ACTIVE_SENSORS":       [],
      "INACTIVE_SENSORS":     []
    },
    {
      "DEVICE_ADDR":  "C8:25:2D:8E:9C:2C",
      "ESP_FW":       "v1.12.4-27-g65c58ac-dirty",
      "NRF_FW":       "v1.0.0",
      "UPTIME":       "10",
      "NONCE":        "1537622823",
      "CONNECTION":   "ETHERNET",
      "NUM_CONN_LOST":        "0",
      "RESET_REASON": "SW",
      "RESET_CNT":    "2",
      "RESET_INFO":   "Reset by software: Low memory",
      "SENSORS_SEEN": "0",
      "ACTIVE_SENSORS":       [],
      "INACTIVE_SENSORS":     []
    }
  ]
}

```


# Gateway configuration

This format is used both for storing the gateway configuration and for reading/writing it.

Depending on how the configuration is used, some data may be excluded. For example, when reading the configuration via HTTP, it does not contain passwords. When writing the configuration, all missing fields retain their previous values.

This data format is used in the following cases:

1. Storing the Gateway configuration in the internal flash memory
2. Configuring the default gateway configuration (gw\_*cfg*\_*default.json) on the separate "gw\_cfg\_def*" partition.\
   Some attributes can be omitted, in this case, the hard-coded defaults will be used.
3. Reading configuration from the Gateway via HTTP using API GET "/ruuvi.json".\
   All passwords are excluded in this case. Additional informational attributes will be added to the generated json: `fw_ver`, `nrf52_fw_ver`, `gw_mac`.
4. Writing network part of the configuration to the Gateway via HTTP using API POST "/ruuvi.json".\
   Only the `use_eth` *attribute and `eth_...` or `wifi_...`* attributes are used in this case, other attributes will be ignored.
5. Writing the main part of the configuration to the Gateway via HTTP using API POST "/ruuvi.json"\
   The `use_eth` attribute should not be present in json, all network-related attributes will be ignored.
6. Automatic configuration downloading from a remote server

#### Example of the default configuration

```json
{
  "wifi_sta_config": {
    "ssid": "",
    "password": ""
  },
  "wifi_ap_config": {
    "password": "",
    "channel": 1
  },
  "use_eth": true,
  "eth_dhcp": true,
  "eth_static_ip": "",
  "eth_netmask": "",
  "eth_gw": "",
  "eth_dns1": "",
  "eth_dns2": "",
  "remote_cfg_use": false,
  "remote_cfg_url": "",
  "remote_cfg_auth_type": "no",
  "remote_cfg_auth_bearer_token": "",
  "remote_cfg_auth_basic_user": "",
  "remote_cfg_auth_basic_pass": "",
  "remote_cfg_refresh_interval_minutes": 0,
  "use_http_ruuvi": true,
  "use_http": true,
  "http_url": "https://network.ruuvi.com/record",
  "http_data_format": "ruuvi",
  "http_auth": "none",
  "use_http_stat": true,
  "http_stat_url": "https://network.ruuvi.com/status",
  "http_stat_user": "",
  "http_stat_pass": "",
  "use_mqtt": false,
  "mqtt_disable_retained_messages": false,
  "mqtt_transport": "TCP",
  "mqtt_server": "test.mosquitto.org",
  "mqtt_port": 1883,
  "mqtt_prefix": "",
  "mqtt_client_id": "",
  "mqtt_user": "",
  "mqtt_pass": "",
  "lan_auth_type": "lan_auth_default",
  "lan_auth_api_key": "",
  "lan_auth_api_key_rw": "",
  "auto_update_cycle": "regular",
  "auto_update_weekdays_bitmask": 127,
  "auto_update_interval_from": 0,
  "auto_update_interval_to": 24,
  "auto_update_tz_offset_hours": 3,
  "ntp_use": true,
  "ntp_use_dhcp": false,
  "ntp_server1": "time.google.com",
  "ntp_server2": "time.cloudflare.com",
  "ntp_server3": "time.nist.gov",
  "ntp_server4": "pool.ntp.org",
  "company_use_filtering": true,
  "company_id": 1177,
  "scan_coded_phy": false,
  "scan_1mbit_phy": true,
  "scan_extended_payload": true,
  "scan_channel_37": true,
  "scan_channel_38": true,
  "scan_channel_39": true,
  "scan_filter_allow_listed": false,
  "scan_filter_list": [],
  "coordinates": ""
}

```

#### Examples of network configuration

Here is the example of json to configure Gateway to use Ethernet:

```json
{
  "use_eth": true,
  "eth_dhcp": true
}
```

Here is the example of json to configure Gateway to use WiFi and activate WiFi access point on channel 7:

```json
{
  "use_eth": false,
  "wifi_ap_config": {
    "channel": 7
  }
}
```

#### JSON schema

The format of this JSON file is described as JSON schema, which provides human- and machine- readable documentation. The JSON schema also contains examples both for each of the properties and for the whole file. The example at the end of JSON schema contains default settings for Ruuvi Gateway.

```json
{
  "$schema": "https://json-schema.org/draft/2019-09/schema",
  "$id": "http://ruuvi.com/schemas/ruuvi_gw_cfg.schema.json",
  "title": "Ruuvi gateway configuration",
  "type": "object",
  "additionalProperties": false,
  "required": [
  ],
  "properties": {
    "fw_ver": {
      "title": "Current firmware version",
      "description": "This field is generated only when configuration is requested via HTTP.",
      "type": "string",
      "examples": [
        "v1.13.1"
      ]
    },
    "nrf52_fw_ver": {
      "title": "Current firmware version of nRF52",
      "description": "This field is generated only when configuration is requested via HTTP.",
      "type": "string",
      "examples": [
        "v1.0.0"
      ]
    },
    "gw_mac": {
      "title": "Gateway MAC address",
      "description": "This field is generated only when configuration is requested via HTTP.",
      "type": "string",
      "examples": [
        "C8:25:2D:8E:9C:2C"
      ]
    },
    "storage": {
      "title": "SSL certificates storage info",
      "description": "This field is generated only when configuration is requested via HTTP.",
      "type": "object",
      "required": [
        "storage_ready",
        "http_cli_cert",
        "http_cli_key",
        "http_srv_cert",
        "stat_cli_cert",
        "stat_cli_key",
        "stat_srv_cert",
        "mqtt_cli_cert",
        "mqtt_cli_key",
        "mqtt_srv_cert",
        "rcfg_cli_cert",
        "rcfg_cli_key",
        "rcfg_srv_cert"
      ],
      "properties": {
        "storage_ready": {
          "type": "boolean",
          "title": "Status of storage"
        },
        "http_cli_cert": {
          "type": "boolean",
          "title": "Status of SSL client certificate for HTTP target"
        },
        "http_cli_key": {
          "type": "boolean",
          "title": "Status of SSL client private key for HTTP target"
        },
        "http_srv_cert": {
          "type": "boolean",
          "title": "Status of SSL server certificate for HTTP target"
        },
        "stat_cli_cert": {
          "type": "boolean",
          "title": "Status of SSL client certificate for Statistics"
        },
        "stat_cli_key": {
          "type": "boolean",
          "title": "Status of SSL client private key for Statistics"
        },
        "stat_srv_cert": {
          "type": "boolean",
          "title": "Status of SSL server certificate for Statistics"
        },
        "mqtt_cli_cert": {
          "type": "boolean",
          "title": "Status of SSL client certificate for MQTT"
        },
        "mqtt_cli_key": {
          "type": "boolean",
          "title": "Status of SSL client private key for MQTT"
        },
        "mqtt_srv_cert": {
          "type": "boolean",
          "title": "Status of SSL server certificate for MQTT"
        },
        "rcfg_cli_cert": {
          "type": "boolean",
          "title": "Status of SSL client certificate for the remote configuration server"
        },
        "rcfg_cli_key": {
          "type": "boolean",
          "title": "Status of SSL client private key for the remote configuration server"
        },
        "rcfg_srv_cert": {
          "type": "boolean",
          "title": "Status of SSL server certificate for the remote configuration server"
        }
      }
    },
    "wifi_sta_config": {
      "title": "Wi-Fi credentials",
      "description": "Gateway will connect to the specified WiFi SSID if it's not empty. Note: 'use_eth' should be 'false'",
      "type": "object",
      "required": [
        "ssid"
      ],
      "properties": {
        "ssid": {
          "type": "string",
          "title": "WiFi network name (SSID)",
          "examples": [
            "",
            "MyWiFi_123"
          ]
        },
        "password": {
          "type": "string",
          "title": "WiFi password",
          "examples": [
            "",
            "my_password"
          ]
        }
      },
      "examples": [
        {
          "ssid": "",
          "password": ""
        },
        {
          "ssid": "MyWiFi_123",
          "password": "my_password"
        }
      ]
    },
    "wifi_ap_config": {
      "title": "WiFi access point default settings",
      "type": "object",
      "required": [
      ],
      "properties": {
        "password": {
          "type": "string",
          "default": "",
          "title": "Default password for connecting to RuuviGatewayXXXX access point",
          "examples": [
            "",
            "12345678"
          ]
        },
        "channel": {
          "type": "integer",
          "default": 1,
          "title": "Default channel for Wi-Fi access point"
        }
      }
    },
    "use_eth": {
      "title": "Network connection type: Ethernet/WiFi",
      "type": "boolean"
    },
    "eth_dhcp": {
      "title": "IP configuration mode (DHCP/manual) when connected via Ethernet",
      "type": "boolean",
      "default": true
    },
    "eth_static_ip": {
      "title": "Manual IP configuration: IP address",
      "type": "string"
    },
    "eth_netmask": {
      "title": "Manual IP configuration: Subnet mask",
      "type": "string"
    },
    "eth_gw": {
      "title": "Manual IP configuration: Network gateway IP address",
      "type": "string"
    },
    "eth_dns1": {
      "title": "Manual IP configuration: IP address of DNS server1",
      "type": "string"
    },
    "eth_dns2": {
      "title": "Manual IP configuration: IP address of DNS server2",
      "type": "string"
    },
    "remote_cfg_use": {
      "title": "Enable automatic downloading of Gateway configuration from a remote server",
      "type": "boolean",
      "default": false
    },
    "remote_cfg_url": {
      "title": "URL of the remote server for automatic Gateway configuration downloading",
      "type": "string"
    },
    "remote_cfg_auth_type": {
      "title": "HTTP authentication credentials for the remote configuration server for automatic Gateway configuration downloading",
      "description": "It should not be empty if 'remote_cfg_use' is 'true'",
      "type": "string",
      "pattern": "^(|none|no|basic|bearer)$",
      "default": "",
      "examples": [
        "",
        "none",
        "basic",
        "bearer"
      ]
    },
    "remote_cfg_auth_bearer_token": {
      "title": "A token for HTTP bearer authentication for the remote configuration server for automatic Gateway configuration downloading",
      "type": "string"
    },
    "remote_cfg_auth_basic_user": {
      "title": "Username for HTTP basic authentication for the remote configuration server for automatic Gateway configuration downloading",
      "type": "string"
    },
    "remote_cfg_auth_basic_pass": {
      "title": "Password for HTTP basic authentication for the remote configuration server for automatic Gateway configuration downloading",
      "type": "string"
    },
    "remote_cfg_refresh_interval_minutes": {
      "title": "Period for checking a new gateway configuration on the remote configuration server (in minutes)",
      "type": "integer"
    },
    "remote_cfg_use_ssl_client_cert": {
      "title": "Enable use of SSL client certificate for authentication on the remote configuration server",
      "type": "boolean",
      "default": false
    },
    "remote_cfg_use_ssl_server_cert": {
      "title": "Enable use of SSL server certificate to authenticate the remote configuration server",
      "type": "boolean",
      "default": false
    },
    "use_http_ruuvi": {
      "title": "Enable HTTP relaying mode to Ruuvi cloud",
      "type": "boolean",
      "default": true
    },
    "use_http": {
      "title": "Enable HTTP relaying mode to a custom server",
      "type": "boolean",
      "default": false
    },
    "http_data_format": {
      "title": "Data format used for HTTP transmission",
      "type": "string",
      "pattern": "^(|ruuvi)$",
      "default": "ruuvi",
      "examples": [
        "ruuvi"
      ]
    },
    "http_auth": {
      "title": "HTTP authentication type",
      "type": "string",
      "pattern": "^(|none|basic|bearer|token)$",
      "default": "none",
      "examples": [
        "",
        "none",
        "basic",
        "bearer",
        "token"
      ]
    },
    "http_url": {
      "title": "URL of the server to which the data collected from Bluetooth sensors will be sent",
      "type": "string",
      "default": "https://network.ruuvi.com/record",
      "examples": [
        "https://network.ruuvi.com/record",
        "http://my_server123.com:8080/record"
      ]
    },
    "http_period": {
      "title": "Period of sending data via HTTP(S)",
      "type": "integer",
      "default": 10,
      "examples": [
        10,
        60
      ]
    },
    "http_user": {
      "title": "Username for HTTP basic authentication for the server",
      "type": "string",
      "default": ""
    },
    "http_pass": {
      "title": "Password for HTTP basic authentication for the server",
      "type": "string",
      "default": ""
    },
    "http_bearer_token": {
      "title": "Bearer token used for authentication",
      "description": "Bearer token is used when http_auth equals to 'bearer'",
      "type": "string",
      "default": ""
    },
    "http_api_key": {
      "title": "API key used for authentication",
      "description": "This API key is used when http_auth equals to 'token'",
      "type": "string",
      "default": ""
    },
    "http_use_ssl_client_cert": {
      "title": "Enable use of SSL client certificate for authentication on the HTTPS server",
      "type": "boolean",
      "default": false
    },
    "http_use_ssl_server_cert": {
      "title": "Enable use of SSL server certificate to authenticate the HTTPS server",
      "type": "boolean",
      "default": false
    },
    "use_http_stat": {
      "title": "Enable sending Gateway status to the HTTP-server",
      "type": "boolean",
      "default": true
    },
    "http_stat_url": {
      "title": "URL of the server to which the gateway status will be sent",
      "type": "string",
      "default": "https://network.ruuvi.com/status",
      "examples": [
        "https://network.ruuvi.com/status",
        "http://my_server123.com:8080/status"
      ]
    },
    "http_stat_user": {
      "title": "Username for HTTP basic authentication for the server",
      "type": "string",
      "default": ""
    },
    "http_stat_pass": {
      "title": "Password for HTTP basic authentication for the server",
      "type": "string",
      "default": ""
    },
    "http_stat_use_ssl_client_cert": {
      "title": "Enable use of SSL client certificate for authentication on the statistics server",
      "type": "boolean",
      "default": false
    },
    "http_stat_use_ssl_server_cert": {
      "title": "Enable use of SSL server certificate to authenticate the statistics server",
      "type": "boolean",
      "default": false
    },
    "use_mqtt": {
      "title": "Enable MQTT relaying mode",
      "type": "boolean",
      "default": false
    },
    "mqtt_disable_retained_messages": {
      "title": "Disable MQTT retained messages",
      "type": "boolean",
      "default": false
    },
    "mqtt_transport": {
      "title": "MQTT transport type to use",
      "description": "TCP - MQTT over TCP, SSL - MQTT over SSL, WS - MQTT over WebSockets, WSS - MQTT over secure WebSockets",
      "type": "string",
      "pattern": "^(TCP|SSL|WS|WSS)$",
      "default": "TCP",
      "examples": [
        "TCP",
        "SSL",
        "WS",
        "WSS"
      ]
    },
    "mqtt_data_format": {
      "title": "Data format used for MQTT transmission",
      "description": "ruuvi_raw - raw data only, ruuvi_raw_and_decoded - raw and decoded data, ruuvi_decoded - decoded data only",
      "type": "string",
      "pattern": "^(ruuvi_raw|ruuvi_raw_and_decoded|ruuvi_decoded)$",
      "default": "ruuvi_raw",
      "examples": [
        "ruuvi_raw",
        "ruuvi_raw_and_decoded",
        "ruuvi_decoded"
      ]
    },
    "mqtt_server": {
      "title": "MQTT server address",
      "type": "string",
      "examples": [
        "test.mosquitto.org"
      ]
    },
    "mqtt_port": {
      "title": "MQTT server port",
      "type": "integer",
      "default": 1883,
      "examples": [
        1883,
        8886,
        8080,
        8081
      ]
    },
    "mqtt_prefix": {
      "title": "MQTT topic prefix",
      "description": "Full MQTT topic is formed by joining the prefix and Bluetooth-sensor's MAC-address. If 'mqtt_prefix' is empty, then default prefix is used: 'ruuvi/<gateway_MAC_address>/'",
      "type": "string",
      "default": "",
      "examples": [
        "ruuvi/C8:25:2D:8E:9C:2C/"
      ]
    },
    "mqtt_client_id": {
      "title": "MQTT client ID",
      "description": "If 'mqtt_client_id' is empty, then default client ID is used: '<gateway_MAC_address>'",
      "type": "string",
      "default": "",
      "examples": [
        "",
        "C8:25:2D:8E:9C:2C",
        "my_mqtt_client1"
      ]
    },
    "mqtt_user": {
      "title": "User name for MQTT authentication",
      "type": "string",
      "default": ""
    },
    "mqtt_pass": {
      "title": "Password for MQTT authentication",
      "type": "string",
      "default": ""
    },
    "mqtt_use_ssl_client_cert": {
      "title": "Enable use of SSL client certificate for authentication on the MQTT server",
      "type": "boolean",
      "default": false
    },
    "mqtt_use_ssl_server_cert": {
      "title": "Enable use of SSL server certificate to authenticate the MQTT server",
      "type": "boolean",
      "default": false
    },
    "lan_auth_type": {
      "title": "Configuring the authentication type when accessing Gateway from LAN",
      "description": "'lan_auth_default' - Ruuvi-authentication with username 'Admin' and as a password the Unique ID is used (in format XX:XX:XX:XX:XX:XX:XX:XX) which is printed on the bottom of the Ruuvi Gateway. 'lan_auth_ruuvi' - Ruuvi-authentication, login/password should be specified in 'lan_auth_user' and 'lan_auth_pass'. 'lan_auth_deny' - deny access from LAN. 'lan_auth_allow' - allow access from LAN without a password. 'lan_auth_basic' - HTTP basic authentication, login/password should be specified in 'lan_auth_user' and 'lan_auth_pass'. 'lan_auth_digest' - HTTP digest authentication, login/password should be specified in 'lan_auth_user' and 'lan_auth_pass'.",
      "type": "string",
      "pattern": "^(lan_auth_default|lan_auth_ruuvi|lan_auth_deny|lan_auth_allow|lan_auth_basic|lan_auth_digest)$",
      "default": "lan_auth_default",
      "examples": [
        "lan_auth_default",
        "lan_auth_ruuvi",
        "lan_auth_deny",
        "lan_auth_allow",
        "lan_auth_basic",
        "lan_auth_digest"
      ]
    },
    "lan_auth_user": {
      "title": "Login for authentication when accessing from LAN",
      "type": "string",
      "default": "Admin",
      "examples": [
        "Admin"
      ]
    },
    "lan_auth_pass": {
      "title": "Password for authentication when accessing from LAN",
      "type": "string"
    },
    "lan_auth_api_key_use": {
      "title": "Use API key (token) for HTTP bearer authentication for read-only access from LAN",
      "description": "This field is generated when configuration is read via HTTP ('lan_auth_api_key' is not generated in this case)",
      "type": "boolean"
    },
    "lan_auth_api_key": {
      "title": "API key (token) for HTTP bearer authentication for read-only access from LAN",
      "description": "If 'lan_auth_api_key' is empty, then bearer authentication is disabled.",
      "type": "string",
      "default": "",
      "examples": [
        "304uOrJMoCNEVaPaXswV9U1qRDPZFbl0V2x7OXHM5nw="
      ]
    },
    "lan_auth_api_key_rw_use": {
      "title": "Use API key (token) for HTTP bearer authentication for read/write access from LAN",
      "description": "This field is generated when configuration is read via HTTP ('lan_auth_api_key_rw' is not generated in this case)",
      "type": "boolean"
    },
    "lan_auth_api_key_rw": {
      "title": "API key (token) for HTTP bearer authentication for read/write access from LAN",
      "description": "If 'lan_auth_api_key_rw' is empty, then bearer authentication is disabled.",
      "type": "string",
      "default": "",
      "examples": [
        "304uOrJMoCNEVaPaXswV9U1qRDPZFbl0V2x7OXHM5nw="
      ]
    },
    "auto_update_cycle": {
      "title": "Configure firmware auto-updating rules.",
      "description": "'regular' - check for updates 1-2 times a day according to the schedule, install new versions only 2 weeks after release. 'beta' - install new versions as soon as a new version is released. 'manual' - do not check for firmware updates and do not install updates automatically",
      "type": "string",
      "pattern": "^(regular|beta|manual)$",
      "default": "regular",
      "examples": [
        "regular",
        "beta",
        "manual"
      ]
    },
    "auto_update_weekdays_bitmask": {
      "title": "Configure firmware auto-updating schedule: specify weekdays",
      "description": "Bit-mask for weekdays: bit 0 - Sunday, bit 1 - Monday, ..., bit 6 - Saturday",
      "type": "integer",
      "default": 127,
      "examples": [
        1,
        3,
        127
      ]
    },
    "auto_update_interval_from": {
      "title": "Configure firmware auto-updating schedule: start time (local timezone)",
      "description": "0 - 00:00, 1 - 01:00, 2 - 02:00, ..., 23 - 23:00",
      "type": "integer",
      "default": 0,
      "examples": [
        0,
        1,
        23
      ]
    },
    "auto_update_interval_to": {
      "title": "Configure firmware auto-updating schedule: end time (local timezone)",
      "description": "1 - 01:00, 2 - 02:00, ..., 24 - 24:00",
      "type": "integer",
      "default": 24,
      "examples": [
        1,
        23,
        24
      ]
    },
    "auto_update_tz_offset_hours": {
      "title": "Configure firmware auto-updating schedule: local timezone offset (hours)",
      "type": "integer",
      "default": 3,
      "examples": [
        3
      ]
    },
    "ntp_use": {
      "title": "Enable time synchronization from NTP servers",
      "type": "boolean",
      "default": true
    },
    "ntp_use_dhcp": {
      "title": "Use DHCP to get the list of NTP servers",
      "type": "boolean",
      "default": false
    },
    "ntp_server1": {
      "title": "Address of NTP server 1 (used only if 'ntp_use_dhcp' is false).",
      "type": "string",
      "default": "time.google.com",
      "examples": [
        "time.google.com"
      ]
    },
    "ntp_server2": {
      "title": "Address of NTP server 2 (used only if 'ntp_use_dhcp' is false).",
      "type": "string",
      "default": "time.cloudflare.com",
      "examples": [
        "time.cloudflare.com"
      ]
    },
    "ntp_server3": {
      "title": "Address of NTP server 3 (used only if 'ntp_use_dhcp' is false).",
      "type": "string",
      "default": "pool.ntp.org",
      "examples": [
        "pool.ntp.org"
      ]
    },
    "ntp_server4": {
      "title": "Address of NTP server 4 (used only if 'ntp_use_dhcp' is false).",
      "type": "string",
      "default": "time.ruuvi.com",
      "examples": [
        "time.ruuvi.com"
      ]
    },
    "company_use_filtering": {
      "title": "Enable filtering messages from Bluetooth sensors by company ID.",
      "type": "boolean",
      "default": true
    },
    "company_id": {
      "title": "Company ID for filtering messages from Bluetooth-sensors.",
      "type": "integer",
      "default": 1177,
      "examples": [
        1177
      ]
    },
    "scan_coded_phy": {
      "title": "Configure Bluetooth scanning: Use coded PHY (long range)",
      "type": "boolean",
      "default": false
    },
    "scan_1mbit_phy": {
      "title": "Configure Bluetooth scanning: Use Use 1 MBit/s PHY",
      "type": "boolean",
      "default": true
    },
    "scan_extended_payload": {
      "title": "Configure Bluetooth scanning: Use extended payload",
      "type": "boolean",
      "default": true
    },
    "scan_channel_37": {
      "title": "Configure Bluetooth scanning: Use channel 37",
      "type": "boolean",
      "default": true
    },
    "scan_channel_38": {
      "title": "Configure Bluetooth scanning: Use channel 38",
      "type": "boolean",
      "default": true
    },
    "scan_channel_39": {
      "title": "Configure Bluetooth scanning: Use channel 39",
      "type": "boolean",
      "default": true
    },
    "scan_filter_allow_listed": {
      "title": "Type of Bluetooth sensors filtering",
      "description": "If it's true, only the sensors in the list will pass through the filter, other sensors will be filtered out. If it's false, then all sensors will pass through the filter except those in the list.",
      "type": "boolean",
      "default": false
    },
    "scan_filter_list": {
      "title": "List of sensors to filter.",
      "description": "Type of filtering is set by scan_filter_allow_listed. If scan_filter_list is empty, then filtering is not active",
      "type": "array",
      "default": [],
      "examples": [
        [],
        ["F4:1F:0C:28:CB:D6"],
        ["F4:1F:0C:28:CB:D6", "F4:C6:46:2C:3E:B4"]
      ]
    },
    "coordinates": {
      "title": "GPS-coordinates of the Gateway",
      "type": "string",
      "default": "",
      "examples": [
        ""
      ]
    },
    "fw_update_url": {
      "title": "URL of firmware update server",
      "type": "string",
      "default": "https://network.ruuvi.com/firmwareupdate",
      "examples": [
        "https://network.ruuvi.com/firmwareupdate"
      ]
    }
  },
  "examples": [
    {
      "wifi_sta_config": {
        "ssid": "",
        "password": ""
      },
      "wifi_ap_config": {
        "password": ""
      },
      "use_eth": false,
      "eth_dhcp": true,
      "eth_static_ip": "",
      "eth_netmask": "",
      "eth_gw": "",
      "eth_dns1": "",
      "eth_dns2": "",
      "remote_cfg_use": false,
      "remote_cfg_url": "",
      "remote_cfg_auth_type": "no",
      "remote_cfg_auth_bearer_token": "",
      "remote_cfg_auth_basic_user": "",
      "remote_cfg_auth_basic_pass": "",
      "remote_cfg_refresh_interval_minutes": 0,
      "use_http": true,
      "http_url": "https://network.ruuvi.com/record",
      "http_period": 10,
      "http_user": "",
      "http_pass": "",
      "use_http_stat": true,
      "http_stat_url": "https://network.ruuvi.com/status",
      "http_stat_user": "",
      "http_stat_pass": "",
      "use_mqtt": false,
      "mqtt_disable_retained_messages": false,
      "mqtt_transport": "TCP",
      "mqtt_data_format": "ruuvi_raw",
      "mqtt_server": "test.mosquitto.org",
      "mqtt_port": 1883,
      "mqtt_prefix": "",
      "mqtt_client_id": "",
      "mqtt_user": "",
      "mqtt_pass": "",
      "lan_auth_type": "lan_auth_default",
      "lan_auth_user": "Admin",
      "lan_auth_api_key": "",
      "auto_update_cycle": "regular",
      "auto_update_weekdays_bitmask": 127,
      "auto_update_interval_from": 0,
      "auto_update_interval_to": 24,
      "auto_update_tz_offset_hours": 3,
      "ntp_use": true,
      "ntp_use_dhcp": false,
      "ntp_server1": "time.google.com",
      "ntp_server2": "time.cloudflare.com",
      "ntp_server3": "pool.ntp.org",
      "ntp_server4": "time.ruuvi.com",
      "company_use_filtering": true,
      "company_id": 1177,
      "scan_coded_phy": false,
      "scan_1mbit_phy": true,
      "scan_extended_payload": true,
      "scan_channel_37": true,
      "scan_channel_38": true,
      "scan_channel_39": true,
      "coordinates": "",
      "fw_update_url": "https://network.ruuvi.com/firmwareupdate"
    }
  ]
}
```


# GW Examples


# Polling mode

It is possible to poll the accumulated data from the Ruuvi Gateway via HTTP.

On the "Access Settings" page, enable bearer authentication and set the bearer token:

<figure><img src="/files/vfj3As21HIcoEgmgd4Ky" alt=""><figcaption></figcaption></figure>

After that it is possible to poll data using the "/history" endpoint:

```shell
curl -v http://<RUUVI_GW_IP>/history 
    -H "Authorization: Bearer Uj+4tj24unVekco/lTLTRyxUfv1J8M6U+sbNsKTWRr0="
```

The accumulated data will be returned in JSON format: [HTTP: Time-stamped data from Bluetooth-sensors](/ruuvi-gateway-firmware/data-formats/http-time-stamped-data-from-bluetooth-sensors)or [HTTP: Data from Bluetooth-sensors without timestamps](/ruuvi-gateway-firmware/data-formats/http-data-from-bluetooth-sensors-without-timestamps)

Example of time-stamped data:

```json
{
  "data": {
    "coordinates": "",
    "timestamp": 1698727580,
    "gw_mac": "F4:D9:16:FE:4F:AD",
    "tags": {
      "E3:75:CF:37:4E:23": {
        "rssi": -51,
        "timestamp": 1698727579,
        "data": "0201061BFF990405164D77BDC74A03FCFF34FFFC87D6DA63CAE375CF374E23",
        "dataFormat": 5,
        "temperature": 28.545,
        "humidity": 76.6325,
        "pressure": 101018,
        "accelX": 1.020,
        "accelY": -0.204,
        "accelZ": -0.004,
        "movementCounter": 218,
        "voltage": 2.686,
        "txPower": -32,
        "measurementSequenceNumber": 25546,
        "id": "E3:75:CF:37:4E:23"
      },
      "F4:1F:0C:28:CB:D6": {
        "rssi": -66,
        "timestamp": 1698727579,
        "data": "0201061BFF99040514F564D7C7B40008FFF403E8B2767A669BF41F0C28CBD6",
        "dataFormat": 5,
        "temperature": 26.825,
        "humidity": 64.5375,
        "pressure": 101124,
        "accelX": 0.008,
        "accelY": -0.012,
        "accelZ": 1.000,
        "movementCounter": 122,
        "voltage": 3.027,
        "txPower": -32,
        "measurementSequenceNumber": 26267,
        "id": "F4:1F:0C:28:CB:D6"
      },
      "C6:A5:B9:E0:AD:06": {
        "rssi": -60,
        "timestamp": 1698727579,
        "data": "0201061BFF99040517C166ACC78C00540004041C8756086435C6A5B9E0AD06",
        "dataFormat": 5,
        "temperature": 30.405,
        "humidity": 65.7100,
        "pressure": 101084,
        "accelX": 0.084,
        "accelY": 0.004,
        "accelZ": 1.052,
        "movementCounter": 8,
        "voltage": 2.682,
        "txPower": -32,
        "measurementSequenceNumber": 25653,
        "id": "C6:A5:B9:E0:AD:06"
      }
    }
  }
}
```

Also, it is possible to request only raw data without decoding:

```bash
curl -v http://<RUUVI_GW_IP>/history?decode=false 
    -H "Authorization: Bearer Uj+4tj24unVekco/lTLTRyxUfv1J8M6U+sbNsKTWRr0="
```

Example of time-stamped data without decoding:

```json
{
  "data": {
    "coordinates": "",
    "timestamp": 1698727580,
    "gw_mac": "F4:D9:16:FE:4F:AD",
    "tags": {
      "E3:75:CF:37:4E:23": {
        "rssi": -51,
        "timestamp": 1698727579,
        "data": "0201061BFF990405164D77BDC74A03FCFF34FFFC87D6DA63CAE375CF374E23"
      },
      "F4:1F:0C:28:CB:D6": {
        "rssi": -66,
        "timestamp": 1698727579,
        "data": "0201061BFF99040514F564D7C7B40008FFF403E8B2767A669BF41F0C28CBD6"
      },
      "C6:A5:B9:E0:AD:06": {
        "rssi": -60,
        "timestamp": 1698727579,
        "data": "0201061BFF99040517C166ACC78C00540004041C8756086435C6A5B9E0AD06"
      }
    }
  }
}
```


# Poll endpoint "/metrics"

Extended internal information on the status of the Ruuvi Gateway can be obtained by polling the endpoint "/metrics".

On the "Access Settings" page, enable bearer authentication and set the bearer token:

<figure><img src="/files/vfj3As21HIcoEgmgd4Ky" alt=""><figcaption></figcaption></figure>

After that it is possible to poll data using the "/metrics" endpoint:

```shell
curl -v http://<RUUVI_GW_IP>/metrics 
    -H "Authorization: Bearer Uj+4tj24unVekco/lTLTRyxUfv1J8M6U+sbNsKTWRr0="
```

Example of the response:

```
ruuvigw_received_advertisements 500999
ruuvigw_uptime_us 171619762770
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_EXEC"} 107564
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_32BIT"} 107564
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_8BIT"} 59012
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_DMA"} 59012
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID2"} 0
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID3"} 0
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID4"} 0
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID5"} 0
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID6"} 0
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_PID7"} 0
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_SPIRAM"} 0
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_INTERNAL"} 107564
ruuvigw_heap_free_bytes{capability="MALLOC_CAP_DEFAULT"} 59012
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_EXEC"} 55568
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_32BIT"} 55568
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_8BIT"} 55568
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_DMA"} 55568
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID2"} 0
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID3"} 0
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID4"} 0
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID5"} 0
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID6"} 0
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_PID7"} 0
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_SPIRAM"} 0
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_INTERNAL"} 55568
ruuvigw_heap_largest_free_block_bytes{capability="MALLOC_CAP_DEFAULT"} 55568
ruuvigw_info{mac="C8:25:2D:8E:9C:2C",esp_fw="v1.12.4-29-g708c35d-dirty",nrf_fw="v1.0.0"} 1
ruuvigw_gw_cfg_crc32 1041546679
ruuvigw_ruuvi_json_crc32 3490230955

```


# Configuration update via API

If your Gateway is password protected, you can configure the Gateway to allow you to update the configuration on it programmatically using bearer authentication.

On the "Access Settings" page, enable full access using bearer authentication and set the API-key:

<figure><img src="/files/iirQvZvmOpQzPFKC9mIE" alt=""><figcaption></figcaption></figure>

If your Gateway is password protected and bearer authentication is not configured, you can still access the Gateway by using the 'RUUVISESSION' cookie from your browser. Open the configuration wizard in your browser and authenticate with the password, then open 'Developer Tools' in your browser, then open URL 'http\://\<RUUVI\_GW\_IP>/ruuvi.json'. In 'Developer Tools', open the 'Network' tab, select the 'Headers' sub-tab and locate the "RUUVISESSION" cookie:\
![](/files/GRPF1BKPFtnJtr259SrE)

Please note that you can only access the Gateway using the cookie from the same PC as the browser used for authentication.

### Examples

#### Getting configuration from the Gateway

In the following examples, the gateway configuration is stored in the 'ruuvi.json' file.

* The Gateway is remotely configurable without a password:

```bash
curl -v http://<RUUVI_GW_IP>/ruuvi.json --output ruuvi.json
```

* The Gateway is remotely configurable using a bearer token:

```bash
curl -v http://<RUUVI_GW_IP>/ruuvi.json 
  -H "Authorization: Bearer 1SDrQH1FkH+pON0GsSjt2gYeMSP02uYqfuu7LWdaBvY=" 
  --output ruuvi.json
```

* Using a cookie from the browser to access the Gateway:

```bash
curl -v http://<RUUVI_GW_IP>/ruuvi.json 
    --cookie "RUUVISESSION=SEUFTNJMCZASBWUO" 
    --output ruuvi.json
```

#### Uploading configuration to the Gateway

To upload the configuration from the "./ruuvi.json" file:

* The Gateway is remotely configurable without a password:

```bash
curl -v http://<RUUVI_GW_IP>/ruuvi.json 
  -H "Content-Type: application/json" 
  -d @./ruuvi.json 
```

* The Gateway is remotely configurable using a bearer token:

```bash
curl -v http://<RUUVI_GW_IP>/ruuvi.json 
  -H "Content-Type: application/json" 
  -H "Authorization: Bearer 1SDrQH1FkH+pON0GsSjt2gYeMSP02uYqfuu7LWdaBvY=" 
  -d @./ruuvi.json 
```

* Using a cookie from the browser to access the Gateway:

```bash
curl -v http://<RUUVI_GW_IP>/ruuvi.json 
  -H "Content-Type: application/json" 
  --cookie "RUUVISESSION=SEUFTNJMCZASBWUO" 
  -d @./ruuvi.json 
```

#### Disabling all data relay interfaces

These examples show how to disable relaying data to the Ruuvi cloud, custom HTTP(S) server and MQTT.

* The Gateway is remotely configurable without a password:

```bash
curl -v -X POST http://ruuvigateway9c2c.local/ruuvi.json 
    -H "Content-Type: application/json" 
    -d '{"use_http_ruuvi":false, "use_http":false, "use_mqtt":false}'
```

* The Gateway is remotely configurable using a bearer token:

```bash
curl -v -X POST http://ruuvigateway9c2c.local/ruuvi.json 
    -H "Content-Type: application/json" 
    -H "Authorization: Bearer 1SDrQH1FkH+pON0GsSjt2gYeMSP02uYqfuu7LWdaBvY=" 
    -d '{"use_http_ruuvi":false, "use_http":false, "use_mqtt":false}'
```

* Using a cookie from the browser to access the Gateway:

```bash
curl -v -X POST http://ruuvigateway9c2c.local/ruuvi.json 
    -H "Content-Type: application/json" 
    --cookie "RUUVISESSION=SEUFTNJMCZASBWUO" 
    -d '{"use_http_ruuvi":false, "use_http":false, "use_mqtt":false}'
```

#### Uploading network configuration

**Note**: The network settings must be uploaded separately from the main Gateway configuration.

Example of "ruuvi.json" with the network settings (See [Gateway configuration](/ruuvi-gateway-firmware/data-formats/gateway-configuration)):

```json
{
  "wifi_sta_config": {
    "ssid": "",
    "password": ""
  },
  "wifi_ap_config": {
    "password": "",
    "channel": 1
  },
  "use_eth": true,
  "eth_dhcp": true,
  "eth_static_ip": "",
  "eth_netmask": "",
  "eth_gw": "",
  "eth_dns1": "",
  "eth_dns2": ""
}
```

The network configuration must contain the **use\_eth** key and the main Gateway configuration must not.


# Firmware update via API

You can run a firmware update from a specified URL programmatically, using bearer authentication.

On the "Access Settings" page, enable full access using bearer authentication and set the API-key:

<figure><img src="/files/iirQvZvmOpQzPFKC9mIE" alt=""><figcaption></figcaption></figure>

Example of a command to run a firmware update from the URL: "<https://my\\_server.com:7000/gw\\_firmware/>"

```shell
    curl -v http://<RUUVI_GW_IP>/fw_update.json 
      -H "Authorization: Bearer 1SDrQH1FkH+pON0GsSjt2gYeMSP02uYqfuu7LWdaBvY=" 
      -H 'Content-Type: application/json' 
      -d '{"url":"https://my_server.com:7000/gw_firmware/"}'
```


# Configuration download from a remote server via API

You can trigger a forced configuration download programmatically, using bearer authentication, but you must first configure the Gateway to use [Automatic configuration download](/ruuvi-gateway-firmware/gateway-html-pages/automatic-configuration-download).

Configuration on the remote server must contain the field **lan\_auth\_api\_key\_rw**:

```json
{
  "remote_cfg_use": true, 
  "remote_cfg_url": "http://192.168.1.101:7000/", 
  "remote_cfg_refresh_interval_minutes": 10,
  "remote_cfg_auth_type": "no",
  "lan_auth_api_key_rw": "1SDrQH1FkH+pON0GsSjt2gYeMSP02uYqfuu7LWdaBvY="
}
```

Example of a command to trigger a configuration download from the preconfigured remote server:

```
curl -v http://<RUUVI_GW_IP>/gw_cfg_download 
    -H "Authorization: Bearer 1SDrQH1FkH+pON0GsSjt2gYeMSP02uYqfuu7LWdaBvY=" -d ''
```


# MQTT examples

Here is an example of MQTT configuration on the Ruuvi Gateway:

<figure><img src="/files/mKK1ur1YCVcZd0jkdDKE" alt=""><figcaption></figcaption></figure>

Here are sample commands to subscribe an MQTT client to notifications from the Ruuvi Gateway:

* Subscribe to service messages from this Gateway:\
  `mosquitto_sub -h test.mosquitto.org -p 1883 -t "ruuvi/AA:BB:CC:DD:EE:FF/gw_status" -v`
* Subscribe to service messages from any Gateway:\
  `mosquitto_sub -h test.mosquitto.org -p 1883 -t "ruuvi/#/gw_status" -v`
* Subscribe to messages from a Bluetooth sensor with the MAC:\
  `mosquitto_sub -h test.mosquitto.org -p 1883 -t "ruuvi/#/<TAG_MAC>/#" -v`
* Subscribe to messages from any Bluetooth sensor that this Gateway receives:\
  `mosquitto_sub -h test.mosquitto.org -p 1883 -t "ruuvi/AA:BB:CC:DD:EE:FF/#/#" -v`
* Subscribe to all messages:\
  `mosquitto_sub -h test.mosquitto.org -p 1883 -t "ruuvi/#" -v`\\


# Home Assistant

The Ruuvi Gateway integration was introduced in Home Assistant 2023.2, see <https://www.home-assistant.io/integrations/ruuvi_gateway/>

## Setup

Install [Home Assistant OS 9.5](https://github.com/home-assistant/operating-system/releases/tag/9.5) or newer.

Configure Ruuvi Gateway to use [Polling mode](/ruuvi-gateway-firmware/examples/polling-mode) (configure a Bearer token for read-only access).

Adding Ruuvi Gateway to your Home Assistant instance can easily be done by simply clicking on this [link](https://my.home-assistant.io/redirect/config_flow_start?domain=ruuvi_gateway). After that, you'll see the following confirmation:

![](/files/JzR2q3vknfIQlTbe3o3g)

After you click the **OPEN LINK** button, you may be prompted to log in:

![](/files/bdRw5GIZUJSWzeIpjO3j)

Then you will see a confirmation to set up the Ruuvi Gateway in Home Assistant:

![](/files/vPItE3sHims49IQENG2I)

After that, you will need to enter the IP address of Ruvvi Gateway and the Bearer token specified when configuring the Gateway:

![](/files/4PypGRi3Sh2djroLJZas)

That's it, then you will see a confirmation that Ruuvi Gateway has been successfully added:

![](/files/T5XwvOW3gOt7kYWoUzu4)

After that, the **Integrations** tab will open, where you can find the Ruuvi Gateway and a set of sensors:

![](/files/GRKYmBfQcjdm89C0PVX5)

You can manually open the **Integrations** tab:

* In the sidebar click on **Settings**
* From the configuration menu select: **Devices & Services**

You will then need to configure the discovered sensors, select one of them and press the **CONFIGURE** button:

![](/files/FNHYaRRd2jiLpAltg9OQ)

Then press the **SUBMIT** button:

![](/files/YZY7O6ilNes62hxm2RuX)

Configure **Area** for the sensor and press the **FINISH** button:

![](/files/OIWvTHgcohXZFeY5dBQX)

After that, the configured sensor will appear on the **Overview** page:

![](/files/xPPjLgRbZBvTxxm6RhTr)

That's all.


# MQTT+AWS IoT Core

Follow these detailed steps to establish a secure connection and send data from your Ruuvi Gateway to AWS IoT Core using MQTT. This setup uses MQTT over SSL/TLS for encrypted data transmission, ensuring secure communication.

1. **Select MQTT Transport Type**:
   * For the transport type, choose **"MQTT over SSL"**. This option encrypts the data, providing a secure connection to AWS IoT Core.
2. **Configure Server Address**:
   * Input your AWS endpoint as the **Server Address**. You can find your unique AWS endpoint in the AWS IoT Core console under the Settings section.
3. **Set Communication Port**:
   * Specify **8883** as the port. This is the standard port for MQTT over SSL, ensuring encrypted communication.
4. **Configure Client ID**:
   * Input the **Client ID** provided by AWS, which uniquely identifies your Ruuvi Gateway. You will find this ID in the AWS IoT Core console, formatted as **"iotconsole-xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"**. The Client ID ensures AWS IoT Core correctly recognizes and manages your device's connection.
5. **Important: Disable Retained Messages**:
   * Due to AWS's policy, enabling retained messages can cause AWS IoT Core to immediately close the connection upon receiving the Last Will and Testament (LWT) message. Therefore, it is crucial to **disable the use of retained messages** to maintain a stable connection. This step prevents potential disruptions and ensures continuous data transmission to AWS IoT Core.
6. **Configure SSL/TLS Certificates**:
   * Enable **"Use client SSL certificate"**.
     * Upload the **Client Certificate File** (.crt) and the **Client Private Key File** (.key) provided by AWS when you registered your device.
   * Enable **"Use custom SSL certificate for the server"**.
     * Upload the **Server SSL Certificate**. This is usually the Amazon Root CA 1 certificate available from AWS documentation.
7. **Select Data Format**:
   * Choose the preferred format for the data you are sending to AWS IoT Core. Options typically include:
     * **Raw Data**: Sends the data as collected by sensors without processing.
     * **Decoded Data**: Sends data that has been processed or formatted.
     * **Both**: Sends both raw and decoded data, providing comprehensive information.

<figure><img src="/files/BARXSFLHxy1mFUQLVSEg" alt=""><figcaption></figcaption></figure>


# GW open ports / services

Lifecycle: in production

Ruuvi Gateway uses the following network services and ports.

### DHCP

* **67/udp inbound** — DHCP **server** in **configuration hotspot mode** (unconfigured / after Configure button).
* **68/udp outbound** — DHCP **client** when Ethernet/Wi‑Fi uses DHCP.

### DNS

* **5353/udp bi‑directional** — mDNS on LAN (gateway discoverable via its \*.local name).
* **53/udp, 53/tcp outbound** — Unicast DNS to configured resolvers (required to resolve Ruuvi Cloud and any user‑configured endpoints).

### HTTP(S)

* **80/tcp inbound** — Web UI & local APIs over LAN (`/`, `/history`, `/metrics`, `/ruuvi.json`).
  * Sensitive configuration fields are encrypted during **hotspot provisioning**. Local HTTP is plaintext; place the device on a trusted LAN or use a reverse proxy/TLS terminator if HTTPS on LAN is required.
* **443/tcp outbound** — HTTPS to cloud/update services: `network.ruuvi.com`, `fwupdate.ruuvi.com`. Allowing `*.ruuvi.com` is recommended.

### MQTT(S) (optional)

* **1883/tcp outbound** — MQTT to user‑configured broker (disabled by default).
* **8883/tcp outbound** — MQTT over TLS to user‑configured broker (disabled by default).

### NTP

* **123/udp outbound** — Time sync. DHCP‑provided NTP servers are supported.
  * Common defaults: `time.ruuvi.com`, `time.google.com`, `time.cloudflare.com`, `pool.ntp.org` (may vary by firmware).


# GW Install custom firmware

If you want to install a custom firmware or a previous version of Ruuvi Gateway firmware, you can do this using Web-UI.

## GW Install previous firmware releases

Open Web-UI, go to "Software Update" page, expand "Advanced Settings" and enable the checkbox "Don't use the software update provided by Ruuvi but download binary files from URL address instead":

<figure><img src="/files/XI6Vtv3LJb7oWfXof5ET" alt=""><figcaption></figcaption></figure>

In the URL input field change the version number to the desired one, then press "UPDATE" button.

The list of released versions can be found here: <https://github.com/ruuvi/ruuvi.gateway_esp.c/releases/>

## GW Install custom firmware

You need to prepare firmware binaries and put them on your HTTP server.

1\. First, you need to build firmware:

```bash
idf.py build
```

2\. Next, copy the firmware binaries to some folder (gw\_fw):

```
mkdir gw_fw
cd build
cp ruuvi_gateway_esp.bin fatfs_gwui.bin fatfs_nrf52.bin ../gw_fw
```

3\. Run local HTTP server on port 7000 in the folder with the firmware binaries:

```
cd ../gw_fw
python3 -m http.server 7000
```

Or instead of steps 2 and 3 you can simply run the local HTTP server in the "build" folder:

```
cd build
python3 -m http.server 7000
```

Then open Web-UI, go to "Software Update" page, expand "Advanced Settings", enable checkbox "Don't use the software update provided by Ruuvi but download binary files from URL address instead" and in the URL input field set the address of your local HTTP server, then press "UPDATE" button:

<figure><img src="/files/BhvTywQWCk4wc8daab74" alt=""><figcaption></figcaption></figure>


# Bluetooth advertisements

Lifecycle: In production

**Introduction**

If you're not already familiar with Bluetooth Low-Energy (BLE) advertisements in general, please read the [Argenox primer](https://www.argenox.com/library/bluetooth-low-energy/ble-advertising-primer/).

The default way of getting data from RuuviTags is to listen to Bluetooth Low-Energy advertisement packets. The data is in manufacturer specific format.

Additionally, connectable Ruuvi devices have a scan responce which contains device name "Ruuvi XXXX" and UUID of the Nordic UART Service. However if you're looking to only read the advertised data, you can ignore the scan responses.

**Advertisement intervals**

The data is broadcast and not acknowledged by the listeners, therefore there is no guarantee that any single data packet will be heard by a nearby phone or gateway. The chance to receive the data depends on the signal strength and noise on the 2.4 GHz band, typically 25 ... 90 % of advertisements can be received in good conditions.

The delay between updates is random, a gateway may miss 10 advertisements in a row even in good conditions. This latency is dependend on the broadcast interval, faster broadcasting means more chances to catch the data. On the other hand faster broadcasting will drain the battery faster, so there is a performance / battery runtime tradeoff.

As of Ruuvi Firmware version 3.28.13 the default advertisement rate is 1285 ms + random delay of 0 ... 10 ms per advertisement. Test versions of firmware advertise at a rate of 211 ms + 0 ... 10 ms and long life versions at a rate of 8995 ms + 0 ... 10 ms. Default and test intervals match Apple guidelines for bluetooth accessories, long life version might not be discovered by Apple devices at all. Android doesn't similar guidelines.

**Advertisement data**

The advertisement begins with mandatory flags highlighted in blue. The the actual manufacturer specific data begins with a header highlighted in dark green which contains:

* Length (not counting length byte): **`0x1B`** = 27 bytes
* Type: **`0xFF`** = Manufacturer specific data
* Manufacturer ID, least significant byte first: **`0x0499`** = Ruuvi Innovations Ltd
* Payload data: **`0x050F274035C454005000C8FC20A456F030E5C9445429E38D`**

Grayed out data is the scan response. Scan response is relevent when establishing the Bluetooth Connection to Ruuvi devices, but we can ignore it here.

![Advertised data and scan response](/files/-Lt4WL9iaKZbYHhRxeVG)

Interpretation of different payloads is described in the sub-sections of this page.


# Data format 3 (RAWv1)

Lifecycle: Deprecated

Related Ruuvi Devices: RuuviTag, Ruuvi Gateway

The data is decoded from "Manufacturer Specific Data" -field, for more details please check [Bluetooth Advertisements section](https://docs.ruuvi.com/communication/bluetooth-advertisements). Manufacturer ID is **`0x0499`**, which gets sent as **`0x9904`**. The actual data payload is:

| Offset  |       Allowed values       | Description                                                                               |
| ------- | :------------------------: | ----------------------------------------------------------------------------------------- |
| 0       |              3             | Data format definition (3 = current sensor readings)                                      |
| 1       |         `0 ... 200`        | Humidity (one lsb is 0.5%, e.g. 128 is 64%) Values above 100% indicate a fault in sensor. |
| 2       |   `-127 ... 127, signed`   | Temperature (MSB is sign, next 7 bits are decimal value)                                  |
| 3       |         `0 ... 99`         | Temperature (fraction, 1/100.)                                                            |
| 4 - 5   |        `0 ... 65535`       | Pressure (Most Significant Byte first, value - 50kPa)                                     |
| 6-7     | `-32767 ... 32767, signed` | Acceleration-X (Most Significant Byte first)                                              |
| 8 - 9   | `-32767 ... 32767, signed` | Acceleration-Y (Most Significant Byte first)                                              |
| 10 - 11 | `-32767 ... 32767, signed` | Acceleration-Z (Most Significant Byte first)                                              |
| 12 - 13 |        `0 ... 65535`       | Battery voltage (millivolts). MSB First                                                   |

## Data field descriptions

### Data Format

The first byte tells the receiver (ie. website) what kind of type of data the packet has.

### Temperature

Values supported: -127.99 °C to +127.99 °C in 0.01 °C increments.

*Example*

| Value    | Measurement |
| -------- | ----------- |
| `0x0000` | 0 °C        |
| `0x8145` | -1.69 °C    |
| `0x0145` | +1.69 °C    |

### Humidity

Values supported: 0.0 % to 100 % in 0.5 % increments.

*Example*

| Value | Measurement |
| ----- | ----------- |
| `000` | 0%          |
| `128` | 64.0%       |
| `200` | 100.0%      |

### Atmospheric Pressure

Values supported: 50000 Pa to 115536 Pa in 1 Pa increments.

*Example*

| Value   | Measurement                            |
| ------- | -------------------------------------- |
| `00000` | 50000 Pa                               |
| `51325` | 101325 Pa (average sea-level pressure) |
| `65536` | 115536 Pa                              |

### Acceleration

Values supported: -32000 to 32000 (mG), however the sensor on RuuviTag supports only 16 G max (2 G in default configuration). Values are 2-complement int16\_t, MSB first. All channels are identical.

*Example*

| Value       | Measurement |
| ----------- | ----------- |
| `0xFC 0x18` | -1000 mG    |
| `0x03 0xE8` | 1000 mG     |

### Battery voltage

Values supported: 0 mV to 65536 mV in 1 mV increments, practically 1800 ... 3600 mV.

## Test vectors

These test vectors are based on [ruuvitag-sensor](https://github.com/ttu/ruuvitag-sensor/tree/master/tests) project. There is no specific value for invalid/not available sensor readings, it is suggested to send `0` if value is not available. The tests are bidirectional, decode-encode results in original raw data. Encode-decode must result in same values with given precision, but floating point rounding differences may occur.

### Case: valid data

Raw binary data: `0x03291A1ECE1EFC18F94202CA0B53`

| Field          | Value       |
| -------------- | ----------- |
| Data format    | `3`         |
| Temperature    | `26.3 C`    |
| Pressure       | `102766`    |
| Humidity       | `20.5 RH-%` |
| Acceleration X | `-1.000 G`  |
| Acceleration Y | `-1.726 G`  |
| Acceleration Z | `0.714 G`   |
| Voltage        | `2.899 V`   |

### Case: maximum values

Raw binary data: `0x03FF7F63FFFF7FFF7FFF7FFFFFFF`

| Field          | Value        |
| -------------- | ------------ |
| Data format    | `3`          |
| Temperature    | `127.99 C`   |
| Pressure       | `115535`     |
| Humidity       | `127.5 RH-%` |
| Acceleration X | `32.767 G`   |
| Acceleration Y | `32.767 G`   |
| Acceleration Z | `32.767 G`   |
| Voltage        | `65.535 V`   |

### Case: minimum values

Raw binary data: `0x0300FF6300008001800180010000`

| Field          | Value       |
| -------------- | ----------- |
| Data format    | `3`         |
| Temperature    | `-127.99 C` |
| Pressure       | `50000`     |
| Humidity       | `0.0 RH-%`  |
| Acceleration X | `-32.767 G` |
| Acceleration Y | `-32.767 G` |
| Acceleration Z | `-32.767 G` |
| Voltage        | `0.000 V`   |


# Data format 4 (URL)

Lifecycle: Obsolete

Related Ruuvi Devices: RuuviTag, up to B3 hardware (superceded in 2018)

The data is packed in [Eddystone URL](https://developers.google.com/beacons/eddystone) with a base of [https://ruu.vi/#](https://ruu.vi) and 9 [URL-safe base64](https://en.wikipedia.org/wiki/Base64#URL_applications) characters. Example URL is <https://ruu.vi/#BFwaAMjlQ>. This is decoded to `0x045c1a00c8e5`

| Offset |     Allowed values     | Description                                                                                                                     |
| ------ | :--------------------: | ------------------------------------------------------------------------------------------------------------------------------- |
| 0      |            4           | Data format definition (4 = current sensor readings)                                                                            |
| 1      |       `0 ... 200`      | Humidity (one lsb is 0.5%, e.g. 128 is 64%)                                                                                     |
| 2      | `-127 ... 127, signed` | Temperature (MSB is sign, next 7 bits are decimal value)                                                                        |
| 3      |        `0 ... 0`       | Temperature (fraction, 1/100.). Not used, reads always as 0.                                                                    |
| 4 - 5  |      `0 ... 65535`     | Pressure (Most Significant Byte first, value - 50kPa). Rounded to 1 hPa accuracy.                                               |
| 6      |        `0..255`        | Random id of tag, only present in format 4. NOTE! because of the URL limitation, only 6 most significant bits will be readable. |

## Data field descriptions

## Temperature

Values supported: -127.99 °C to +127.99 °C in 0.01 °C increments.

*Example*

| Value    | Measurement |
| -------- | ----------- |
| `0x0000` | 0 °C        |
| `0x8145` | -1.69 °C    |
| `0x0145` | +1.69 °C    |

## Humidity

Values supported: 0.0 % to 100 % in 0.5 % increments.

*Example*

| Value | Measurement |
| ----- | ----------- |
| `000` | 0%          |
| `128` | 64.0%       |
| `200` | 100.0%      |

## Atmospheric Pressure

Values supported: 50000 Pa to 115536 Pa in 1 Pa increments.

*Example*

| Value   | Measurement                            |
| ------- | -------------------------------------- |
| `00000` | 50000 Pa                               |
| `51325` | 101325 Pa (average sea-level pressure) |
| `65536` | 115536 Pa                              |

## Tag ID (only on format 4)

Contains a single random base 64 character used to identify tag.


# Data format 5 (RAWv2)

Lifecycle: In Production

Related Ruuvi Devices: RuuviTag, Ruuvi Gateway

The data is decoded from "Manufacturer Specific Data" -field, for more details please check [Bluetooth Advertisements section](https://docs.ruuvi.com/communication/bluetooth-advertisements). Manufacturer ID is **`0x0499`** , which is transmitted as **`0x9904`** in raw data. The actual data payload is:

| Offset |      Allowed values      | Description                                                                                                                                                                                                                                                                                        |
| ------ | :----------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0      |            `5`           | Data format (8bit)                                                                                                                                                                                                                                                                                 |
| 1-2    |    `-32767 ... 32767`    | Temperature in 0.005 degrees                                                                                                                                                                                                                                                                       |
| 3-4    |      `0 ... 40 000`      | Humidity (16bit unsigned) in 0.0025% (0-163.83% range, though realistically 0-100%)                                                                                                                                                                                                                |
| 5-6    |       `0 ... 65534`      | Pressure (16bit unsigned) in 1 Pa units, with offset of -50 000 Pa                                                                                                                                                                                                                                 |
| 7-8    |    `-32767 ... 32767`    | Acceleration-X (Most Significant Byte first)                                                                                                                                                                                                                                                       |
| 9-10   |    `-32767 ... 32767`    | Acceleration-Y (Most Significant Byte first)                                                                                                                                                                                                                                                       |
| 11-12  |    `-32767 ... 32767`    | Acceleration-Z (Most Significant Byte first)                                                                                                                                                                                                                                                       |
| 13-14  | `0 ... 2046`, `0 ... 30` | Power info (11+5bit unsigned), first 11 bits is the battery voltage above 1.6V, in millivolts (1.6V to 3.646V range). Last 5 bits unsigned are the TX power above -40dBm, in 2dBm steps. (-40dBm to +20dBm range)                                                                                  |
| 15     |        `0 ... 254`       | Movement counter (8 bit unsigned), incremented by motion detection interrupts from accelerometer                                                                                                                                                                                                   |
| 16-17  |       `0 ... 65534`      | Measurement sequence number (16 bit unsigned), each time a measurement is taken, this is incremented by one, used for measurement de-duplication. Depending on the transmit interval, multiple packets with the same measurements can be sent, and there may be measurements that never were sent. |
| 18-23  |      `Any valid mac`     | 48bit MAC address.                                                                                                                                                                                                                                                                                 |

*Not available* is signified by largest presentable number for unsigned values, smallest presentable number for signed values and all bits set for mac. All fields are MSB first 2-complement, i.e. `0xFC18` is read as `-1000` and `0x03E8` is read as `1000`. If original data overflows the data format, data is clipped to closests value that can be represented. For example temperature 170.00 C becomes 163.835 C and acceleration -40.000 G becomes -32.767 G.

### Data field descriptions

#### Temperature

Values supported: (-163.835 °C to +163.835 °C in 0.005 °C increments.

*Example*

| Value    | Measurement             |
| -------- | ----------------------- |
| `0x0000` | 0 °C                    |
| `0x01C3` | +2.255 °C               |
| `0xFE3D` | -2.255 °C               |
| `0x8000` | Invalid / not available |

#### **Humidity**

Values supported: 0.0 % to 100 % in 0.0025 % increments. Higher values than 100 % are possible, but they generally indicate a faulty or miscalibrated sensor.

*Example*

| Value   | Measurement             |
| ------- | ----------------------- |
| `000`   | 0%                      |
| `10010` | 25.025%                 |
| `40000` | 100.0%                  |
| `65535` | Invalid / not available |

#### **Atmospheric Pressure**

Values supported: 50000 Pa to 115536 Pa in 1 Pa increments.

*Example*

| Value   | Measurement                            |
| ------- | -------------------------------------- |
| `00000` | 50000 Pa                               |
| `51325` | 101325 Pa (average sea-level pressure) |
| `65534` | 115534 Pa                              |
| `65535` | Invalid / not available                |

#### **Acceleration**

Values supported: -32767 to 32767 (mG), however the sensor on RuuviTag supports only 16 G max (2 G in default configuration). Values are 2-complement int16\_t, MSB first. All channels are identical.

*Example*

| Value    | Measurement             |
| -------- | ----------------------- |
| `0xFC18` | -1000 mG                |
| `0x03E8` | 1000 mG                 |
| `0x8000` | Invalid / not available |

#### **Battery voltage**

Values supported: 1600 mV to 3647 mV in 1 mV increments, practically 1800 ... 3600 mV.

*Example*

| Value  | Measurement             |
| ------ | ----------------------- |
| `0000` | 1600 mV                 |
| `1400` | 3000 mV                 |
| `2047` | Invalid / not available |

#### **Tx Power**

Values supported: -40 dBm to +22 dBm in 2 dBm increments.

*Example*

| Value | Measurement             |
| ----- | ----------------------- |
| `00`  | -40 dBm                 |
| `22`  | +4 dBm                  |
| `31`  | Invalid / not available |

#### **Movement counter**

Movement counter is one-byte counter which gets triggered when LIS2DH12 gives "activity interrupt". Sensitivity depends on the firmware, by default the sensitivity is a movement over 64 mG. The counter will roll over. Movement is deduced by "rate of change". Please note that the highest valid value is 254, and 255 is reserved for the "not available".

*Example*

| Value | Measurement             |
| ----- | ----------------------- |
| `00`  | 0 counts                |
| `100` | 100 counts              |
| `255` | Invalid / not available |

#### **Measurement sequence number**

Mesurement sequence number gets incremented by one for every measurement. It can be used to gauge signal quality and packet loss as well as to deduplicated data entries. You should note that the measurement sequence refers to data rather than transmission, so you might receive many transmissions with the same measurement sequence number. Please note that the highest valid value is 65534, and 65535 is reserved for the "not available".

*Example*

| Value   | Measurement             |
| ------- | ----------------------- |
| `00`    | 0 counts                |
| `1000`  | 1000 counts             |
| `65535` | Invalid / not available |

### Test vectors

These test vectors are based on [ruuvitag-sensor](https://github.com/ttu/ruuvitag-sensor/tree/master/tests) project. The tests are bidirectional, decode-encode results in original raw data. Encode-decode must result in same values with given precision, but floating point rounding differences may occur.

#### Case: valid data

Raw binary data: `0x0512FC5394C37C0004FFFC040CAC364200CDCBB8334C884F`

| Field                | Value               |
| -------------------- | ------------------- |
| Data format          | `5`                 |
| Temperature          | `24.3 C`            |
| Pressure             | `100044`            |
| Humidity             | `53.49 RH-%`        |
| Acceleration X       | `0.004 G`           |
| Acceleration Y       | `-0.004 G`          |
| Acceleration Z       | `1.036 G`           |
| TX Power             | `4 dBm`             |
| Voltage              | `2.977 V`           |
| Movement counter     | `66`                |
| Measurement Sequence | `205`               |
| MAC                  | `CB B8 33 4C 88 4F` |

#### Case: maximum values

Raw binary data: `0x057FFFFFFEFFFE7FFF7FFF7FFFFFDEFEFFFECBB8334C884F`

| Field                | Value               |
| -------------------- | ------------------- |
| Data format          | `5`                 |
| Temperature          | `163.835 C`         |
| Pressure             | `115534`            |
| Humidity             | `163.8350 RH-%`     |
| Acceleration X       | `32.767 G`          |
| Acceleration Y       | `32.767 G`          |
| Acceleration Z       | `32.767 G`          |
| TX Power             | `20 dBm`            |
| Voltage              | `3.646 V`           |
| Movement counter     | `254`               |
| Measurement Sequence | `65534`             |
| MAC                  | `CB B8 33 4C 88 4F` |

#### Case: minimum values

Raw binary data: `0x058001000000008001800180010000000000CBB8334C884F`

| Field                | Value               |
| -------------------- | ------------------- |
| Data format          | `5`                 |
| Temperature          | `-163.835 C`        |
| Pressure             | `50000`             |
| Humidity             | `0.000 RH-%`        |
| Acceleration X       | `-32.767 G`         |
| Acceleration Y       | `-32.767 G`         |
| Acceleration Z       | `-32.767 G`         |
| TX Power             | `-40 dBm`           |
| Voltage              | `1.600 V`           |
| Movement counter     | `0`                 |
| Measurement Sequence | `0`                 |
| MAC                  | `CB B8 33 4C 88 4F` |

#### Case: Invalid values

Raw binary data: `0x058000FFFFFFFF800080008000FFFFFFFFFFFFFFFFFFFFFF`

| Field                | Value |
| -------------------- | ----- |
| Data format          | `5`   |
| Temperature          | `NAN` |
| Pressure             | `NAN` |
| Humidity             | `NAN` |
| Acceleration X       | `NAN` |
| Acceleration Y       | `NAN` |
| Acceleration Z       | `NAN` |
| TX Power             | `NAN` |
| Voltage              | `NAN` |
| Movement counter     | `NAN` |
| Measurement Sequence | `NAN` |
| MAC                  | `NAN` |


# Data format C5 (Cut-RAWv2)

Lifecycle: Beta. Last updated 2024-01-30

Related Ruuvi Devices: RuuviTag, Ruuvi Gateway

The data is decoded from "Manufacturer Specific Data" -field, for more details please check [Bluetooth Advertisements section](https://docs.ruuvi.com/communication/bluetooth-advertisements). Manufacturer ID is **`0x0499`** , which is transmitted as **`0x9904`** in raw data.

The format resembles original RAWv2, but acceleration is left out to leave 6 bytes for other information in primary advertisement.

The actual data payload is:

| Offset |      Allowed values      | Description                                                                                                                                                                                                                                                                                        |
| ------ | :----------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0      |          `0xC5`          | Data format (8bit)                                                                                                                                                                                                                                                                                 |
| 1-2    |    `-32767 ... 32767`    | Temperature in 0.005 degrees                                                                                                                                                                                                                                                                       |
| 3-4    |      `0 ... 40 000`      | Humidity (16bit unsigned) in 0.0025% (0-163.83% range, though realistically 0-100%)                                                                                                                                                                                                                |
| 5-6    |       `0 ... 65534`      | Pressure (16bit unsigned) in 1 Pa units, with offset of -50 000 Pa                                                                                                                                                                                                                                 |
| 7-8    | `0 ... 2046`, `0 ... 30` | Power info (11+5bit unsigned), first 11 bits is the battery voltage above 1.6V, in millivolts (1.6V to 3.646V range). Last 5 bits unsigned are the TX power above -40dBm, in 2dBm steps. (-40dBm to +20dBm range)                                                                                  |
| 9      |        `0 ... 254`       | Movement counter (8 bit unsigned), incremented by motion detection interrupts from accelerometer                                                                                                                                                                                                   |
| 10-11  |       `0 ... 65534`      | Measurement sequence number (16 bit unsigned), each time a measurement is taken, this is incremented by one, used for measurement de-duplication. Depending on the transmit interval, multiple packets with the same measurements can be sent, and there may be measurements that never were sent. |
| 12-17  |      `Any valid mac`     | 48bit MAC address.                                                                                                                                                                                                                                                                                 |

*Not available* is signified by largest presentable number for unsigned values, smallest presentable number for signed values and all bits set for mac. All fields are MSB first. All signed values are 2-complement, i.e. `0xFC18` is read as `-1000` and `0x03E8` is read as `1000`. If original data overflows the data format, data is clipped to closests value that can be represented. For example temperature 170.00 C becomes 163.835 C and acceleration -40.000 G becomes -32.767 G.

### Data field descriptions

#### Temperature

Values supported: (-163.835 °C to +163.835 °C in 0.005 °C increments.

*Example*

| Value    | Measurement             |
| -------- | ----------------------- |
| `0x0000` | 0 °C                    |
| `0x01C3` | +2.255 °C               |
| `0xFE3D` | -2.255 °C               |
| `0x8000` | Invalid / not available |

#### **Humidity**

Values supported: 0.0 % to 100 % in 0.0025 % increments. Higher values than 100 % are possible, but they generally indicate a faulty or miscalibrated sensor.

*Example*

| Value   | Measurement             |
| ------- | ----------------------- |
| `000`   | 0%                      |
| `10010` | 25.025%                 |
| `40000` | 100.0%                  |
| `65535` | Invalid / not available |

#### **Atmospheric Pressure**

Values supported: 50000 Pa to 115536 Pa in 1 Pa increments.

*Example*

| Value   | Measurement                            |
| ------- | -------------------------------------- |
| `00000` | 50000 Pa                               |
| `51325` | 101325 Pa (average sea-level pressure) |
| `65534` | 115534 Pa                              |
| `65535` | Invalid / not available                |

#### **Battery voltage**

Values supported: 1600 mV to 3647 mV in 1 mV increments, practically 1800 ... 3600 mV.

*Example*

| Value  | Measurement             |
| ------ | ----------------------- |
| `0000` | 1600 mV                 |
| `1400` | 3000 mV                 |
| `2047` | Invalid / not available |

#### **Tx Power**

Values supported: -40 dBm to +22 dBm in 2 dBm increments.

*Example*

| Value | Measurement             |
| ----- | ----------------------- |
| `00`  | -40 dBm                 |
| `22`  | +4 dBm                  |
| `31`  | Invalid / not available |

#### **Movement counter**

Movement counter is one-byte counter which gets triggered when LIS2DH12 gives "activity interrupt". Sensitivity depends on the firmware, by default the sensitivity is a movement over 64 mG. The counter will roll over. Movement is deduced by "rate of change". Please note that the highest valid value is 254, and 255 is reserved for the "not available".

*Example*

| Value | Measurement             |
| ----- | ----------------------- |
| `00`  | 0 counts                |
| `100` | 100 counts              |
| `255` | Invalid / not available |

#### **Measurement sequence number**

Mesurement sequence number gets incremented by one for every measurement. It can be used to gauge signal quality and packet loss as well as to deduplicated data entries. You should note that the measurement sequence refers to data rather than transmission, so you might receive many transmissions with the same measurement sequence number. Please note that the highest valid value is 65534, and 65535 is reserved for the "not available".

*Example*

| Value   | Measurement             |
| ------- | ----------------------- |
| `00`    | 0 counts                |
| `1000`  | 1000 counts             |
| `65535` | Invalid / not available |

### Test vectors

These test vectors are based on [ruuvitag-sensor](https://github.com/ttu/ruuvitag-sensor/tree/master/tests) project. The tests are bidirectional, decode-encode results in original raw data. Encode-decode must result in same values with given precision, but floating point rounding differences may occur.

#### Case: valid data

Raw binary data: `0x0512FC5394C37CAC364200CDCBB8334C884F`

| Field                | Value               |
| -------------------- | ------------------- |
| Data format          | `5`                 |
| Temperature          | `24.3 C`            |
| Pressure             | `100044`            |
| Humidity             | `53.49 RH-%`        |
| TX Power             | `4 dBm`             |
| Voltage              | `2.977 V`           |
| Movement counter     | `66`                |
| Measurement Sequence | `205`               |
| MAC                  | `CB B8 33 4C 88 4F` |

#### Case: maximum values

Raw binary data: `0x057FFFFFFEFFFEFFDEFEFFFECBB8334C884F`

| Field                | Value               |
| -------------------- | ------------------- |
| Data format          | `5`                 |
| Temperature          | `163.835 C`         |
| Pressure             | `115534`            |
| Humidity             | `163.8350 RH-%`     |
| TX Power             | `20 dBm`            |
| Voltage              | `3.646 V`           |
| Movement counter     | `254`               |
| Measurement Sequence | `65534`             |
| MAC                  | `CB B8 33 4C 88 4F` |

#### Case: minimum values

Raw binary data: `0x058001000000000000000000CBB8334C884F`

| Field                | Value               |
| -------------------- | ------------------- |
| Data format          | `5`                 |
| Temperature          | `-163.835 C`        |
| Pressure             | `50000`             |
| Humidity             | `0.000 RH-%`        |
| TX Power             | `-40 dBm`           |
| Voltage              | `1.600 V`           |
| Movement counter     | `0`                 |
| Measurement Sequence | `0`                 |
| MAC                  | `CB B8 33 4C 88 4F` |

#### Case: Invalid values

Raw binary data: `0x058000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF`

| Field                | Value |
| -------------------- | ----- |
| Data format          | `5`   |
| Temperature          | `NAN` |
| Pressure             | `NAN` |
| Humidity             | `NAN` |
| TX Power             | `NAN` |
| Voltage              | `NAN` |
| Movement counter     | `NAN` |
| Measurement Sequence | `NAN` |
| MAC                  | `NAN` |


# Data format 6

Lifecycle: In Production

Related Ruuvi Devices: Ruuvi Air, Ruuvi Gateway

This data format uses Bluetooth 4 advertisement extension to be compatible with Bluetooth 4 devices. Any Bluetooth 5.0 and upwards capable device should discard this data format and listen to data format E1 packets instead.

The data is decoded from "Manufacturer Specific Data" -field, for more details please check [Bluetooth Advertisements section](https://docs.ruuvi.com/communication/bluetooth-advertisements). Manufacturer ID is **`0x0499`** , which is transmitted as **`0x9904`** in raw data. The actual data payload is:

| Offset        |   Allowed values   | Description                                                                                                                  |
| ------------- | :----------------: | ---------------------------------------------------------------------------------------------------------------------------- |
| 0             |         `6`        | Data format (8bit)                                                                                                           |
| 1-2           | `-32767 ... 32767` | Temperature in 0.005 degrees                                                                                                 |
| 3-4           |   `0 ... 40 000`   | Humidity (16bit unsigned) in 0.0025% (0-163.83% range, though realistically 0-100%)                                          |
| 5-6           |    `0 ... 65534`   | Pressure (16bit unsigned) in 1 Pa units, with offset of -50 000 Pa                                                           |
| 7-8           |    `0 ... 10000`   | PM 2.5, ug/m^3. Resolution 0.1/bit, range 0 ... 1000. 16bit unsigned                                                         |
| 9-10          |    `0 ... 40000`   | CO2 concentration, ppm. Resolution 1/bit, range 0 ... 40000. 16bit unsigned                                                  |
| 11 +FLAGS b6  |     `0 ... 500`    | VOC index, unitless. Resolution 1 / bit, range 0 ... 500. 9 bit unsigned, least significant bit in Flags byte                |
| 12, +FLAGS b7 |     `0 ... 500`    | NOX index, unitless. Resolution 1 / bit, range 0 ... 500. 9 bit unsigned, least significant bit in Flags byte                |
| 13            |     `0 ... 254`    | Luminosity, Lux. Logarithmic, range 0 ... 65535. See below for details                                                       |
| 14            |        `255`       | Reserved                                                                                                                     |
| 15            |      `0...255`     | Measurement sequence. A device sending also in E1 data format will have identical least significant byte for the same sample |
| 16            |    `0bVVXX XXXV`   | Flags. See below for details                                                                                                 |
| 17-19         |   `Any valid mac`  | Lowest 3 bytes of device MAC address                                                                                         |

*Not available* is signified by largest presentable number for unsigned values, smallest presentable number for signed values and all bits set for mac. All fields are MSB first. All signed values are 2-complement, i.e. `0xFC18` is read as `-1000` and `0x03E8` is read as `1000`. If original data overflows the data format, data is clipped to closest value that can be represented. For example temperature 170.00 C becomes 163.835 C

### Data field descriptions

#### Temperature

Values supported: -163.835 °C to +163.835 °C in 0.005 °C increments.

*Example*

| Value    | Measurement             |
| -------- | ----------------------- |
| `0x0000` | 0 °C                    |
| `0x01C3` | +2.255 °C               |
| `0xFE3D` | -2.255 °C               |
| `0x8000` | Invalid / not available |

#### **Humidity**

Values supported: 0.0 % to 100 % in 0.0025 % increments. Higher values than 100 % are possible, but they generally indicate a faulty or miscalibrated sensor.

*Example*

| Value   | Measurement             |
| ------- | ----------------------- |
| `0`     | 0%                      |
| `10010` | 25.025%                 |
| `40000` | 100.0%                  |
| `65535` | Invalid / not available |

#### **Atmospheric Pressure**

Values supported: 50000 Pa to 115534 Pa in 1 Pa increments.

*Example*

| Value   | Measurement                            |
| ------- | -------------------------------------- |
| `00000` | 50000 Pa                               |
| `51325` | 101325 Pa (average sea-level pressure) |
| `65534` | 115534 Pa                              |
| `65535` | Invalid / not available                |

#### **PM 2.5**

Values supported: 0 to 6553.4 (ug/m^3), however the sensor on Ruuvi supports only 1000 ug/m^3. Value is uint16\_t, MSB first. Resolution is 0.1 per bit

*Example*

| Value    | Measurement             |
| -------- | ----------------------- |
| `0x0000` | 0 ug/m^3                |
| `0x03E8` | 100.0 ug/m^3            |
| `0xFFFF` | Invalid / not available |

#### CO2

Values supported: 0 to 65534 (ppm), however the sensor on Ruuvi supports only 40000 ppm and in natural environment CO2 is always at least around 400 ppm. Resolution is 1 per bit. Values are uint16\_t, MSB first.

| Value    | Measurement             |
| -------- | ----------------------- |
| `0x0000` | 0 ppm                   |
| `0x03E8` | 1000 ppm                |
| `0xFFFF` | Invalid / not available |

#### VOC, NOX

Volatile Organic Compounds and Nitrogen Oxides are unitless indexes which learn the installation environment and track changes over time. For VOC the index average is 100, i.e. values under 100 mean the air quality is improving and values over 100 mean the air quality is getting worse.

Nox index has base value of 1, values higher than 1 meaning there's more nitrogen oxides in the air than usual.

Both values use 9 bits, least significant bit is in Flags byte.

| Value   | Measurement             |
| ------- | ----------------------- |
| `0x000` | 0                       |
| `0x0E8` | 232                     |
| `0x1FF` | Invalid / not available |

#### **Luminosity**

Luminosity represents the light level in the environment of the sensor. The light level is compensated with human eye sensitivity curve. Value is 8-bit unsigned logarithmic number. Values should be rounded at most to 0.01 precision.

Please note that the highest valid code is 254, and 255 is reserved for the "not available".

Decoding and encoding:

```
MAX_VALUE := 65535
MAX_CODE  := 254
DELTA     := ln(MAX_VALUE + 1) / MAX_CODE
CODE      := round(ln(value + 1) / DELTA)
VALUE     := exp(CODE * delta) -1
```

*Example*

| Value  | Measurement             |
| ------ | ----------------------- |
| `0x00` | 0 lux                   |
| `0x01` | `0.04` lux              |
| `0x10` | `1.01` lux              |
| `0x80` | `244.06` lux            |
| `0xFE` | `65535.00` lux          |
| `0xFF` | Invalid / not available |

#### **Measurement sequence number**

Measurement sequence number gets incremented by one for every measurement. It can be used to gauge signal quality and packet loss as well as to deduplicated data entries. You should note that the measurement sequence refers to data rather than transmission, so you might receive many transmissions with the same measurement sequence number.

Please note that the highest valid value is 255, there is no "invalid / not available" value as this counter tracks the E1 format counter.

*Example*

| Value  | Measurement |
| ------ | ----------- |
| `0x00` | 0 counts    |
| `0x10` | 10 counts   |
| `0xFF` | 255 counts  |

#### **Flags**

Flags byte contains additional information and is interpreted bit-by-bit. "X" Means "don't care, 1 or 0". "V" Means the value of bit. Bits 1...5 are reserved. Bit 0 is least significant, bit 7 is most significant.

| Value         | Significance                                                                                |
| ------------- | ------------------------------------------------------------------------------------------- |
| `0bXXXX XXXV` | 1 -> Calibration in progress, sensor data not fully accurate yet. 0 -> Calibration complete |
| `0bXVXX XXXX` | VOC bit 9, 1-> set, 0 -> not set                                                            |
| `0bVXXX XXXX` | NOX bit 9, 1-> set, 0 -> not set                                                            |

#### **MAC address**

MAC address is static, statistically unique random identifier of the device. The address is 48 bits, but this data format carries only 24 least significant bits.

Please note that the Bluetooth advertisement itself carries the full MAC address of the sensor, and most host devices can and should parse the MAC address directly from the Bluetooth metadata. However, iOS devices do not reveal the full MAC address to the application, but instead convert the MAC address to an UUID which is not guaranteed to stay static. Hence iOS devices should use these 24 bits as a static identifier of the device.

### Test vectors

These test vectors are based on [ruuvi.endpoints.c](https://github.com/ruuvi/ruuvi.endpoints.c) project. The tests are bidirectional, decode-encode results in original raw data. Encode-decode must result in same values with given precision, but floating point rounding differences may occur.

#### Case: valid data

Raw binary data: `0x06170C5668C79E007000C90501D9XXCD004C884F` XX : Reserved

| Field                | Value                                                                                                                |
| -------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Data format          | `06`                                                                                                                 |
| Temperature          | `29.500` C                                                                                                           |
| Pressure             | `101102` Pa                                                                                                          |
| Humidity             | `55.300` RH-%                                                                                                        |
| PM 2.5               | `11.2` ug/m^3                                                                                                        |
| CO2                  | `201` ppm                                                                                                            |
| VOC                  | `10`                                                                                                                 |
| NOX                  | 2                                                                                                                    |
| Luminosity           | `13 026.67` Lux                                                                                                      |
| Measurement Sequence | `205`                                                                                                                |
| Flags                | <p>Calibration in progress: <code>False</code></p><p>VOC b9: <code>False</code></p><p>NOX b9: <code>False</code></p> |
| MAC                  | `4C 88 4F`                                                                                                           |

#### Case: maximum values

Raw binary data: `0x067FFF9C40FFFE27109C40FAFAFEXXFF074C8F4F` XX : Reserved

| Field                | Value                                                                                                               |
| -------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Data format          | `06`                                                                                                                |
| Temperature          | `163.835` C                                                                                                         |
| Pressure             | `115534` Pa                                                                                                         |
| Humidity             | `100.000` RH-%                                                                                                      |
| PM 2.5               | `1000.0` ug/m^3                                                                                                     |
| CO2                  | `40000` ppm                                                                                                         |
| VOC                  | `500`                                                                                                               |
| NOX                  | `500`                                                                                                               |
| Luminosity           | `65535.00` Lux                                                                                                      |
| Measurement Sequence | `255`                                                                                                               |
| Flags                | <p>Calibration in progress: <code>True</code></p><p>VOC b9: <code>False</code></p><p>NOX b9: <code>False</code></p> |
| MAC                  | `4C 88 4F`                                                                                                          |

Case: minimum values

Raw binary data: `0x0680010000000000000000000000XX00004C884F` XX : Reserved

| Field                | Value                                                                                                                |
| -------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Data format          | `06`                                                                                                                 |
| Temperature          | `-163.835` C                                                                                                         |
| Pressure             | `50000` Pa                                                                                                           |
| Humidity             | `000.000` RH-%                                                                                                       |
| PM 2.5               | `0000.0` ug/m^3                                                                                                      |
| CO2                  | `00000` ppm                                                                                                          |
| VOC                  | `000`                                                                                                                |
| NOX                  | `000`                                                                                                                |
| Luminosity           | `00000.00` Lux                                                                                                       |
| Measurement Sequence | `000`                                                                                                                |
| Flags                | <p>Calibration in progress: <code>false</code></p><p>VOC b9: <code>false</code></p><p>NOX b9: <code>false</code></p> |
| MAC                  | `4C 88 4F`                                                                                                           |

#### Case: Invalid values

Raw binary data: `0x068000FFFFFFFFFFFFFFFFFFFFFFXXFFFFFFFFFF` XX : Reserved

| Field                | Value                                                                                                             |
| -------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Data format          | `06`                                                                                                              |
| Temperature          | `NaN` C                                                                                                           |
| Pressure             | `NaN` Pa                                                                                                          |
| Humidity             | `NaN` RH-%                                                                                                        |
| PM 2.5               | `NaN` ug/m^3                                                                                                      |
| CO2                  | `NaN` ppm                                                                                                         |
| VOC                  | `NaN`                                                                                                             |
| NOX                  | `NaN`                                                                                                             |
| Luminosity           | `NaN` Lux                                                                                                         |
| Measurement Sequence | `255`                                                                                                             |
| Flags                | <p>Calibration in progress: <code>true</code></p><p>VOC b9: <code>true</code></p><p>NOX b9: <code>true</code></p> |
| MAC                  | `FF FF FF`                                                                                                        |


# Data format 8 (Encrypted environmental)

Lifecycle: Proposal

Related Ruuvi Devices: RuuviTag

This is a proposed encrypted data format which is not yet implemented in Ruuvi devices outside of a few proof-of-concept projects

The encryption uses nRF52-builtin AES128 encryption in Elctronic Codebook (ECB) mode. Data to be encrypted is temprature, humidity, pressure, voltage, TX power, measurement count and movement counts. The measurement sequence counter protects against replay attacks.

Data format has an unencrypted header, 16 bytes of AES-128 encrypted data, 1 byte crc8 and 6 bytes long MAC address for iOS devices.

| Offset |      Allowed values      | Description                                                                                                                                                                                                                                                                                        |
| ------ | :----------------------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0      |            `8`           | Data format.                                                                                                                                                                                                                                                                                       |
| 1-2    |    `-32767 ... 32767`    | Temperature in 0.005 degrees.                                                                                                                                                                                                                                                                      |
| 3-4    |      `0 ... 40 000`      | Humidity (16bit unsigned) in 0.0025% (0-163.83% range, though realistically 0-100%).                                                                                                                                                                                                               |
| 5-6    |       `0 ... 65534`      | Pressure (16bit unsigned) in 1 Pa units, with offset of -50 000 Pa.                                                                                                                                                                                                                                |
| 7-8    | `0 ... 2046`, `0 ... 30` | Power info (11+5bit unsigned), first 11 bits is the battery voltage above 1.6V, in millivolts (1.6V to 3.646V range). Last 5 bits unsigned are the TX power above -40dBm, in 2dBm steps. (-40dBm to +20dBm range).                                                                                 |
| 9-10   |       `0 ... 65534`      | Movement counter (16 bit unsigned), incremented by motion detection interrupts from accelerometer                                                                                                                                                                                                  |
| 11-12  |       `0 ... 65534`      | Measurement sequence number (16 bit unsigned), each time a measurement is taken, this is incremented by one, used for measurement de-duplication. Depending on the transmit interval, multiple packets with the same measurements can be sent, and there may be measurements that never were sent. |
| 13-16  |           `Any`          | Reserved for future use.                                                                                                                                                                                                                                                                           |
| 17     |        `0 ... 255`       | CRC8, used to check for correct decryption.                                                                                                                                                                                                                                                        |
| 18-23  |      `Any valid MAC`     | 48bit MAC address.                                                                                                                                                                                                                                                                                 |

The encryption key is formed according to the application, usually a static key shared in application + unique key derived from 8-byte tag ID.

## Invalid values

If a value cannot be determined for any reason, a special invalid value is sent. For unsigned values the invalid value is largest presentable number, for example `0xFFFF` and for signed values the invalud value is smallest presentable number, for example `0x8000`. Invalid values should be treated as NULL, NAN, NONE or similar by the parser.

## Example

| Data        | Value                                                                                                    |
| ----------- | -------------------------------------------------------------------------------------------------------- |
| Temperature | 24.58 C                                                                                                  |
| Humidity    | 40.54 RH-%                                                                                               |
| Pressure    | 100453 Pa                                                                                                |
| Battery     | 2.765 V                                                                                                  |
| TX Power    | +4 dBm                                                                                                   |
| Movement    | 15                                                                                                       |
| Measurement | 6353                                                                                                     |
| Reserved    | 0                                                                                                        |
| Checksum    | [174](https://crccalc.com/?crc=13343F58C51548E6000F18D100000000\&method=crc8\&datatype=hex\&outtype=hex) |
| MAC         | 0xAABBCCDDEEFF                                                                                           |

| Keys     | Binary                                                  |
| -------- | ------------------------------------------------------- |
| ID       | 0x0011223344556677                                      |
| Password | 0x5275757669636f6d5275757669546167 *"RuuvicomRuuviTag"* |

Unencrypted binary:

| DF | T    | H    | P    | B+TX | C    | M    | R        | CH | MAC          |
| -- | ---- | ---- | ---- | ---- | ---- | ---- | -------- | -- | ------------ |
| 08 | 1334 | 3F58 | C515 | 48E6 | 000F | 18D1 | 000000E0 | 1C | AABBCCDDEEDD |

Encryption key:

| Component | Binary                                          |
| --------- | ----------------------------------------------- |
| ID        | 00 11 22 33 44 55 66 77                         |
| password  | 52 75 75 76 69 63 6f 6d 52 75 75 76 69 54 61 67 |
| Result    | 52 64 57 45 2d 36 09 1a 52 75 75 76 69 54 61 67 |

Encrypted data: [`0x43825B56324FE019C4BD4D6D3CECAC6E`](http://extranet.cryptomathic.com/aescalc/index?key=526457452D36091A5275757669546167\&iv=00000000000000000000000000000000\&input=13343F58C51548E6000F18D100000000\&mode=ecb\&action=Encrypt\&output=9E49ED7745032DF5D2CC6E2A3047207B)

Complete message:

| DF | T    | H    | P    | B+TX | C    | M    | R        | CH | MAC          |
| -- | ---- | ---- | ---- | ---- | ---- | ---- | -------- | -- | ------------ |
| 08 | 4382 | 5B56 | 324F | E019 | C4BD | 4D6D | 3CECAC6E | 1C | AABBCCDDEEDD |

## Implementing the data format

Contact us at <support@ruuvi.com> if you want to implement this data format for your use case.


# Data format E1 (Extended v1)

Lifecycle: In Production

Related Ruuvi Devices: Ruuvi Air, Ruuvi Gateway

This data format uses Bluetooth 5 advertisement extension to provide more data than Bluetooth 4 advertisements can. Any Bluetooth 5.0 and upwards capable device should be able to receive this data format. It extends on data format 6; if the same device receives both in data format 6 and E1, the format 6 packet should be discarded.

The data is decoded from "Manufacturer Specific Data" -field, for more details please check [Bluetooth Advertisements section](https://docs.ruuvi.com/communication/bluetooth-advertisements). Manufacturer ID is **`0x0499`** , which is transmitted as **`0x9904`** in raw data. The actual data payload is:

| Offset        |   Allowed values   | Description                                                                                                   |
| ------------- | :----------------: | ------------------------------------------------------------------------------------------------------------- |
| 0             |        `E1`        | Data format (8bit)                                                                                            |
| 1-2           | `-32767 ... 32767` | Temperature in 0.005 degrees                                                                                  |
| 3-4           |   `0 ... 40 000`   | Humidity (16bit unsigned) in 0.0025% (0-163.83% range, though realistically 0-100%)                           |
| 5-6           |    `0 ... 65534`   | Pressure (16bit unsigned) in 1 Pa units, with offset of -50 000 Pa                                            |
| 7-8           |    `0 ... 10000`   | PM 1.0, ug/m^3. Resolution 0.1/bit, range 0 ... 1000. 16bit unsigned                                          |
| 9-10          |    `0 ... 10000`   | PM 2.5, ug/m^3. Resolution 0.1/bit, range 0 ... 1000. 16bit unsigned                                          |
| 11-12         |    `0 ... 10000`   | PM 4.0, ug/m^3. Resolution 0.1/bit, range 0 ... 1000. 16bit unsigned                                          |
| 13-14         |    `0 ... 10000`   | PM 10.0, ug/m^3. Resolution 0.1/bit, range 0 ... 1000. 16bit unsigned                                         |
| 15-16         |    `0 ... 40000`   | CO2 concentration, ppm. Resolution 1/bit, range 0 ... 40000. 16bit unsigned                                   |
| 17, +FLAGS b6 |     `0 ... 500`    | VOC index, unitless. Resolution 1 / bit, range 0 ... 500. 9 bit unsigned, least significant bit in Flags byte |
| 18, +FLAGS b7 |     `0 ... 500`    | NOX index, unitless. Resolution 1 / bit, range 0 ... 500. 9 bit unsigned, least significant bit in Flags byte |
| 19-21         | `0 ... 14 428 400` | Luminosity, Lux. Resolution 0.01/bit, range 0 ... 144 284                                                     |
| 22            |        `255`       | Reserved                                                                                                      |
| 23            |        `255`       | Reserved                                                                                                      |
| 24            |        `255`       | Reserved                                                                                                      |
| 25-27         | `0 ... 16 777 214` | Measurement sequence counter. Each new sample increments counter by 1. 24bit unsigned                         |
| 28            |    `0bVVXXXXXV`    | Flags. Value of each bit is described below                                                                   |
| 29-33         |   `0xFFFFFFFFFF`   | Reserved                                                                                                      |
| 34-39         |   `Any valid mac`  | 48bit MAC address.                                                                                            |

*Not available* is signified by largest presentable number for unsigned values, smallest presentable number for signed values and all bits set for mac. All fields are MSB first. All signed values are 2-complement, i.e. `0xFC18` is read as `-1000` and `0x03E8` is read as `1000`. If original data overflows the data format, data is clipped to closest value that can be represented. For example temperature 170.00 C becomes 163.835 C

### Data field descriptions

#### Temperature

Values supported: -163.835 °C to +163.835 °C in 0.005 °C increments.

*Example*

| Value    | Measurement             |
| -------- | ----------------------- |
| `0x0000` | 0 °C                    |
| `0x01C3` | +2.255 °C               |
| `0xFE3D` | -2.255 °C               |
| `0x8000` | Invalid / not available |

#### **Humidity**

Values supported: 0.0 % to 100 % in 0.0025 % increments. Higher values than 100 % are possible, but they generally indicate a faulty or miscalibrated sensor.

*Example*

| Value   | Measurement             |
| ------- | ----------------------- |
| `0`     | 0%                      |
| `10010` | 25.025%                 |
| `40000` | 100.0%                  |
| `65535` | Invalid / not available |

#### **Atmospheric Pressure**

Values supported: 50000 Pa to 115534 Pa in 1 Pa increments.

*Example*

| Value   | Measurement                            |
| ------- | -------------------------------------- |
| `00000` | 50000 Pa                               |
| `51325` | 101325 Pa (average sea-level pressure) |
| `65534` | 115534 Pa                              |
| `65535` | Invalid / not available                |

#### **PM 1.0/2.5/4.0/10.0**

Values supported: 0 to 6553.4 (ug/m^3), however the sensor on Ruuvi supports only 1000 ug/m^3. Values are uint16\_t, MSB first. All channels are identical. Resolution is 0.1 per bit

Note: Each measurement means "particle smaller than this", i.e. 10.0 includes 4.0 includes 2.5 includes 1.0, so measured value for larger particles is always larger.

*Example*

| Value    | Measurement             |
| -------- | ----------------------- |
| `0x0000` | 0 ug/m^3                |
| `0x03E8` | 100.0 ug/m^3            |
| `0xFFFF` | Invalid / not available |

#### CO2

Values supported: 0 to 65534 (ppm), however the sensor on Ruuvi supports only 40000 ppm and in natural environment CO2 is always at least around 400 ppm. Resolution is 1 per bit. Values are uint16\_t, MSB first.

| Value    | Measurement             |
| -------- | ----------------------- |
| `0x0000` | 0 ppm                   |
| `0x03E8` | 1000 ppm                |
| `0xFFFF` | Invalid / not available |

#### VOC, NOX

Volatile Organic Compounds and Nitrogen Oxides are unitless indexes which learn the installation environment and track changes over time. For VOC the index average is 100, i.e. values under 100 mean the air quality is improving and values over 100 mean the air quality is getting worse.

Nox index has base value of 1, values higher than 1 meaning there's more nitrogen oxides in the air than usual.

Both values use 9 bits, least significant bit is in Flags byte.

| Value   | Measurement             |
| ------- | ----------------------- |
| `0x000` | 0                       |
| `0x0E8` | 232                     |
| `0x1FF` | Invalid / not available |

#### **Luminosity**

Luminosity represents the light level in the environment of the sensor. The light level is compensated with human eye sensitivity curve. Value is 24-bit unsigned fixed-point number with resolution of 0.01

Please note that the highest valid value is 16 777 214, and 16 777 215 is reserved for the "not available".

*Example*

| Value      | Measurement             |
| ---------- | ----------------------- |
| `0x0000`   | 0 lux                   |
| `0x03E8`   | 10.00 lux               |
| 16 777 215 | Invalid / not available |

#### **Measurement sequence number**

Measurement sequence number gets incremented by one for every measurement. It can be used to gauge signal quality and packet loss as well as to deduplicated data entries. You should note that the measurement sequence refers to data rather than transmission, so you might receive many transmissions with the same measurement sequence number. Please note that the highest valid value is 16 777 214, and 16 777 215 is reserved for the "not available".

*Example*

| Value        | Measurement             |
| ------------ | ----------------------- |
| `00`         | 0 counts                |
| `1000`       | 1000 counts             |
| `16 777 215` | Invalid / not available |

#### **Flags**

Flags byte contains additional information and is interpreted bit-by-bit. "X" Means "don't care, 1 or 0". "V" Means the value of bit. Bits 1...5 are reserved. Bit 0 is least significant, bit 7 is most significant.

| Value         | Significance                                                                                |
| ------------- | ------------------------------------------------------------------------------------------- |
| `0bXXXX XXXV` | 1 -> Calibration in progress, sensor data not fully accurate yet. 0 -> Calibration complete |
| `0bXVXX XXXX` | VOC bit 9, 1-> set, 0 -> not set                                                            |
| `0bVXXX XXXX` | NOX bit 9, 1-> set, 0 -> not set                                                            |

#### **MAC address**

MAC address is static, statistically unique random identifier of the device. The address is 48 bits, with 2 most significant bits always set.

### Test vectors

These test vectors are based on [ruuvi.endpoints.c](https://github.com/ruuvi/ruuvi.endpoints.c) project. The tests are bidirectional, decode-encode results in original raw data. Encode-decode must result in same values with given precision, but floating point rounding differences may occur.

#### Case: valid data

Raw binary data: `0xE1170C5668C79E0065007004BD11CA00C90A0213E0ACXXXXXXDECDEE01XXXXXXXXXXCBB8334C884F` XX : Reserved

| Field                | Value                                                                                                               |
| -------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Data format          | `E1`                                                                                                                |
| Temperature          | `29.500` C                                                                                                          |
| Pressure             | `101102` Pa                                                                                                         |
| Humidity             | `55.300` RH-%                                                                                                       |
| PM 1.0               | `10.1` ug/m^3                                                                                                       |
| PM 2.5               | `11.2` ug/m^3                                                                                                       |
| PM 4.0               | `121.3` ug/m^3                                                                                                      |
| PM 10.0              | `455.4` ug/m^3                                                                                                      |
| CO2                  | `201` ppm                                                                                                           |
| VOC                  | `20`                                                                                                                |
| NOX                  | `4`                                                                                                                 |
| Luminosity           | `13 027.00` Lux                                                                                                     |
| Measurement Sequence | `14 601 710`                                                                                                        |
| Flags                | <p>Calibration in progress: <code>True</code></p><p>VOC b9: <code>false</code></p><p>NOX b9: <code>false</code></p> |
| MAC                  | `CB B8 33 4C 88 4F`                                                                                                 |

#### Case: maximum values

Raw binary data: `0xE17FFF9C40FFFE27102710271027109C40FAFADC28F0XXXXXXFFFFFE3FXXXXXXXXXXCBB8334C884F` XX : Reserved

| Field                | Value                                                                                                               |
| -------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Data format          | `E1`                                                                                                                |
| Temperature          | `163.835` C                                                                                                         |
| Pressure             | `115534` Pa                                                                                                         |
| Humidity             | `100.000` RH-%                                                                                                      |
| PM 1.0               | `1000.0` ug/m^3                                                                                                     |
| PM 2.5               | `1000.0` ug/m^3                                                                                                     |
| PM 4.0               | `1000.0` ug/m^3                                                                                                     |
| PM 10.0              | `1000.0` ug/m^3                                                                                                     |
| CO2                  | `40000` ppm                                                                                                         |
| VOC                  | `500`                                                                                                               |
| NOX                  | `500`                                                                                                               |
| Luminosity           | `144284.00` Lux                                                                                                     |
| Measurement Sequence | `16 777 214`                                                                                                        |
| Flags                | <p>Calibration in progress: <code>True</code></p><p>VOC b9: <code>false</code></p><p>NOX b9: <code>false</code></p> |
| MAC                  | `CB B8 33 4C 88 4F`                                                                                                 |

Case: minimum values

Raw binary data: `0xE1800100000000000000000000000000000000000000XXXXXX0000000XXXXXXXXXXXCBB8334C884F` XX : Reserved

| Field                | Value                                                                                                                |
| -------------------- | -------------------------------------------------------------------------------------------------------------------- |
| Data format          | `E1`                                                                                                                 |
| Temperature          | `-163.835` C                                                                                                         |
| Pressure             | `50000` Pa                                                                                                           |
| Humidity             | `000.000` RH-%                                                                                                       |
| PM 1.0               | `0000.0` ug/m^3                                                                                                      |
| PM 2.5               | `0000.0` ug/m^3                                                                                                      |
| PM 4.0               | `0000.0` ug/m^3                                                                                                      |
| PM 10.0              | `0000.0` ug/m^3                                                                                                      |
| CO2                  | `00000` ppm                                                                                                          |
| VOC                  | `000`                                                                                                                |
| NOX                  | `000`                                                                                                                |
| Luminosity           | `00000.00` Lux                                                                                                       |
| Measurement Sequence | `00 000 000`                                                                                                         |
| Flags                | <p>Calibration in progress: <code>false</code></p><p>VOC b9: <code>false</code></p><p>NOX b9: <code>false</code></p> |
| MAC                  | `CB B8 33 4C 88 4F`                                                                                                  |

#### Case: Invalid values

Raw binary data: `0xE18000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFXXXXXXFFFFFFFEXXXXXXXXXXFFFFFFFFFFFF` XX : Reserved

| Field                | Value                                                                                                              |
| -------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Data format          | `E1`                                                                                                               |
| Temperature          | `NaN` C                                                                                                            |
| Pressure             | `NaN` Pa                                                                                                           |
| Humidity             | `NaN` RH-%                                                                                                         |
| PM 1.0               | `NaN` ug/m^3                                                                                                       |
| PM 2.5               | `NaN` ug/m^3                                                                                                       |
| PM 4.0               | `NaN` ug/m^3                                                                                                       |
| PM 10.0              | `NaN` ug/m^3                                                                                                       |
| CO2                  | `NaN` ppm                                                                                                          |
| VOC                  | `NaN`                                                                                                              |
| NOX                  | `NaN`                                                                                                              |
| Luminosity           | `NaN` Lux                                                                                                          |
| Measurement Sequence | `NaN`                                                                                                              |
| Flags                | <p>Calibration in progress: <code>false</code></p><p>VOC b9: <code>true</code></p><p>NOX b9: <code>true</code></p> |
| MAC                  | `FF FF FF FF FF FF`                                                                                                |


# Bluetooth connection

Lifecycle: Beta testing

### **Creating a connection**

The Ruuvi advertises itself in connectable, scannable mode in 1 MBit / s PHY. The scan response contains full device name “Ruuvi XXXX” and UUID of Nordic UART Service (NUS).

The central must request for scan responses (active scan) and connect to a device with matching scan response. Advertising payload should be ignored.

The Ruuvi has 3 services: Nordic Buttonless DFU service (DFU), Device Information Service (DIS) and NUS. NUS is used for most of the application communication with the device.

In development versions of firmware there is 120 second delay from starting of the connection in which the central must complete the registration process and register to NUS TX Notfications. If the deadline is not met, tag assumes that there is a software lockup and reboots. In production versions this deadline is 12 seconds.

![Connection flow](/files/-LsuRjkafxirOKRp68xi)


# DIS (Device Information Service)


# NUS (Nordic UART Service)

**Introduction**

Nordic Uart Service emulates Universal Asynchronous Receiver Transmitter over Bluetooth. NUS has 2 characteristics, RX and TX. Central registers to TX notifications and once notification registration is acknowledged central can start writing to RX characteristic. Each characteristic accepts up to 20 byte payloads, interpretation of those payloads depends on application.

**Watchdog on RuuviTag** In development versions of RuuviTag firmware there is 120 second delay from starting of the connection in which the central must complete the registration process. If the deadline is not met, tag assumes that there is a software lockup and reboots. In production versions this deadline is 12 seconds.

**Technical details**

The Ruuvi Firmware standard messages have a structure of 3 byte header and 8 byte payload. The header has a structure of destination, source and type. Last bit of the type is R / !W bit, if the last bit is set (odd number) the message is understood as a read command. The payload is defined by the header.

| **Service**         | UUID                                 |
| ------------------- | ------------------------------------ |
| Nordic UART Service | 6E400001-B5A3-F393-E0A9-E50E24DCCA9E |

| Characteristic | UUID                                 | Read | Write | Notify |
| -------------- | ------------------------------------ | ---- | ----- | ------ |
| RX             | 6E400002-B5A3-F393-E0A9-E50E24DCCA9E |      | X     |        |
| TX             | 6E400003-B5A3-F393-E0A9-E50E24DCCA9E | X    |       | X      |

**Getting started** We have published [ruuvi.air.ble\_nus](https://github.com/ruuvi/ruuvi.air.ble_nus) tool for developing GATT communication with RuuviTags and Ruuvi Air. This can be used for quickly trying out the 2-way communication before developing your own applcations.


# Heartbeat transmissions

Lifecycle: Alpha

While the GATT connection is established and central has registered to NUS notifications, the sensor will send a heartbeat of current sensor data at the advertisement broadvast interval.

The sensor data is in format 5, however due to GATT transmission payloads being limited to 20 bytes without MTU negotiation the data format is cut at 18 bytes, i.e. MAC address of the tag is not sent.

The heartbeat transmissions may be omitted when GATT connection is busy, for example during log reads.

The heartbeats are same data payloads as what is sent over BLE advertisements, and the interval is same as advertisement update interval.

If the heartbeat can't fit into the 20 bytes of payload of GATT transmission, it will be cut into last data element which fits in whole. For example the Ruuvi Dataformat 5 has 24 bytes of which 6 last bytes are MAC address of the tag. As the MAC address (gray) cannot fit into the payload, it is cut out leaving 18 bytes which are transmitted (green).

![Heartbeat data cut to 18 bytes](/files/-Lt4qCBtYs6kMQNQtUNl)

For details on how to parse the data please review the section Data Format 5 / RAWv2.


# Read logged history - RuuviTag

Lifecycle: in production

Related Ruuvi Devices: RuuviTag, Ruuvi

### **Log read flow**

The log read is initiated by central which requests for the type of data and time range of the logs. The log is read with command that has a header destination set to physical quantity being read, source to any and type as a log read. The payload is two 32-bit timestamps, seconds since unix epoch. First timestamp is current time, and second time is the lower bound of log data timestamps. For example if the timestamps are 1567047917 and 1566047917 data from time between 2019-08-13 13:18 and 2019-08-29 03:05 is sent.

The logs are sent in format where header destination is the source of read command, source is the physical quantity and type is log write. Payload is 4 bytes of timestamp and 4 bytes of value. Interpretation of value depends on the type of data, possible types are listed below.

When the log buffer is sent and no more data remains, a special message with the entire payload set to 0xFF is sent. It should be noted that there is no way to send only “missing sections” of data from the middle of the logs, logs are always retrieved to the end of the stored data.

### **Example communication - read temperature**

| **Device** | **Header** | **Payload**         | **Description**                                                                                                         |
| ---------- | ---------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Central    | 0x30 30 11 | 0x5D6740ED 5D57FEAD | “To: temperature. From: temperature. Action: read log data. Clock is 2019-08-29 03:05 now, start from 2019-08-13 13:18” |
| Peripheral | 0x30 30 10 | 5D57FEAD 000000098D | “To: temperature. From: temperature. Action: write log data. Temperature at 2019-08-13 13:18 24.45 C“                   |
| .          | .          | .                   | Log entry                                                                                                               |
| .          | .          | .                   | Log Entry                                                                                                               |
| Peripheral | 0x30 30 10 | 0x5D6740ED FFFFF8AC | “To: temperature. From: temperature. Action: write log data. Temperature at 2019-08-29 0305 -18.76 C”                   |
| Peripheral | 0x30 30 10 | 0xFFFFFFFF FFFFFFFF | “To: temperature. From: temperature. Action: write log data. No more logs”                                              |

### **Example communication - read all environmental data**

| **Device** | **Header** | **Payload**         | **Description**                                                                                                             |
| ---------- | ---------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Central    | 0x3A 3A 11 | 0x5D6740ED 5D57FEAD | “To: environmental. From: environmental. Action: read log data. Clock is 2019-08-29 03:05 now, start from 2019-08-13 13:18” |
| Peripheral | 0x3A 30 10 | 5D57FEAD 000000098D | “To: environmental. From: temperature. Action: write log data. Temperature at 2019-08-13 13:18 24.45 C“                     |
| Peripheral | 0x3A 31 10 | 5D57FEAD 000000098D | “To: environmental. From: humidity. Action: write log data. Humidity at 2019-08-13 13:18 24.45 RH-%“                        |
| Peripheral | 0x3A 32 10 | 5D57FEAD 000000098D | “To: environmental. From: pressure. Action: write log data. Pressure at 2019-08-13 13:18 2445 Pa“                           |
| .          | .          | .                   | Log entry                                                                                                                   |
| .          | .          | .                   | Log Entry                                                                                                                   |
| .          | .          | .                   | Log Entry                                                                                                                   |
| Peripheral | 0x3A 3A 10 | 0xFFFFFFFF FFFFFFFF | “To: environmental. From: environmental. Action: write log data. No more logs”                                              |

You can try the communication out quickly with our [sample script](https://github.com/ruuvi/ruuvi.air.ble_nus/blob/master/scripts/ruuvi_ble_nus_read_hist.py)

```
python3 ruuvi_ble_nus_read_hist.py --port /dev/ttyACM1 --mac_addr DB:50:4B:2A:65:6A --req env --cur_time 1733763600 --time_interval 3600
```

### **Data endpoints**

| Endpoint byte | Value                    | Interpretation                                                           |
| ------------- | ------------------------ | ------------------------------------------------------------------------ |
| 0x30          | Temperature              | int32\_t, 0.01 C per LSB                                                 |
| 0x31          | Humidity                 | uint32\_t, 0.01 RH-% per LSB                                             |
| 0x32          | Air pressure             | uint32\_t, 1 Pa per LSB                                                  |
| 0x3A          | All environmental values | Special value which can be used to query all environmental data at once. |

### **Full length data**

File below is abbreviated nRF Connect log with some heartbeat transmissions and clutter removed. Log read command was added manually

{% file src="/files/-LsvAmsF0ZoW4MTPm9Qy" %}
Connection log
{% endfile %}

### Timeout

If log read takes over 5 minutes, the tag assumes that it has encourtered an internal error and will send error message with header type `0xF0` and payload set to `0xFF`, e.g. `0x 30 30 F0 FF FF FF FF FF FF FF FF` . It's safe to resume log read by sending a repeated log read command with start timestamp at the last received element.


# Read logged history - Ruuvi Air

Related Ruuvi Devices: Ruuvi Air

## Overview

Ruuvi Air supports reading logged sensor history data over a Bluetooth Low Energy (BLE) connection using the Nordic UART Service (NUS). This allows mobile applications to retrieve historical air quality measurements stored on the device.

The protocol is similar to [RuuviTag log reading](/communication/bluetooth-connection/nordic-uart-service-nus/log-read), but uses a different data format (E1) that includes additional air quality measurements such as PM2.5, CO₂, VOC and NOx levels.

## Log read flow

1. The mobile app connects to Ruuvi Air over BLE
2. The app negotiates a larger MTU (247+ bytes recommended)
3. The app subscribes to NUS TX characteristic notifications
4. The app sends a log read command to the NUS RX characteristic
5. Ruuvi Air responds with multiple packets containing logged records
6. When all records are sent, Ruuvi Air sends an end-of-transmission marker

### Request format

The log read request is an 11-byte message with the following structure:

| Byte offset | Field        | Description                                                         |
| ----------- | ------------ | ------------------------------------------------------------------- |
| 0           | Destination  | `0x3B` (Air Quality endpoint)                                       |
| 1           | Source       | `0x3B` (Air Quality endpoint)                                       |
| 2           | Operation    | `0x21` (Multi-record log read) or `0x11` (Single-record log read)   |
| 3-6         | Current time | Current Unix timestamp (big-endian, seconds since epoch)            |
| 7-10        | Start time   | Start Unix timestamp to read from (big-endian, seconds since epoch) |

The multi-record read (`0x21`) is recommended as it packs multiple records per BLE packet for faster transfer.

### Example request

Reading all air quality data from timestamp `1733760000` (2024-12-09 16:00:00 UTC) with current time `1733763600` (2024-12-09 17:00:00 UTC):

```
0x3B 3B 21 67 57 01 B0 67 56 F2 00
```

* `0x3B 3B`: Destination and Source = Air Quality
* `0x21`: Multi-record log read operation
* `0x6757 01B0`: Current time = 1733763600
* `0x6756 F200`: Start time = 1733760000

## Response format

### Multi-record response packet

When using multi-record read (`0x21`), each response packet contains multiple records:

| Byte offset | Field         | Description                      |
| ----------- | ------------- | -------------------------------- |
| 0           | Destination   | Source index from request        |
| 1           | Source        | `0x3B` (Air Quality endpoint)    |
| 2           | Operation     | `0x20` (Multi-record log write)  |
| 3           | Num records   | Number of records in this packet |
| 4           | Record length | Length of each record (38 bytes) |
| 5+          | Records       | Packed record data               |

### Record data format (38 bytes per record)

Each record contains a timestamp followed by E1 data format payload:

| Byte offset | Field        | Type         | Description                       |
| ----------- | ------------ | ------------ | --------------------------------- |
| 0-3         | Timestamp    | uint32\_t BE | Unix timestamp in seconds         |
| 4           | Data format  | uint8\_t     | `0xE1`                            |
| 5-6         | Temperature  | int16\_t BE  | Temperature × 200 (°C)            |
| 7-8         | Humidity     | uint16\_t BE | Humidity × 400 (%)                |
| 9-10        | Pressure     | uint16\_t BE | (Pressure - 50000) Pa             |
| 11-12       | PM1.0        | uint16\_t BE | PM1.0 × 10 (µg/m³)                |
| 13-14       | PM2.5        | uint16\_t BE | PM2.5 × 10 (µg/m³)                |
| 15-16       | PM4.0        | uint16\_t BE | PM4.0 × 10 (µg/m³)                |
| 17-18       | PM10.0       | uint16\_t BE | PM10.0 × 10 (µg/m³)               |
| 19-20       | CO₂          | uint16\_t BE | CO₂ (ppm)                         |
| 21          | VOC          | uint8\_t     | VOC index (0-500), bit 9 in flags |
| 22          | NOx          | uint8\_t     | NOx index (0-500), bit 9 in flags |
| 23-25       | Reserved     | uint24\_t BE | Reserved                          |
| 26          | Reserved     | uint8\_t     | Reserved                          |
| 27          | Reserved     | uint8\_t     | Reserved                          |
| 28          | Reserved     | uint8\_t     | Reserved                          |
| 29-31       | Sequence cnt | uint24\_t BE | Measurement sequence counter      |
| 32          | Flags        | uint8\_t     | Extended bits for 9-bit values    |
| 33-37       | Reserved     | -            | Reserved for future use           |

### Flags byte interpretation

The flags byte contains the 9th bit for values that need more than 8 bits of precision:

| Bit | Field     |
| --- | --------- |
| 6   | VOC bit 9 |
| 7   | NOx bit 9 |

### Decoding formulas

| Field       | Formula                     | Unit  | Range                |
| ----------- | --------------------------- | ----- | -------------------- |
| Temperature | raw / 200.0                 | °C    | -163.840 to +163.830 |
| Humidity    | raw / 400.0                 | %     | 0 to 100             |
| Pressure    | raw + 50000                 | Pa    | 50000 to 115534      |
| PM values   | raw / 10.0                  | µg/m³ | 0 to 6553.4          |
| CO₂         | raw                         | ppm   | 0 to 65534           |
| VOC/NOx     | (byte \| (flag\_bit9 << 8)) | index | 0 to 500             |

### Invalid values

When a sensor value is unavailable or invalid, the following special values are used:

| Field       | Invalid value |
| ----------- | ------------- |
| Temperature | `0x8000`      |
| Humidity    | `0xFFFF`      |
| Pressure    | `0xFFFF`      |
| PM values   | `0xFFFF`      |
| CO₂         | `0xFFFF`      |
| VOC/NOx     | `0x1FF` (511) |
| Sequence    | `0xFFFFFF`    |

### End of transmission

When all records have been sent, Ruuvi Air sends a final packet with:

* `num_records = 0`
* `record_length = 38`

Example end marker:

```
0x3B 3B 20 00 26
```

This indicates no more records are available.

## Example communication

| Device     | Data                                 | Description                                                                          |
| ---------- | ------------------------------------ | ------------------------------------------------------------------------------------ |
| Central    | `0x3B 3B 21 67 57 01 B0 67 56 F2 00` | "Read air quality log. Current time: 2024-12-09 17:00, start from: 2024-12-09 16:00" |
| Peripheral | `0x3B 3B 20 06 26 ...`               | "6 records of 38 bytes each in this packet"                                          |
| ...        | ...                                  | Additional packets with records                                                      |
| Peripheral | `0x3B 3B 20 00 26`                   | "No more records (end of log)"                                                       |

You can try the communication out quickly with our [sample script](https://github.com/ruuvi/ruuvi.air.ble_nus/blob/master/scripts/ruuvi_ble_nus_read_hist.py)

```
python3 ruuvi_ble_nus_read_hist.py --port /dev/ttyACM1 --mac_addr FD:54:6F:6C:52:92 --req airq --multi --cur_time 1733763600 --time_interval 3600
```

## Implementation notes

### MTU considerations

Negotiating a larger MTU (247+ bytes) is recommended for optimal throughput. A minimum MTU of \~46 bytes is required to fit a single 38-byte record with headers. With a larger MTU, BLE Data Length Extension (DLE) enables up to 244 bytes per notification. With 38 bytes per record and a 5-byte packet header, up to 6 records can be packed in a single BLE packet:

```
(244 - 5) / 38 = 6 records per packet
```

### Time synchronization

The protocol uses a time offset calculation to handle clock drift between the device and the mobile app:

1. The app sends its current Unix time in the request
2. The device calculates the offset: `offset = app_time - device_time`
3. Timestamps in responses are adjusted by this offset

This ensures the returned timestamps are relative to the app's clock rather than the device's internal clock.

### Record interval

Ruuvi Air logs measurements once per 5 minutes. A full day of history contains approximately 288 records.

## Code references

* **Firmware**: [ruuvi.air.main/src/nus.c](https://github.com/ruuvi/ruuvi.air.main/blob/main/src/nus.c)
* **Android**: [NordicGattManager.kt](https://github.com/ruuvi/com.ruuvi.bluetooth.default/blob/main/default_bluetooth_library/src/main/java/com/ruuvi/station/bluetooth/gatt/NordicGattManager.kt)
* **iOS**: [BTServices.swift](https://github.com/ruuvi/BTKit/blob/main/Sources/BTKit/Devices/BTServices.swift)
* **Data format**: [ruuvi.endpoints.c](https://github.com/ruuvi/ruuvi.endpoints.c)

## Related documentation

* [Read logged history - RuuviTag](/communication/bluetooth-connection/nordic-uart-service-nus/log-read)
* [Data format E1](/communication/bluetooth-advertisements/data-format-e1)


# Real Time Transfer (RTT)

Lifecycle: In production

RuuviTags are nRF52-based devices. The programming is done over Serial Wire Debug (SWD) interface through nRF52 devkit. The RuuviTag can be interfaced to nRF52-DK either through [Ruuvi Devshield](https://shop.ruuvi.com/product/devkit/) or a [Tag-Connect cable](http://www.tag-connect.com/TC2030-CTX-NL).

The Real-Time Transfer is Segger's technology for printing UART-like messages from the attached Cortex-M device. Test versions of Ruuvi Firmware print out the logs via RTT which can be useful for example when developing your own applications which communicate over GATT.

**Connecting the hardware**

First you'll need to connect your PC to nRF52-DK via USB-micro cable, then you'll need to connect the nRF52-DK to Ruuvi via devkit or the Tag-Connect cable. nRF52-DK autodetects an external target and programs it, but if there is an issue with connection you can get errors from the software or program the nRF52 onboard the nRF52-DK. You can cut the power measurement jumper on nRF52-DK to be sure to not accidentally connect to the wrong nRF52.

![RuuviTag zip-tied to devkit with soldered wires for debugging.](/files/-Lt90cJSLERoHKmCbift)

![Programming a RuuviTag with DK and Tag-connect cable](/files/-Lt90rcrkTDkbv7Yd1nm)

If you get a Tag-Connect cable, be sure to get the 10-pin connector Cortex-M no-legs version or you'll need to solder the wires to pins on DK as above.

**Software connection**

You'll need to install[ Segger software and documentation pack](https://www.segger.com/downloads/jlink/). On Windows you can use JLinkRTTViewer which has a GUI for connecting to device and storing the logs to a file, on Linux and Mac OSX you have to use JLinkExe to connect to the Ruuvi and JLinkRTTClient to view the logs.

JLinkExe has to be started first, commands are

* *connect*
* Device -> *NRF52832\_XXAA*
* Interface -> *s* (SWD)
* Speed -> *4000* (kHz)
* Restart program and halt -> *r*
* Once JLinkRTTViewer is connected, go -> *g*

![JLinkExe connected to Ruuvi](/files/-Lt92eIK_ga3Nsut_ODT)

JLinkRTTClient will connect automatically to the JLinkExe session, just start JLinkRTTClient and start the firmware program on JLinkExe with *g.*

![RTT Logs](/files/-Lt92UcUBOH76bV3mJdk)


# Ruuvi Cloud

Lifecycle: in Production

## Overview

Ruuvi Network (Serverless) is divided into two core APIs:

1. Gateway API
2. User API
3. Internal API

Gateway API handles receiving and storing data sent by Ruuvi Gateways to Ruuvi Network. User API is the User facing API for creating and editing user information and retrieving user data as well as claiming and sharing tags.

{% content-ref url="/pages/-MESATXpupJidhE5KPzt" %}
[Gateway API](/communicate-with-ruuvi-cloud/cloud/gateway-api)
{% endcontent-ref %}

{% content-ref url="/pages/-MESFD0X\_MRcSpGFDbXc" %}
[User API](/communicate-with-ruuvi-cloud/cloud/user-api)
{% endcontent-ref %}

{% content-ref url="/pages/-MHW0N7-rmqygODp5lY6" %}
[Internal API](/communicate-with-ruuvi-cloud/cloud/internal-api)
{% endcontent-ref %}

Ruuvi Station Android and iOS apps and also Station Web (<https://station.ruuvi.com/>) can store some of the settings in the cloud:

{% content-ref url="/pages/PHgEaxj8mg6oZyum7iAf" %}
[Cloud stored app settings](/communicate-with-ruuvi-cloud/cloud/cloud-stored-app-settings)
{% endcontent-ref %}


# User API

Ruuvi Cloud user facing API. Lifecycle: in production

User API uses a JSON based API to allow users to register, secure and edit their information as well as claim and share sensors, retrieve sensor data and alter their subscription details. The documentation is being migrated to the OpenAPI specification. Migrated endpoints are on their own subpages with detailed descriptions, while legacy documentation is at the root level.

In case OpenAPI and this hand-written documentation are in conflict, OpenAPI documentation is correct.

The OpenAPI specification can be found at <https://github.com/ruuvi/ruuvi.cloudapi.yaml>.

There is also an associated Postman collection for easy getting started, <https://www.postman.com/ruuvi-7a5dd1f8-3803443/ruuvi-user-api/api/42a942ba-8a05-4ecf-a305-cd7439b2fc69?version=fa7b6f1e-9cc6-48d9-8afa-ccc0a68c671d>. The Postman collection is updated as new endpoints are migrated to OpenAPI.

Most of the API requests have to be authenticated with a bearer token. To obtain a token, you must call /register endpoint which will email you a short verification code. Then you must call /verify endpoint with the emailed token to receive the full bearer token. Details on how to obtain the token are under Register User section in API documentation. Token usage is described with each endpoint. In short, add `"Authorization: Bearer $TOKEN"` to the request headers.

## Request deletion of account

<mark style="color:green;">`POST`</mark> `https://network.ruuvi.com/request-delete`

This operation requests complete removal of user account from Ruuvi Cloud. After a successful call to this endpoint, user gets a verification email with a link to confirm deletion of account.

#### Headers

| Name                                            | Type   | Description                           |
| ----------------------------------------------- | ------ | ------------------------------------- |
| Authorization<mark style="color:red;">\*</mark> | String | Bearer token to authorize the request |

#### Request Body

| Name                                    | Type   | Description                |
| --------------------------------------- | ------ | -------------------------- |
| email<mark style="color:red;">\*</mark> | String | Email of account to delete |

{% tabs %}
{% tab title="200: OK Verification email has been sent" %}

```javascript
{
    "result": "success",
    "data": {
        "email": "otso+test3@ruuvi.com"
    }
}
```

{% endtab %}

{% tab title="400: Bad Request Missing authorization or email parameter" %}

```javascript
{
    "result": "error",
    "error": "<SPECIFIC ERROR>",
    "code": "ER_<ERROR>"
}
```

{% endtab %}

{% tab title="403: Forbidden Returned if Authorization token does not match email" %}

```javascript
{
    "result": "error",
    "error": "Unauthorized request.",
    "code": "ER_UNAUTHORIZED"
}
```

{% endtab %}
{% endtabs %}

## Verify account deletion operation

<mark style="color:blue;">`GET`</mark> `https://network.ruuvi.com/verify-delete`

Following actions will be done:

User sensors will be unshared

Sensors shared to user will be removed.

~~Data of user sensors will be deleted~~. (TODO)

User account data, including sensor claims and settings, will be deleted.

Account deletion is a permament action which cannot be undone

#### Path Parameters

| Name                                    | Type   | Description               |
| --------------------------------------- | ------ | ------------------------- |
| token<mark style="color:red;">\*</mark> | String | Short verification string |

{% tabs %}
{% tab title="200: OK Account deletion was started" %}

```javascript
{
    // Response
}
```

{% endtab %}

{% tab title="403: Forbidden Invalid or missing authorization token" %}

```javascript
{
   {
    "result": "error",
    "error": "Unauthorized request.",
    "code": "ER_UNAUTHORIZED"
}
```

{% endtab %}
{% endtabs %}

## Claim a sensor for user

<mark style="color:green;">`POST`</mark> `https://netowrk.ruuvi.com/claim`

After this call, given sensor is claimed under authenticated user account

#### Headers

| Name                                            | Type   | Description                            |
| ----------------------------------------------- | ------ | -------------------------------------- |
| Authorization<mark style="color:red;">\*</mark> | String | BBearer token to authorize the request |

#### Request Body

| Name                                     | Type   | Description                                                                |
| ---------------------------------------- | ------ | -------------------------------------------------------------------------- |
| sensor<mark style="color:red;">\*</mark> | String | MAC address of sensor to claim, e.g. "AA:BB:CC:11:22:33"                   |
| name                                     | String | Human-readable name of sensor, e.g. "Fridge temperature sensor"            |
| description                              | String | Human-readable description of sensor, e.g. "Sensor in top shelf of fridge" |

{% tabs %}
{% tab title="200: OK Sensor was claimed successfully" %}

```javascript
{
    "result": "success",
    "data": {
        "sensor": "C5:2A:E7:4D:CE:7F"
    }
}
```

{% endtab %}

{% tab title="400: Bad Request Request had malformed JSON or was missing a required parameter.  Alternatively user account has reached subscription limit" %}

```javascript
{
    'code': {'ER_MISSING_ARGUMENT', 'ER_CLAIM_COUNT_REACHED'}
}
```

{% endtab %}

{% tab title="401: Unauthorized Request had no authorization token" %}

```javascript
{

}
```

{% endtab %}

{% tab title="409: Conflict Sensor is claimed by another account" %}

```javascript
{
    'code': 'ER_SENSOR_ALREADY_CLAIMED';
}
```

{% endtab %}

{% tab title="500: Internal Server Error Unexpected error occurred in handler" %}

```javascript
{
    // Response
}
```

{% endtab %}
{% endtabs %}

## Unclaim a sensor from your user

<mark style="color:green;">`POST`</mark> `https://network.ruuvi.com/unclaim`

Unclaims a sensor from your user, revoking your own access to it and making it claimable by other users.

#### Headers

| Name                                            | Type   | Description                           |
| ----------------------------------------------- | ------ | ------------------------------------- |
| Authorization<mark style="color:red;">\*</mark> | string | Bearer token to authorize the request |

#### Request Body

| Name                                     | Type    | Description                     |
| ---------------------------------------- | ------- | ------------------------------- |
| sensor<mark style="color:red;">\*</mark> | string  | ID of the sensor to be unshared |
| deleteData                               | boolean | set to true to delete user data |

{% tabs %}
{% tab title="200 " %}

```
{
    "result": "success"
}
```

{% endtab %}
{% endtabs %}

## Get your sensors

<mark style="color:blue;">`GET`</mark> `https://network.ruuvi.com/sensors`

Fetches a list of sensors you have access to including who those are shared to. This end-point deprecates the old *shared* end-point.

#### Query Parameters

<table><thead><tr><th width="136">Name</th><th>Type</th><th>Description</th></tr></thead><tbody><tr><td>sensor</td><td>string</td><td>Optionally filter only one sensor</td></tr></tbody></table>

#### Headers

| Name          | Type   | Description              |
| ------------- | ------ | ------------------------ |
| Authorization | string | Bearer token of the user |

{% tabs %}
{% tab title="200 " %}

```
{
    "result": "success",
    "data": {
        "sensors": [
            {
                "sensor": "<SENSOR ID>",
                "name": "<SENSOR NAME>",
                "picture": "<SENSOR PICTURE URL>",
                "public": <TRUE|FALSE>,
                "canShare": <TRUE|FALSE>,
                "offsetHumidity": <DOUBLE>,
                "offsetTemperature": <DOUBLE>,
                "offsetPressure": <DOUBLE>,
                "measurements": [
                    {
                        "gwmac": "<SOURCE GATEWAY MAC>",
                        "coordinates": "<COORDINATES / N/A>",
                        "rssi": <RSSI>,
                        "timestamp": <UNIX TIMESTAMP OF MEASUREMENT>,
                        "data": "<HEX ENCODED SENSOR DATA>"
                    }
                ]
                "sharedTo": [
                    "<EMAIL OF TARGET USER 1>",
                    ...
                ]
            },
            ...
        ],
        "sharedToMe": [
            {
                "sensor": "<SENSOR ID>",
                "name": "<SENSOR NAME>",
                "picture": "<SENSOR PICTURE URL>",
                "public": <TRUE|FALSE>,
                "canShare": <TRUE|FALSE>,
                "offsetHumidity": <DOUBLE>,
                "offsetTemperature": <DOUBLE>,
                "offsetPressure": <DOUBLE>,
                "measurements": [
                    {
                        "gwmac": "<SOURCE GATEWAY MAC>",
                        "coordinates": "<COORDINATES / N/A>",
                        "rssi": <RSSI>,
                        "timestamp": <UNIX TIMESTAMP OF MEASUREMENT>,
                        "data": "<HEX ENCODED SENSOR DATA>"
                    }
                ]
            },
        ]
    }
}
```

{% endtab %}

{% tab title="401 " %}

```
{
    "result": "error",
    "error": "Unauthorized.",
    "code": "ER_UNAUTHORIZED"
}
```

{% endtab %}
{% endtabs %}

## Get your sensors with calibration data, latest measurement, and alerts settings

<mark style="color:blue;">`GET`</mark> `https://network.ruuvi.com/sensors-dense`

Fetches the list of claimed and shared sensors with calibration data, sensor last measurement, subscription type and alert settings. By default the endpoint returns only the claimed sensors with calibration data. Optional arguments must be passed to get shared sensors, last measurement, and alert settings.

#### Query Parameters

| Name           | Type   | Description                                                                                                                                      |
| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| sensor         | string | Optionally filter only one sensor                                                                                                                |
| sharedToOthers | bool   | Optionally returns the list of users with whom each of the sensors is shared to. Returns empty list for non-owners                               |
| sharedToMe     | bool   | Optionally returns the sensors shared to the logged-in user alongside claimed sensors by the user                                                |
| measurements   | bool   | Optionally returns the latest measurement of each of the sensors in the collection. Returns also the subscription on which the data is based on. |
| alerts         | bool   | Optionally returns the alerts settings of each of the sensors in the collection                                                                  |
| settings       | bool   | Optionally returns the sensor-specific settings of sensors.                                                                                      |
| mode           | string | Fetch mode: \[dense, sparse, mixed], determines how the data is returned. Default: mixed                                                         |

{% tabs %}
{% tab title="401: Unauthorized " %}

```javascript
{
    "result": "error",
    "error": "Unauthorized.",
    "code": "ER_UNAUTHORIZED"
}
```

{% endtab %}

{% tab title="200: OK " %}

```javascript
{
    "result": "success",
    "data": {
        "sensors": [
            {
                "sensor": "<SENSOR ID>",
                "name": "<SENSOR NAME>",
                "picture": "<SENSOR PICTURE URL>",
                "public": <TRUE|FALSE>,
                "canShare": <TRUE|FALSE>,
                "offsetHumidity": <DOUBLE>,
                "offsetTemperature": <DOUBLE>,
                "offsetPressure": <DOUBLE>,
                "measurements": [
                    {
                        "gwmac": "<SOURCE GATEWAY MAC>",
                        "coordinates": "<COORDINATES / N/A>",
                        "rssi": <RSSI>,
                        "timestamp": <UNIX TIMESTAMP OF MEASUREMENT>,
                        "data": "<HEX ENCODED SENSOR DATA>"
                    }
                ],
                "sharedTo": [
                    "<EMAIL OF TARGET USER 1>",
                    ...
                ],
                "alerts": [
                    {
                        userId: <userId>,
                        sensorId: <sensorMAC>,
                        type: <humidity|pressure|temperature>,
                        min: <lower limit>,
                        max: <higher limit>,
                        enabled: <true|false>,
                        offsetHumidity: <double>,
                        offsetTemperature: <double>,
                        offsetPressure: <double>,
                        triggered: <true|false>,
                        triggeredAt: <timestamp>
                    },
                    ...
                ]
            },
            owner: <EMAIL, masked if public sensor>
            subscription: {
                    "maxHistoryDays": <INT>,
                    "maxResolutionMinutes": <INT>,
                    "emailAlertAllowed": <true|false>,
                    "pushAlertAllowed": <true|false>,
                    "subscriptionName": <STRING>
            }
            ...
        ]
    }
}
```

{% endtab %}
{% endtabs %}

## Get User Info

<mark style="color:blue;">`GET`</mark> `https://network.ruuvi.com/user`

Fetches user information for an authenticated user.

#### Headers

| Name           | Type   | Description                                                |
| -------------- | ------ | ---------------------------------------------------------- |
| Authentication | string | Authentication Bearer token retrieved from the login flow. |

{% tabs %}
{% tab title="200 User information successfully retrieved." %}

```
{
    "result": "success",
    "data": {
        "email": "my-email@email.com",
        "sensors": [
            {
                "sensor": "CD:CD:CD:CD:ED:01",
                "owner": "my-email@email.com",
                "name": "Sauna",
                "picture": "https://url-to/picture.png",
                "public": true
            },
            {
                "sensor": "AB:BA:CD:BE:AB:AA",
                "owner": "someone-else@email.com",
                "name": "Kitchen",
                "picture": "",
                "public": false
            }
        ]
    }
}
```

{% endtab %}

{% tab title="401 Unauthorized request." %}

```
{
    "result": "error",
    "error": "Unauthorized request.",
    "code": "ER_UNAUTHORIZED"
}
```

{% endtab %}
{% endtabs %}

## Get Sensor data

<mark style="color:blue;">`GET`</mark> `https://network.ruuvi.com/get`

Returns the data points for the requested sensor. Notice that for implementing pagination, you can use **since** and **until** parameters with custom **limit** to segment your results as they are always returned in either ascending or descending order by timestamp.

Data can be fetched in dense, sparse and mixed mode. Dense mode returns highest data density possible, but has a limited time range before data is pruned to save storage space. Sparse mode has downsampled data, but time range is not limited. Mixed mode returns all the dense data available and rest of the time range is filled with sparse data

#### Query Parameters

| Name                                     | Type   | Description                                                                                     |
| ---------------------------------------- | ------ | ----------------------------------------------------------------------------------------------- |
| mode                                     | string | Fetch mode: \[dense, sparse, mixed], determines how the data is returned. Default: mixed        |
| until                                    | string | Maximum timestamp of first returned result in Unix epoch format, in seconds. Default until now. |
| since                                    | string | Minimum timestamp of first returned result in Unix epoch format, in seconds. Default 0.         |
| limit                                    | string | Maximum amount of results returned (capped at 5000).                                            |
| sort                                     | string | Sort Direction for the result: \[asc, desc]. Default descending                                 |
| sensor<mark style="color:red;">\*</mark> | string | Sensor ID to retrieve the data                                                                  |

#### Headers

| Name          | Type   | Description                           |
| ------------- | ------ | ------------------------------------- |
| Authorization | string | Bearer token to authorize the request |

{% tabs %}
{% tab title="200 Returns the most recent data points for the requested tag based on configuration and parameters.f" %}

```
{
    "result": "success",
    "data": {
        "sensor": "<SENSOR ID>",
        "total": <TOTAL MEASUREMENTS RETURNED>,
        "name": "<SENSOR NAME>",
        "picture": "<SENSOR PICTURE URL OR FILENAME>",
        "measurements": [
            {
                "gwmac": "<SOURCE GATEWAY MAC>",
                "coordinates": "<COORDINATES / N/A>",
                "rssi": <RSSI>,
                "timestamp": <UNIZ TIMESTAMP OF MEASUREMENT>,
                "data": "<HEX ENCODED SENSOR DATA>"
            },
            ...
        ]
    }
}
```

{% endtab %}

{% tab title="400 " %}

```
{
    "result": "error",
    "error": "Invalid <SPECIFIC ERROR>",
    "code": "ER_INVALID_<SPECIFIC>"
}
```

{% endtab %}

{% tab title="401 In case of an invalid or expired authentication token, you will receive a unauthorized response." %}

```
{
    "result": "error",
    "error": "Unauthorized request.",
    "code": "ER_UNAUTHORIZED"
}
```

{% endtab %}

{% tab title="403 If you have not claimed or been shared the target sensor, you will receive a Forbidden." %}

```
{
    "result": "error",
    "error": "Forbidden.",
    "code": "ER_FORBIDDEN"
}
```

{% endtab %}
{% endtabs %}

## Update Sensor metadata

<mark style="color:green;">`POST`</mark> `https://network.ruuvi.com/update`

Updates sensor metadata.

#### Headers

| Name          | Type   | Description |
| ------------- | ------ | ----------- |
| Authorization | string |             |

#### Request Body

| Name              | Type    | Description                                                                                       |
| ----------------- | ------- | ------------------------------------------------------------------------------------------------- |
| offsetHumidity    | number  | Offset humidity to calibrate sensor                                                               |
| offsetPressure    | number  | Offset pressure to calibrate sensor                                                               |
| offsetTemperature | number  | Offset temperature to calibrate sensor                                                            |
| public            | boolean | If true, data will be publicly accessible.                                                        |
| sensor            | string  | Sensor ID to update                                                                               |
| name              | string  | Desired name of the tag                                                                           |
| timestamp         | number  | Epoch timestamp in seconds of settings. If backend has fresher data stored, this will be ignored. |

{% tabs %}
{% tab title="200 Only returns the fields that had an update targeted to them." %}

```
{
    "result": "success",
    "data": {
        "sensor": "<SENSOR ID>",
        "name": "<GIVEN NAME>",
        "public": "<GIVEN PUBLIC VALUE>"
    }
}
```

{% endtab %}

{% tab title="403 " %}

```
{
    "result": "error",
    "error": "Forbidden.",
    "code": "ER_FORBIDDEN"
}
```

{% endtab %}

{% tab title="404 " %}

```
{
    "result": "error",
    "error": "Sensor not claimed or found. Data not updated.",
    "code": "ER_SENSOR_NOT_FOUND"
}
```

{% endtab %}

{% tab title="500 " %}

```
{
    "result": "error",
    "error": "Unknown error occurred.",
    "code": "ER_INTERNAL"
}
```

{% endtab %}

{% tab title="409: Conflict Cloud has fresher data than timestamp" %}

```
{
    "result": "error",
    "error": "Newer setting already exists",
    "code": "ER_CONFLICT",
    "sub_code": "ER_OLD_ENTRY"
}
```

{% endtab %}
{% endtabs %}

## Upload Sensor image (part 1)

<mark style="color:green;">`POST`</mark> `https://network.ruuvi.com/upload`

Retrieves a signed upload URL to a bucket. This makes the back-end ready for the image upload to happen.

#### Headers

| Name          | Type   | Description              |
| ------------- | ------ | ------------------------ |
| Authorization | string | Bearer token of the user |

#### Request Body

| Name   | Type   | Description                                                                                                                                                      |
| ------ | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| action | string | One of: *upload*, *reset* (default: 'upload' if not given')                                                                                                      |
| sensor | string | ID of the target Sensor                                                                                                                                          |
| type   | string | <p>(<strong>Required</strong> when type is 'upload')<br>Content-Type of the desired image upload. Supported formats:<br>image/png<br>image/gif<br>image/jpeg</p> |

{% tabs %}
{% tab title="200 " %}

```
{
    "result": "success",
    "data": {
        "uploadURL": "<SIGNED UPLOAD URL>"
    }
}
```

{% endtab %}

{% tab title="403 " %}

```
{
    "result": "error",
    "error": "Forbidden.",
    "code": "ER_FORBIDDEN"
}
```

{% endtab %}
{% endtabs %}

## Upload the actual image

<mark style="color:orange;">`PUT`</mark> `<URL FROM part 1>`

Create a PUT request to the URL produced by /upload end-point with the data payload to complete the upload.

#### Headers

| Name         | Type   | Description                     |
| ------------ | ------ | ------------------------------- |
| Content-Type | string | Matching content type to part 1 |

#### Request Body

| Name              | Type   | Description                      |
| ----------------- | ------ | -------------------------------- |
| Image binary data | object | Binary data for the image upload |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}

{% tab title="403 " %}

```
```

{% endtab %}
{% endtabs %}

## Get User Settings

<mark style="color:blue;">`GET`</mark> `https://network.ruuvi.com/settings`

Gets the full list of existing user settings.

#### Headers

| Name          | Type   | Description              |
| ------------- | ------ | ------------------------ |
| Authorization | string | Bearer token of the user |

{% tabs %}
{% tab title="200 " %}

```
{
    "status": "success",
    "data": {
        "settings": {
            "<SETTING 1>": "<SETTING 1 VALUE>",
            ...
        }
    }
}
```

{% endtab %}
{% endtabs %}

## Update user setting

<mark style="color:green;">`POST`</mark> `https://network.ruuvi.com/settings`

Sets a single user setting (currently).

#### Headers

| Name          | Type   | Description              |
| ------------- | ------ | ------------------------ |
| Authorization | string | Bearer token of the user |

#### Request Body

| Name      | Type   | Description                                                                                       |
| --------- | ------ | ------------------------------------------------------------------------------------------------- |
| value     | string | Setting value                                                                                     |
| name      | string | Setting key (alphanumeric with "\_", "-" and "."                                                  |
| timestamp | number | Epoch timestamp in seconds of settings. If backend has fresher data stored, this will be ignored. |

{% tabs %}
{% tab title="200 " %}

```
{
    "status": "success",
    "data": {
        "action": "<added|updated>"
    }
}
```

{% endtab %}

{% tab title="409: Conflict Cloud has fresher data than timestamp" %}

```
{
    "result": "error",
    "error": "Newer setting already exists",
    "code": "ER_CONFLICT",
    "sub_code": "ER_OLD_ENTRY"
}
```

{% endtab %}
{% endtabs %}

## Check if a sensor with given MAC address is claimed by someone

<mark style="color:blue;">`GET`</mark> `https://network.ruuvi.com/check`

#### Query Parameters

| Name                                     | Type        | Description                |
| ---------------------------------------- | ----------- | -------------------------- |
| sensor<mark style="color:red;">\*</mark> | MAC address | AA:BB:CC:DD:EE:FF (String) |

#### Headers

| Name                                            | Type   | Description            |
| ----------------------------------------------- | ------ | ---------------------- |
| Authorization<mark style="color:red;">\*</mark> | Bearer | Bearer \<Bearer Token> |

{% tabs %}
{% tab title="200: OK Masked email of sensor owner, empty string if sensor is not owned" %}

```javascript
{
    "status": "success",
    "data": {
        "email": <string>
    }
}
```

{% endtab %}

{% tab title="400: Bad Request If request doesn" %}

```javascript
    "status": "success",
    "data": {
        "email": <string>
    }
}
```

{% endtab %}

{% tab title="403: Forbidden If there was no valid authentication" %}

```javascript
{
    "result": "error",
    "error": "Unauthorized request.",
    "code": "ER_UNAUTHORIZED"
}
```

{% endtab %}
{% endtabs %}

## Contest ownership of a sensor

<mark style="color:green;">`POST`</mark> `https://network.ruuvi.com/contest-sensor`

This call is used to reclaim a sensor claimed by someone else. After this endpoint returns 200, the sensor is claimed by calling account. Parameters are passed as a body JSON object.

#### Headers

| Name                                            | Type   | Description              |
| ----------------------------------------------- | ------ | ------------------------ |
| Authorization<mark style="color:red;">\*</mark> | String | Bearer token of the user |

#### Request Body

| Name                                     | Type   | Description                      |
| ---------------------------------------- | ------ | -------------------------------- |
| sensor<mark style="color:red;">\*</mark> | String | MAC address of sensor to reclaim |
| secret<mark style="color:red;">\*</mark> | String | Secret of sensor to reclaim      |

{% tabs %}
{% tab title="200: OK Sensor ownership was transferred" %}

```javascript
{
    "result": "success",
    "data": {
        "sensor": "C5:2A:E7:4D:CE:7F"
    }
}
```

{% endtab %}

{% tab title="400: Bad Request Missing parameter or user has full claim count" %}

```javascript
{
    'code': {'ER_CLAIM_COUNT_REACHED', 'ER_MISSING_ARGUMENT'}
}
```

{% endtab %}

{% tab title="401: Unauthorized User is not authenticated" %}

```javascript
{
    'code': 'ER_UNAUTHORIZED'
}
```

{% endtab %}

{% tab title="403: Forbidden macAddress and secret do not match" %}

```javascript
{
    'code': 'ER_FORBIDDEN'
}
```

{% endtab %}
{% endtabs %}

## Claim a subscription by a code

<mark style="color:green;">`POST`</mark> `https://network.ruuvi.com/subscription`

This endpoints applies a new subscription to user immediately. Previous subscription is lost. Parameters are passed as JSON in body. The success response has full subscription history of user, with active subscription being first element of array of subscriptions.

#### Headers

| Name                                            | Type   | Description              |
| ----------------------------------------------- | ------ | ------------------------ |
| Authorization<mark style="color:red;">\*</mark> | String | Bearer token of the user |

#### Request Body

| Name                                   | Type   | Description          |
| -------------------------------------- | ------ | -------------------- |
| code<mark style="color:red;">\*</mark> | String | Code of subscription |

{% tabs %}
{% tab title="200: OK Subscription was applied successfully" %}

```javascript
{
    "result": "success",
    "data": {
        "subscriptions": [
            {
                "subscriptionName": "DEV",
                "maxClaims": 25,
                "maxShares": 40,
                "maxSharesPerSensor": 5,
                "maxHistoryDays": 720,
                "maxResolutionMinutes": 1,
                "isActive": true,
                "startTime": 1673435374,
                "endTime": 1673608174
            },
            {
                "subscriptionName": "DEV",
                "maxClaims": 25,
                "maxShares": 40,
                "maxSharesPerSensor": 5,
                "maxHistoryDays": 720,
                "maxResolutionMinutes": 1,
                "isActive": false,
                "startTime": 1673435292,
                "endTime": 1673608092
            }
        ]
    }
}
```

{% endtab %}

{% tab title="400: Bad Request Missing code" %}

```javascript
{
    "result": "error",
    "error": "Invalid request format.",
    "code": "ER_INVALID_FORMAT"
}
```

{% endtab %}

{% tab title="401: Unauthorized Missing or invalid authorization header" %}

```javascript
{
    "result": "error",
    "error": "Unauthorized request.",
    "code": "ER_UNAUTHORIZED"
}
```

{% endtab %}

{% tab title="409: Conflict Subscription code is already used" %}

```javascript
{
    "result": "error",
    "error": "Code already claimed",
    "code": "ER_SUBSCRIPTION_CODE_USED"
}
```

{% endtab %}

{% tab title="404: Not Found Subscription code does not exist" %}

```javascript
{
    "result": "error",
    "error": "Code not found",
    "code": "ER_SUBSCRIPTION_NOT_FOUND"
}
```

{% endtab %}
{% endtabs %}

## Get subscription history

<mark style="color:blue;">`GET`</mark> `https://network.ruuvi.com/subscription`

Return array of JSON objects detaling the subscriptions user has had.

#### Headers

| Name                                            | Type   | Description              |
| ----------------------------------------------- | ------ | ------------------------ |
| Authorization<mark style="color:red;">\*</mark> | String | Bearer token of the user |

{% tabs %}
{% tab title="200: OK Returns subscription history" %}

```javascript
{
    "result": "success",
    "data": {
        "subscriptions": [
            {
                "subscriptionName": "DEV",
                "maxClaims": 25,
                "maxShares": 40,
                "maxSharesPerSensor": 5,
                "maxHistoryDays": 720,
                "maxResolutionMinutes": 1,
                "isActive": true,
                "startTime": 1673435374,
                "endTime": 1673608174
            },
            {
                "subscriptionName": "DEV",
                "maxClaims": 25,
                "maxShares": 40,
                "maxSharesPerSensor": 5,
                "maxHistoryDays": 720,
                "maxResolutionMinutes": 1,
                "isActive": false,
                "startTime": 1673435292,
                "endTime": 1673608092
            }
        ]
    }
}
```

{% endtab %}

{% tab title="403: Forbidden Request was not authenticated or authentication token was not valid" %}

```javascript
{
    "result": "error",
    "error": "Unauthorized request.",
    "code": "ER_UNAUTHORIZED"
}
```

{% endtab %}
{% endtabs %}

## Register a push notification token for user

<mark style="color:green;">`POST`</mark> `https://network.ruuvi.com/push-register`

Register a device to Cloud so Cloud can send push notifications to user. Currently only alerts for Android and iOS are supported.

Tokens must be unique, one token cannot be associated with two accounts. If token already exists in Ruuvi Cloud with another account, the token will be removed from old account.

#### Headers

| Name                                            | Type   | Description              |
| ----------------------------------------------- | ------ | ------------------------ |
| Authorization<mark style="color:red;">\*</mark> | String | Bearer token of the user |

#### Request Body

| Name                                    | Type   | Description                                                                                                                 |
| --------------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------- |
| token<mark style="color:red;">\*</mark> | String | Device identification token                                                                                                 |
| type<mark style="color:red;">\*</mark>  | String | Device type, e.g. "Android" or "iOS"                                                                                        |
| data                                    | String | Optional data to be passed to to push notification. Can be e.g. authentication token.                                       |
| params                                  | String | Optional parameters used internally by Ruuvi Cloud when delivering notifications. Currently unused,this is for future needs |
| name                                    | String | Human-readable device name, e.g. "Otso's mobile phone". Defaults to device type.                                            |

{% tabs %}
{% tab title="200: OK Token was successfully registered" %}

```javascript
{
    "result": "success",
    "data": {
        "tokenId": INT
    }
}
```

{% endtab %}

{% tab title="400: Bad Request Request had malformed data" %}

```javascript
{
    // Response
}
```

{% endtab %}

{% tab title="401: Unauthorized Request did not have authentication" %}

```javascript
{
    // Response
}
```

{% endtab %}

{% tab title="403: Forbidden Authentication was invalid" %}

```javascript
{
    // Response
}
```

{% endtab %}

{% tab title="500: Internal Server Error Internal problem. Try again later. " %}

```javascript
{
    // Response
}
```

{% endtab %}
{% endtabs %}

## Remove a push notification token for user

<mark style="color:green;">`POST`</mark> `https://network.ruuvi.com/push-unregister`

Removes given token from user, e.g. when signing off from the app. This does not require authentication to ensure that a device can always unregister itself.

Either full token or Token ID must be given, but both are optional. If both arguments are given, either can be processed but not both in one request.

#### Request Body

| Name  | Type   | Description                            |
| ----- | ------ | -------------------------------------- |
| token | String | Device identification token            |
| id    | String | Token ID received in listing of tokens |

{% tabs %}
{% tab title="200: OK Token was removed" %}

```javascript
{
    // Response
}
```

{% endtab %}

{% tab title="400: Bad Request Message body did not have required data" %}

```javascript
{
    // Response
}
```

{% endtab %}

{% tab title="500: Internal Server Error Internal problem. Try again later. " %}

```javascript
{
    // Response
}
```

{% endtab %}

{% tab title="404: Not Found Given token or ID was not found." %}

```javascript
{
    // Response
}
```

{% endtab %}
{% endtabs %}

## Get a list of tokens associated with user account

<mark style="color:blue;">`GET`</mark> `https://network.ruuvi.com/push-list`

List all tokens of user. Returns a listing of tokenId - name pairs.

#### Headers

| Name                                            | Type   | Description              |
| ----------------------------------------------- | ------ | ------------------------ |
| Authorization<mark style="color:red;">\*</mark> | String | Bearer token of the user |

{% tabs %}
{% tab title="200: OK List was successfully retrieved" %}

```javascript
{
    "result": "success",
    "data": {
        "tokens": [
            {
                "id": 3308157406,
                "lastAccessed": 1674799893,
                "name": "Otso's landline"
            },
            {
                "id": 3721015809,
                "lastAccessed": 1674799924,
                "name": "Otso's mobile phone"
            }
        ]
    }
}
```

{% endtab %}

{% tab title="401: Unauthorized Authentication token was missing" %}

```javascript
{
    // Response
}
```

{% endtab %}

{% tab title="403: Forbidden Authentication token is invalid" %}

```javascript
{
    // Response
}
```

{% endtab %}

{% tab title="500: Internal Server Error Iinternal problem. Try again later. " %}

```javascript
{
    // Response
}
```

{% endtab %}
{% endtabs %}


# Air Firmware

Ruuvi Air firmware update information

## Get Ruuvi Air firmware update information

> Returns latest, beta, alpha and historical firmware versions for Ruuvi Air. No authentication required. Final path is formed by joining the "url" field with the "fileName" field from the response,  i.e. ${url}/${fileName}.\
> Generally only ruuvi\_air\_fw\.signed.bin is needed, other files are restoring production firmware after a  factory reset or updating the bootloader / firmware installer. Check Ruuvi Air developer documentation for details.\
> Versions postfixed with +dev are development versions and signed with public development keys.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Air-Firmware","description":"Ruuvi Air firmware update information"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[],"paths":{"/air_firmwareupdate":{"get":{"summary":"Get Ruuvi Air firmware update information","description":"Returns latest, beta, alpha and historical firmware versions for Ruuvi Air. No authentication required. Final path is formed by joining the \"url\" field with the \"fileName\" field from the response,  i.e. ${url}/${fileName}.\nGenerally only ruuvi_air_fw.signed.bin is needed, other files are restoring production firmware after a  factory reset or updating the bootloader / firmware installer. Check Ruuvi Air developer documentation for details.\nVersions postfixed with +dev are development versions and signed with public development keys.\n","operationId":"getAirFirmwareUpdate","tags":["Air-Firmware"],"responses":{"200":{"description":"Firmware update information","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AirFirmwareResponse"}}}}}}}},"components":{"schemas":{"AirFirmwareResponse":{"type":"object","properties":{"result":{"type":"string"},"data":{"type":"object","additionalProperties":{"type":"object","properties":{"version":{"type":"string"},"url":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"versionCode":{"type":"integer"},"fileName":{"type":"string"},"fwloader":{"type":"string"},"mcuboot_s1":{"type":"string"},"mcuboot":{"type":"string"}},"required":["version","url","created_at","versionCode","fileName"]}}}}}}}
```


# Alerts

Alert management for sensors

## Get alerts

> Fetches alerts for all sensors the user has access to or a single sensor if an optional parameter is provided.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Alerts","description":"Alert management for sensors"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"AlertsResponse":{"type":"object","required":["sensors"],"properties":{"sensors":{"type":"array","description":"List of sensors with their alerts","items":{"$ref":"#/components/schemas/SensorWithAlerts"}}}},"SensorWithAlerts":{"allOf":[{"$ref":"#/components/schemas/SensorBase"},{"type":"object","required":["subscription","alerts"],"properties":{"subscription":{"$ref":"#/components/schemas/Subscription"},"alerts":{"type":"array","description":"List of alerts configured for this sensor","items":{"$ref":"#/components/schemas/Alert"}}}}]},"SensorBase":{"type":"object","required":["owner","sensor","name","public","canShare","offsetTemperature","offsetHumidity","offsetPressure","customProfile","lastUpdated"],"properties":{"owner":{"type":"string","format":"email"},"sensor":{"type":"string","description":"MAC address of the sensor"},"name":{"type":"string"},"picture":{"type":"string","maxLength":2048},"public":{"type":"boolean"},"canShare":{"type":"boolean"},"offsetTemperature":{"type":"number","format":"float"},"offsetHumidity":{"type":"number","format":"float"},"offsetPressure":{"type":"number","format":"float"},"customProfile":{"type":"boolean"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the sensor or its profile was last updated (whichever is more recent).\n"}}},"Subscription":{"type":"object","required":["subscriptionName","maxClaims","maxShares","maxSharesPerSensor","maxHistoryDays","maxResolutionMinutes","emailAlertAllowed","pushAlertAllowed","telegramAlertAllowed","delayedAlertAllowed","pdfExportAllowed","offlineAlertAllowed","isActive","startTime","endTime","endAt","lastUpdated"],"properties":{"subscriptionName":{"type":"string"},"maxClaims":{"type":"integer"},"maxShares":{"type":"integer"},"maxSharesPerSensor":{"type":"integer"},"maxHistoryDays":{"type":"integer"},"maxResolutionMinutes":{"type":"integer"},"emailAlertAllowed":{"type":"boolean"},"pushAlertAllowed":{"type":"boolean"},"telegramAlertAllowed":{"type":"boolean"},"delayedAlertAllowed":{"type":"boolean"},"pdfExportAllowed":{"type":"boolean"},"offlineAlertAllowed":{"type":"boolean"},"isActive":{"type":"boolean"},"startTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription started."},"endTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription ends."},"endAt":{"type":"string","format":"date-time"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription was last updated. If missing, it is assumed to be the current time.\n"}}},"Alert":{"type":"object","required":["type","min","max","counter","delay","enabled","description","triggered","lastUpdated"],"properties":{"type":{"$ref":"#/components/schemas/AlertType"},"min":{"type":"number","format":"double","description":"Lower limit for the alert condition"},"max":{"type":"number","format":"double","description":"Upper limit for the alert condition"},"counter":{"type":"integer","description":"Movement counter threshold (relevant for movement type alerts)"},"delay":{"type":"integer","description":"Delay in minutes before triggering the alert"},"enabled":{"type":"boolean","description":"Whether the alert is active"},"description":{"type":"string","description":"User-provided description of the alert"},"triggered":{"type":"boolean","description":"Whether the alert has been triggered"},"triggeredAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the alert was triggered. '1970-01-01T00:00:00.000Z' indicates the alert has never been triggered"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the alert was last updated"}}},"AlertType":{"type":"string","enum":["temperature","pressure","humidity","humidityAbsolute","dewPoint","signal","movement","battery","offline","luminosity","voc","co2","sound","soundInstant","soundAverage","soundPeak","nox","pm10","pm25","pm40","pm100","aqi"],"description":"Type of alert condition"},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/alerts":{"get":{"summary":"Get alerts","operationId":"getAlerts","tags":["Alerts"],"description":"Fetches alerts for all sensors the user has access to or a single sensor if an optional parameter is provided.\n","parameters":[{"in":"query","name":"sensor","required":false,"description":"Optional filter to get alerts for a single sensor by its MAC address (e.g. C5:2A:E7:4D:CE:7F).\n","schema":{"type":"string","pattern":"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"}}],"responses":{"200":{"description":"Alerts fetched successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/AlertsResponse"}}}]}}}},"401":{"description":"UNAUTHORIZED - Auth token missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN - User does not have access to the requested sensor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Create and update Alerts

> Sets an alert on a sensor for a given metric. The alert condition is tested\
> against the absolute value received from the sensors in conjunction with the user-set\
> offsets for that particular sensor.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Alerts","description":"Alert management for sensors"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"AlertType":{"type":"string","enum":["temperature","pressure","humidity","humidityAbsolute","dewPoint","signal","movement","battery","offline","luminosity","voc","co2","sound","soundInstant","soundAverage","soundPeak","nox","pm10","pm25","pm40","pm100","aqi"],"description":"Type of alert condition"},"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"AlertActionResult":{"type":"object","required":["action"],"properties":{"action":{"type":"string","description":"Result of the alert operation"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/alerts":{"post":{"summary":"Create and update Alerts","description":"Sets an alert on a sensor for a given metric. The alert condition is tested\nagainst the absolute value received from the sensors in conjunction with the user-set\noffsets for that particular sensor.\n","operationId":"setAlert","tags":["Alerts"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sensor","type"],"properties":{"sensor":{"type":"string","description":"MAC address of the sensor (e.g. `C5:2A:E7:4D:CE:7F`)","pattern":"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"},"type":{"$ref":"#/components/schemas/AlertType"},"enabled":{"type":"boolean","description":"Whether the alert is active. If missing, defaults to false. If value was already existing, it will not be changed."},"min":{"type":"number","format":"double","description":"Lower limit for the alert condition. If missing, defaults to 0. If value was already existing, it will not be changed."},"max":{"type":"number","format":"double","description":"Upper limit for the alert condition. If missing, defaults to 0. If value was already existing, it will not be changed."},"counter":{"type":"integer","description":"Movement counter threshold (relevant for movement type alerts). If missing, defaults to 0. If value was already existing, it will not be changed."},"description":{"type":"string","description":"User-provided description of the alert. If missing, defaults to empty string. If value was already existing, it will not be changed."},"delay":{"type":"integer","description":"Delay in minutes before triggering the alert. If missing, defaults to 0. If value was already existing, it will not be changed."},"timestamp":{"type":"integer","format":"int64","description":"Optional epoch timestamp for the alert. If missing, it is assumed to be the current time."}}}}}},"responses":{"200":{"description":"Alert stored or updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/AlertActionResult"}}}]}}}},"400":{"description":"BAD REQUEST - Missing or malformed fields   (`sensor`, `type`, or `enabled` missing; sensor not MAC address; type not valid)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"UNAUTHORIZED - Auth token missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN - User does not have permission to set alerts for this sensor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONFLICT - Alert with same or newer timestamp already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Register User

User registration and verification

## Register a new user or create new access token for existing user

> Requests a verification code to be emailed.  The verification code is then entered to /verify to get the access token. To protect against spam, this endpoint is rate limited.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Register-User","description":"User registration and verification"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[],"paths":{"/register":{"post":{"summary":"Register a new user or create new access token for existing user","description":"Requests a verification code to be emailed.  The verification code is then entered to /verify to get the access token. To protect against spam, this endpoint is rate limited.\n","operationId":"registerUser","tags":["Register-User"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"Verification code emailed successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/registerResult"}}}]}}}},"400":{"description":"INVALID - Missing or malformed fields   Email missing or not valid format\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests - Rate limit exceeded Rate limits apply per email address, IP address and total.  Try again later.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"registerResult":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Email registered"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}}}
```

## Verify a user's email with a verification code to get an access token

> Verify a user's email with a verification code to get an access token.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Register-User","description":"User registration and verification"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[],"paths":{"/verify":{"get":{"summary":"Verify a user's email with a verification code to get an access token","description":"Verify a user's email with a verification code to get an access token.\n","operationId":"verifyUser","tags":["Register-User"],"parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Verification code successful","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/verifyResult"}}}]}}}},"400":{"description":"INVALID - Missing or malformed fields   Code missing\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Forbidden - Verification code invalid or expired\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests - Rate limit exceeded Rate limits apply per IP address and total.  Try again later.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"verifyResult":{"type":"object","required":["email","accessToken","newUser"],"properties":{"email":{"type":"string","format":"email","description":"Email verified"},"accessToken":{"type":"string","description":"Access token for the verified user"},"newUser":{"type":"boolean","description":"Indicates if the user is new"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}}}
```


# Sensor Settings

Sensor settings management

## Get settings for sensor

> Returns settings for all sensors owned by the authenticated user. Optionally filter by a single sensor MAC address using the \`sensor\` query parameter.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Sensor-Settings","description":"Sensor settings management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"SettingGetResult":{"type":"object","required":["sensors"],"properties":{"sensors":{"type":"array","items":{"$ref":"#/components/schemas/Settings"}}}},"Settings":{"type":"object","allOf":[{"$ref":"#/components/schemas/SensorBase"},{"type":"object","required":["subscription","settings"],"properties":{"subscription":{"$ref":"#/components/schemas/Subscription"},"settings":{"$ref":"#/components/schemas/SettingObject"}}}]},"SensorBase":{"type":"object","required":["owner","sensor","name","public","canShare","offsetTemperature","offsetHumidity","offsetPressure","customProfile","lastUpdated"],"properties":{"owner":{"type":"string","format":"email"},"sensor":{"type":"string","description":"MAC address of the sensor"},"name":{"type":"string"},"picture":{"type":"string","maxLength":2048},"public":{"type":"boolean"},"canShare":{"type":"boolean"},"offsetTemperature":{"type":"number","format":"float"},"offsetHumidity":{"type":"number","format":"float"},"offsetPressure":{"type":"number","format":"float"},"customProfile":{"type":"boolean"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the sensor or its profile was last updated (whichever is more recent).\n"}}},"Subscription":{"type":"object","required":["subscriptionName","maxClaims","maxShares","maxSharesPerSensor","maxHistoryDays","maxResolutionMinutes","emailAlertAllowed","pushAlertAllowed","telegramAlertAllowed","delayedAlertAllowed","pdfExportAllowed","offlineAlertAllowed","isActive","startTime","endTime","endAt","lastUpdated"],"properties":{"subscriptionName":{"type":"string"},"maxClaims":{"type":"integer"},"maxShares":{"type":"integer"},"maxSharesPerSensor":{"type":"integer"},"maxHistoryDays":{"type":"integer"},"maxResolutionMinutes":{"type":"integer"},"emailAlertAllowed":{"type":"boolean"},"pushAlertAllowed":{"type":"boolean"},"telegramAlertAllowed":{"type":"boolean"},"delayedAlertAllowed":{"type":"boolean"},"pdfExportAllowed":{"type":"boolean"},"offlineAlertAllowed":{"type":"boolean"},"isActive":{"type":"boolean"},"startTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription started."},"endTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription ends."},"endAt":{"type":"string","format":"date-time"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription was last updated. If missing, it is assumed to be the current time.\n"}}},"SettingObject":{"type":"object","description":"Sensor settings as flattened key-value pairs.\n- Setting values are stored with keys matching the setting type name.\n- Each setting has a corresponding `{settingName}_lastUpdated` field containing an epoch timestamp.\n- Up to 500 setting pairs (1000 total properties including timestamps).\n- Setting values are strings (max 1000 chars), timestamp fields are integers (epoch seconds).\n\n\n**Property patterns:**\n- Pattern `^(?!.*_lastUpdated$).+$`: Setting value\n- Pattern `^.+_lastUpdated$`: Epoch timestamp in seconds for when the setting was last updated","maxProperties":1000,"additionalProperties":{"oneOf":[{"type":"string","maxLength":1000,"description":"Setting value (matches pattern: ^(?!.*_lastUpdated$).+$)"},{"type":"integer","format":"int64","description":"Epoch timestamp in seconds for when the setting was last updated (matches pattern: ^.+_lastUpdated$)"}]}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/sensor-settings":{"get":{"summary":"Get settings for sensor","operationId":"getSensorSetting","tags":["Sensor-Settings"],"description":"Returns settings for all sensors owned by the authenticated user. Optionally filter by a single sensor MAC address using the `sensor` query parameter.\n","parameters":[{"in":"query","name":"sensor","required":false,"description":"Filter results to a single sensor by its MAC address (e.g. C6:F1:E7:D3:DA:11).\n","schema":{"type":"string","pattern":"^[0-9A-F]{2}(:[0-9A-F]{2}){5}$"}}],"responses":{"200":{"description":"Sensor settings fetched successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","required":["data"],"properties":{"data":{"$ref":"#/components/schemas/SettingGetResult"}}}]}}}},"401":{"description":"UNAUTHORIZED - Auth token missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN - User does not have access to this sensor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT FOUND - Sensor does not exist, or it exists but user does not have permission to view this sensor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Configure a setting for a sensor

> Creates a new setting for given sensor

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Sensor-Settings","description":"Sensor settings management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"SettingSetResult":{"type":"object","required":["action"],"properties":{"action":{"type":"string","description":"Outcome of the set setting operation","enum":["added","updated"]}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/sensor-settings":{"post":{"summary":"Configure a setting for a sensor","description":"Creates a new setting for given sensor","operationId":"setSensorSetting","tags":["Sensor-Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sensor","value","type"],"properties":{"sensor":{"type":"string","description":"MAC address of the sensor (e.g. `C6:F1:E7:D3:DA:11`)","pattern":"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"},"value":{"type":"array","description":"Array of strings\nExample: `[\"\\\"CuriousSetting\\\"\"]`\n","items":{"type":"string"}},"type":{"type":"array","description":"Array of setting types (strings)","items":{"type":"string"}},"timestamp":{"type":"integer","format":"int64","description":"Optional epoch timestamp for the setting. If missing, it is assumed to be the current time."}}}}}},"responses":{"200":{"description":"Setting stored or updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SettingSetResult"}}}]}}}},"400":{"description":"INVALID - Missing or malformed fields   (`sensor`, `value`, or `type` missing; sensor not MAC; value not JSON array; type not array)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"UNAUTHORIZED - Auth token missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN - User does not have permission to edit this sensor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT FOUND - Sensor does not exist, or it exists but user does not have permission to view this sensor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONFLICT - Setting with same or newer timestamp already exists","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Sessions

Authenticated session management

## List current sessions

> Returns the authenticated user's active sessions.\
> \
> Sessions are ordered by most recent access first.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Sessions","description":"Authenticated session management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"Session":{"type":"object","required":["id","createdAt","lastAccessed","current"],"properties":{"id":{"type":"integer","format":"int64","description":"Unique identifier of the active session."},"createdAt":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the session was created."},"lastAccessed":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the session was last used."},"current":{"type":"boolean","description":"Whether this session authorized the current request."}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/sessions":{"get":{"summary":"List current sessions","description":"Returns the authenticated user's active sessions.\n\nSessions are ordered by most recent access first.\n","operationId":"getSessions","tags":["Sessions"],"responses":{"200":{"description":"Active sessions retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["sessions"],"properties":{"sessions":{"type":"array","items":{"$ref":"#/components/schemas/Session"}}}}}}]}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Delete all current sessions

> Deletes all active sessions for the authenticated user, including the session\
> used to authorize this request.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Sessions","description":"Authenticated session management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/sessions":{"delete":{"summary":"Delete all current sessions","description":"Deletes all active sessions for the authenticated user, including the session\nused to authorize this request.\n","operationId":"deleteSessions","tags":["Sessions"],"responses":{"200":{"description":"All active sessions deleted successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","additionalProperties":false}}}]}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Delete the current session

> Deletes the session used to authorize this request.

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Sessions","description":"Authenticated session management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/sessions/current":{"delete":{"summary":"Delete the current session","description":"Deletes the session used to authorize this request.","operationId":"deleteCurrentSession","tags":["Sessions"],"responses":{"200":{"description":"Current session deleted successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","additionalProperties":false}}}]}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Delete a session

> Deletes a single active session by ID.

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Sessions","description":"Authenticated session management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/sessions/{id}":{"delete":{"summary":"Delete a session","description":"Deletes a single active session by ID.","operationId":"deleteSession","tags":["Sessions"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","format":"int64"},"description":"ID of the session to delete."}],"responses":{"200":{"description":"Session deleted successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","additionalProperties":false}}}]}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT FOUND - Session does not exist","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Push Notifications

Push notification token management

## Register a token for push notifications

> Registers a push notification token for the authenticated user.\
> The type of the token determines the handler used to send notifications.  Currently supported types are \`iOS\` and \`Android\`, but other values are allowed  for future compatibility.\
> Both Android and iOS handlers use params to define the language of the notifications, default "en". Supported languages as of 2025-11 are English (en), Finnish (fi), Swedish (sv), French (fr), Russian (ru), and German (de).\
> Rate limits apply per user account, IP address and total calls. <br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Push-Notifications","description":"Push notification token management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/push-register":{"post":{"summary":"Register a token for push notifications","description":"Registers a push notification token for the authenticated user.\nThe type of the token determines the handler used to send notifications.  Currently supported types are `iOS` and `Android`, but other values are allowed  for future compatibility.\nBoth Android and iOS handlers use params to define the language of the notifications, default \"en\". Supported languages as of 2025-11 are English (en), Finnish (fi), Swedish (sv), French (fr), Russian (ru), and German (de).\nRate limits apply per user account, IP address and total calls. \n","operationId":"pushRegister","tags":["Push-Notifications"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","type","name"],"properties":{"token":{"type":"string","description":"Push notification token, used to authenticate at the push notification services"},"type":{"type":"string","description":"Device platform type","enum":["iOS","Android"]},"name":{"type":"string","description":"Human-readable device name"},"data":{"type":"object","description":"Additional nested data for the device. Handler-specific.","additionalProperties":true},"params":{"type":"object","description":"Optional parameters for push notification behavior. Handler-specific.","additionalProperties":true}}}}}},"responses":{"200":{"description":"Push notification token registered successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["id","lastAccessed","name"],"properties":{"id":{"type":"integer","format":"int64","description":"Unique identifier for the registered push token"},"lastAccessed":{"type":"integer","format":"int64","description":"Unix timestamp of last access"},"name":{"type":"string","description":"Human-readable device name"}}}}}]}}}},"400":{"description":"INVALID - Missing or malformed fields   (`token`, `type`, or `name` missing; invalid type value)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"UNAUTHORIZED - Auth token missing","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN - User does not have permission to register push tokens","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests - Rate limit exceeded Rate limits apply per user account, IP address and total calls.  Try again later.\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Sensors

Sensor data and metadata retrieval

## Get sensors with calibration data, latest measurements, and alert settings

> Fetches the list of claimed and shared sensors with calibration data, sensor\
> last measurement, subscription type and alert settings.\
> \
> By default the endpoint returns only the claimed sensors with calibration\
> data. Optional query parameters must be passed to get shared sensors,\
> last measurements, and alert settings.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Sensors","description":"Sensor data and metadata retrieval"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"SensorBase":{"type":"object","required":["owner","sensor","name","public","canShare","offsetTemperature","offsetHumidity","offsetPressure","customProfile","lastUpdated"],"properties":{"owner":{"type":"string","format":"email"},"sensor":{"type":"string","description":"MAC address of the sensor"},"name":{"type":"string"},"picture":{"type":"string","maxLength":2048},"public":{"type":"boolean"},"canShare":{"type":"boolean"},"offsetTemperature":{"type":"number","format":"float"},"offsetHumidity":{"type":"number","format":"float"},"offsetPressure":{"type":"number","format":"float"},"customProfile":{"type":"boolean"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the sensor or its profile was last updated (whichever is more recent).\n"}}},"SensorMeasurement":{"type":"object","required":["gwmac","coordinates","rssi","timestamp","data"],"properties":{"gwmac":{"type":"string","description":"MAC address of the gateway that received this measurement","pattern":"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"},"coordinates":{"type":"string","description":"GPS coordinates of the gateway, or \"N/A\" if unavailable"},"rssi":{"type":"integer","description":"Received Signal Strength Indicator in dBm"},"timestamp":{"type":"integer","format":"int64","description":"Unix epoch timestamp in seconds when the measurement was recorded"},"data":{"type":"string","description":"Hex-encoded raw sensor data payload"}}},"Subscription":{"type":"object","required":["subscriptionName","maxClaims","maxShares","maxSharesPerSensor","maxHistoryDays","maxResolutionMinutes","emailAlertAllowed","pushAlertAllowed","telegramAlertAllowed","delayedAlertAllowed","pdfExportAllowed","offlineAlertAllowed","isActive","startTime","endTime","endAt","lastUpdated"],"properties":{"subscriptionName":{"type":"string"},"maxClaims":{"type":"integer"},"maxShares":{"type":"integer"},"maxSharesPerSensor":{"type":"integer"},"maxHistoryDays":{"type":"integer"},"maxResolutionMinutes":{"type":"integer"},"emailAlertAllowed":{"type":"boolean"},"pushAlertAllowed":{"type":"boolean"},"telegramAlertAllowed":{"type":"boolean"},"delayedAlertAllowed":{"type":"boolean"},"pdfExportAllowed":{"type":"boolean"},"offlineAlertAllowed":{"type":"boolean"},"isActive":{"type":"boolean"},"startTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription started."},"endTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription ends."},"endAt":{"type":"string","format":"date-time"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription was last updated. If missing, it is assumed to be the current time.\n"}}},"Alert":{"type":"object","required":["type","min","max","counter","delay","enabled","description","triggered","lastUpdated"],"properties":{"type":{"$ref":"#/components/schemas/AlertType"},"min":{"type":"number","format":"double","description":"Lower limit for the alert condition"},"max":{"type":"number","format":"double","description":"Upper limit for the alert condition"},"counter":{"type":"integer","description":"Movement counter threshold (relevant for movement type alerts)"},"delay":{"type":"integer","description":"Delay in minutes before triggering the alert"},"enabled":{"type":"boolean","description":"Whether the alert is active"},"description":{"type":"string","description":"User-provided description of the alert"},"triggered":{"type":"boolean","description":"Whether the alert has been triggered"},"triggeredAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the alert was triggered. '1970-01-01T00:00:00.000Z' indicates the alert has never been triggered"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the alert was last updated"}}},"AlertType":{"type":"string","enum":["temperature","pressure","humidity","humidityAbsolute","dewPoint","signal","movement","battery","offline","luminosity","voc","co2","sound","soundInstant","soundAverage","soundPeak","nox","pm10","pm25","pm40","pm100","aqi"],"description":"Type of alert condition"},"SettingObject":{"type":"object","description":"Sensor settings as flattened key-value pairs.\n- Setting values are stored with keys matching the setting type name.\n- Each setting has a corresponding `{settingName}_lastUpdated` field containing an epoch timestamp.\n- Up to 500 setting pairs (1000 total properties including timestamps).\n- Setting values are strings (max 1000 chars), timestamp fields are integers (epoch seconds).\n\n\n**Property patterns:**\n- Pattern `^(?!.*_lastUpdated$).+$`: Setting value\n- Pattern `^.+_lastUpdated$`: Epoch timestamp in seconds for when the setting was last updated","maxProperties":1000,"additionalProperties":{"oneOf":[{"type":"string","maxLength":1000,"description":"Setting value (matches pattern: ^(?!.*_lastUpdated$).+$)"},{"type":"integer","format":"int64","description":"Epoch timestamp in seconds for when the setting was last updated (matches pattern: ^.+_lastUpdated$)"}]}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/sensors-dense":{"get":{"summary":"Get sensors with calibration data, latest measurements, and alert settings","description":"Fetches the list of claimed and shared sensors with calibration data, sensor\nlast measurement, subscription type and alert settings.\n\nBy default the endpoint returns only the claimed sensors with calibration\ndata. Optional query parameters must be passed to get shared sensors,\nlast measurements, and alert settings.\n","operationId":"getSensorsDense","tags":["Sensors"],"parameters":[{"name":"sensor","in":"query","required":false,"schema":{"type":"string","pattern":"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"},"description":"Optionally filter results to a single sensor by MAC address"},{"name":"sharedToOthers","in":"query","required":false,"schema":{"type":"boolean"},"description":"If true, each sensor object includes the list of users it is shared to. Returns an empty list for sensors the caller does not own.\n"},{"name":"sharedToMe","in":"query","required":false,"schema":{"type":"boolean"},"description":"If true, sensors that are shared to the logged-in user are included in the response alongside sensors claimed by the user.\n"},{"name":"measurements","in":"query","required":false,"schema":{"type":"boolean"},"description":"If true, the latest measurement for each sensor is included. Also returns the active subscription details for each sensor.\n"},{"name":"alerts","in":"query","required":false,"schema":{"type":"boolean"},"description":"If true, the alert settings for each sensor are included.\n"},{"name":"settings","in":"query","required":false,"schema":{"type":"boolean"},"description":"If true, sensor-specific settings are included for each sensor.\n"},{"name":"mode","in":"query","required":false,"schema":{"type":"string","enum":["dense","sparse","mixed"],"default":"mixed"},"description":"Fetch mode for measurement data. `dense` returns highest data density for a limited time range. `sparse` returns downsampled data with no time-range limit. `mixed` returns all dense data available and fills the rest with sparse data. Default: `mixed`.\n"}],"responses":{"200":{"description":"Sensors retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["sensors"],"properties":{"sensors":{"type":"array","description":"List of sensors the caller has claimed, and optionally sensors shared to them","items":{"allOf":[{"$ref":"#/components/schemas/SensorBase"},{"type":"object","properties":{"owner":{"type":"string","format":"email","description":"Email address of the sensor owner. Partially masked when the sensor is public and the caller does not own it.\n"},"sharedTo":{"type":"array","description":"Email addresses of users this sensor is shared to. Present when `sharedToOthers=true`.\n","items":{"type":"string","format":"email"}},"measurements":{"type":"array","description":"Latest measurement(s) received from the sensor. Present when `measurements=true`.\n","items":{"$ref":"#/components/schemas/SensorMeasurement"}},"subscription":{"allOf":[{"$ref":"#/components/schemas/Subscription"},{"description":"Active subscription details for this sensor. Present when `measurements=true`.\n"}]},"alerts":{"type":"array","description":"Alert settings configured for this sensor. Present when `alerts=true`.\n","items":{"$ref":"#/components/schemas/Alert"}},"settings":{"allOf":[{"$ref":"#/components/schemas/SettingObject"},{"description":"Sensor-specific settings as flattened key-value pairs. Present when `settings=true`.\n"}]}}}]}}}}}}]}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN - Sensor exists but caller does not have access","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Share

Sensor sharing and unsharing

## Share a sensor with another user

> Shares your sensor data with another user via their e-mail address. \
> This grants them access to the sensor data via the get endpoint.\
> \
> A notification e-mail will be sent to the target user about the new share.\
> If the target user does not exist yet, an invitation to create an account \
> will be sent and they will gain access upon sign up.\
> \
> The features share receiver gets are based on your account type - if you have\
> a Pro account, the share receiver also gets Pro features for this sensor.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Share","description":"Sensor sharing and unsharing"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"ShareResult":{"type":"object","required":["sensor"],"properties":{"sensor":{"type":"string","description":"MAC address of the shared sensor","pattern":"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"},"invited":{"type":"boolean","description":"True if the target user did not exist and an invitation email was sent. False if the target user already has an account.\n"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/share":{"post":{"summary":"Share a sensor with another user","description":"Shares your sensor data with another user via their e-mail address. \nThis grants them access to the sensor data via the get endpoint.\n\nA notification e-mail will be sent to the target user about the new share.\nIf the target user does not exist yet, an invitation to create an account \nwill be sent and they will gain access upon sign up.\n\nThe features share receiver gets are based on your account type - if you have\na Pro account, the share receiver also gets Pro features for this sensor.\n","operationId":"shareSensor","tags":["Share"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sensor","user"],"properties":{"sensor":{"type":"string","description":"MAC address of the sensor to share (e.g. `C6:F1:E7:D3:DA:11`)","pattern":"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"},"user":{"type":"string","format":"email","description":"E-mail address of the user to share the sensor with"}}}}}},"responses":{"200":{"description":"Sensor shared successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/ShareResult"}}}]}}}},"400":{"description":"INVALID - Missing or malformed fields   (`sensor` or `user` missing; sensor not valid MAC address; user not valid email)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN - User does not own this sensor or cannot share it","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONFLICT - Sensor is already shared to this user","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"INTERNAL SERVER ERROR - Unexpected error occurred","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Unshare a sensor from a user

> Revokes access to the sensor from a target user.\
> \
> This can also be used to remove sensors that have been shared with your own user\
> (i.e., declining a share from someone else).<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Share","description":"Sensor sharing and unsharing"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/unshare":{"post":{"summary":"Unshare a sensor from a user","description":"Revokes access to the sensor from a target user.\n\nThis can also be used to remove sensors that have been shared with your own user\n(i.e., declining a share from someone else).\n","operationId":"unshareSensor","tags":["Share"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["sensor"],"properties":{"sensor":{"type":"string","description":"MAC address of the sensor to unshare (e.g. `C6:F1:E7:D3:DA:11`)","pattern":"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"},"user":{"type":"string","format":"email","description":"E-mail address of the user to revoke access from.\nIf omitted, the share is removed from the authenticated user's account\n(useful for declining a share someone else made to you).\n"}}}}}},"responses":{"200":{"description":"Sensor unshared successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","description":"Empty object on success"}}}]}}}},"400":{"description":"INVALID - Missing or malformed fields   (`sensor` missing or not valid MAC address)\n","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"FORBIDDEN - User does not have permission to unshare this sensor","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"NOT FOUND - Shared sensor or target user not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"INTERNAL SERVER ERROR - Unexpected error occurred","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Subscription

User subscription management

## Get subscription history

> Returns an array of JSON objects detailing the subscriptions the user has had.\
> The active subscription (if any) will have \`isActive: true\`.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Subscription","description":"User subscription management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"Subscription":{"type":"object","required":["subscriptionName","maxClaims","maxShares","maxSharesPerSensor","maxHistoryDays","maxResolutionMinutes","emailAlertAllowed","pushAlertAllowed","telegramAlertAllowed","delayedAlertAllowed","pdfExportAllowed","offlineAlertAllowed","isActive","startTime","endTime","endAt","lastUpdated"],"properties":{"subscriptionName":{"type":"string"},"maxClaims":{"type":"integer"},"maxShares":{"type":"integer"},"maxSharesPerSensor":{"type":"integer"},"maxHistoryDays":{"type":"integer"},"maxResolutionMinutes":{"type":"integer"},"emailAlertAllowed":{"type":"boolean"},"pushAlertAllowed":{"type":"boolean"},"telegramAlertAllowed":{"type":"boolean"},"delayedAlertAllowed":{"type":"boolean"},"pdfExportAllowed":{"type":"boolean"},"offlineAlertAllowed":{"type":"boolean"},"isActive":{"type":"boolean"},"startTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription started."},"endTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription ends."},"endAt":{"type":"string","format":"date-time"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription was last updated. If missing, it is assumed to be the current time.\n"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/subscription":{"get":{"summary":"Get subscription history","description":"Returns an array of JSON objects detailing the subscriptions the user has had.\nThe active subscription (if any) will have `isActive: true`.\n","operationId":"getSubscription","tags":["Subscription"],"responses":{"200":{"description":"Subscription history retrieved successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["subscriptions"],"properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}}}}}}]}}}},"400":{"description":"BAD REQUEST - Malformed request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Claim a subscription by a code

> Applies a new subscription to the authenticated user immediately.\
> Parameters are passed as JSON in the request body.\
> The success response contains full subscription history,\
> with the active subscription first in the \`subscriptions\` array.<br>

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"Subscription","description":"User subscription management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"Subscription":{"type":"object","required":["subscriptionName","maxClaims","maxShares","maxSharesPerSensor","maxHistoryDays","maxResolutionMinutes","emailAlertAllowed","pushAlertAllowed","telegramAlertAllowed","delayedAlertAllowed","pdfExportAllowed","offlineAlertAllowed","isActive","startTime","endTime","endAt","lastUpdated"],"properties":{"subscriptionName":{"type":"string"},"maxClaims":{"type":"integer"},"maxShares":{"type":"integer"},"maxSharesPerSensor":{"type":"integer"},"maxHistoryDays":{"type":"integer"},"maxResolutionMinutes":{"type":"integer"},"emailAlertAllowed":{"type":"boolean"},"pushAlertAllowed":{"type":"boolean"},"telegramAlertAllowed":{"type":"boolean"},"delayedAlertAllowed":{"type":"boolean"},"pdfExportAllowed":{"type":"boolean"},"offlineAlertAllowed":{"type":"boolean"},"isActive":{"type":"boolean"},"startTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription started."},"endTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription ends."},"endAt":{"type":"string","format":"date-time"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription was last updated. If missing, it is assumed to be the current time.\n"}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/subscription":{"post":{"summary":"Claim a subscription by a code","description":"Applies a new subscription to the authenticated user immediately.\nParameters are passed as JSON in the request body.\nThe success response contains full subscription history,\nwith the active subscription first in the `subscriptions` array.\n","operationId":"claimSubscriptionByCode","tags":["Subscription"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Subscription claim code"}}}}}},"responses":{"200":{"description":"Subscription was applied successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"type":"object","required":["subscriptions"],"properties":{"subscriptions":{"type":"array","items":{"$ref":"#/components/schemas/Subscription"}}}}}}]}}}},"400":{"description":"BAD REQUEST - Missing or malformed fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or invalid","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# User Settings

User settings management

## Get user settings

> Returns settings for the authenticated user.

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"User-Settings","description":"User settings management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"UserSettingObject":{"type":"object","description":"User settings as flattened key-value pairs.\n- Setting values are returned with uppercase setting names.\n- Each setting has a corresponding `{settingName}_lastUpdated` field containing an epoch timestamp.\n- Setting values are strings, timestamp fields are integers (epoch seconds).\n\n\n**Property patterns:**\n- Pattern `^(?!.*_lastUpdated$).+$`: Setting value\n- Pattern `^.+_lastUpdated$`: Epoch timestamp in seconds for when the setting was last updated","propertyNames":{"pattern":"^(?!.*_lastUpdated_lastUpdated$)(?!.*(?:^|_)[A-F0-9]{16,}(?:_|_lastUpdated$|$))(?!.*(?:^|_)\\d{8,}(?:_|_lastUpdated$|$))[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+(?:_lastUpdated)?$"},"additionalProperties":{"oneOf":[{"type":"string","maxLength":65535,"description":"Setting value (matches pattern: ^(?!.*_lastUpdated$).+$)"},{"type":"integer","format":"int64","description":"Epoch timestamp in seconds for when the setting was last updated (matches pattern: ^.+_lastUpdated$)"}]}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/settings":{"get":{"summary":"Get user settings","description":"Returns settings for the authenticated user.","operationId":"getUserSettings","tags":["User-Settings"],"responses":{"200":{"description":"User settings fetched successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","required":["data"],"properties":{"data":{"type":"object","required":["settings"],"properties":{"settings":{"$ref":"#/components/schemas/UserSettingObject"}}}}}]}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```

## Configure a user setting

> Creates or updates a setting for the authenticated user.

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"tags":[{"name":"User-Settings","description":"User settings management"}],"servers":[{"url":"https://network.ruuvi.com"},{"url":"https://testnet.ruuvi.com"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}},"SettingSetResult":{"type":"object","required":["action"],"properties":{"action":{"type":"string","description":"Outcome of the set setting operation","enum":["added","updated"]}}},"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}},"paths":{"/settings":{"post":{"summary":"Configure a user setting","description":"Creates or updates a setting for the authenticated user.","operationId":"setUserSetting","tags":["User-Settings"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","value"],"additionalProperties":false,"properties":{"name":{"type":"string","description":"User setting name in snake case, for example `UNIT_TEMPERATURE`, `profile_language_code`, or `settings_123`. Names are stored in uppercase.","maxLength":80,"pattern":"^(?!.*_[Ll][Aa][Ss][Tt][Uu][Pp][Dd][Aa][Tt][Ee][Dd]$)(?!.*(?:^|_)[A-Fa-f0-9]{16,}(?:_|$))(?!.*(?:^|_)\\d{8,}(?:_|$))[A-Za-z][A-Za-z0-9]*(?:_[A-Za-z0-9]+)+$"},"value":{"type":"string","description":"User setting value.","maxLength":65535},"timestamp":{"type":"integer","format":"int64","description":"Optional epoch timestamp for the setting. If missing, it is assumed to be the current time."}}}}}},"responses":{"200":{"description":"Setting stored or updated successfully","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/Success"},{"type":"object","properties":{"data":{"$ref":"#/components/schemas/SettingSetResult"}}}]}}}},"400":{"description":"INVALID - Missing or malformed fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"UNAUTHORIZED - Auth token missing or invalid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"CONFLICT - Setting with same or newer timestamp already exists.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}
```


# Models

## The AirFirmwareResponse object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"AirFirmwareResponse":{"type":"object","properties":{"result":{"type":"string"},"data":{"type":"object","additionalProperties":{"type":"object","properties":{"version":{"type":"string"},"url":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"versionCode":{"type":"integer"},"fileName":{"type":"string"},"fwloader":{"type":"string"},"mcuboot_s1":{"type":"string"},"mcuboot":{"type":"string"}},"required":["version","url","created_at","versionCode","fileName"]}}}}}}}
```

## The Success object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"Success":{"type":"object","required":["result","data"],"properties":{"result":{"type":"string","enum":["success"]},"data":{"description":"Endpoint-specific payload"}}}}}}
```

## The SensorBase object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"SensorBase":{"type":"object","required":["owner","sensor","name","public","canShare","offsetTemperature","offsetHumidity","offsetPressure","customProfile","lastUpdated"],"properties":{"owner":{"type":"string","format":"email"},"sensor":{"type":"string","description":"MAC address of the sensor"},"name":{"type":"string"},"picture":{"type":"string","maxLength":2048},"public":{"type":"boolean"},"canShare":{"type":"boolean"},"offsetTemperature":{"type":"number","format":"float"},"offsetHumidity":{"type":"number","format":"float"},"offsetPressure":{"type":"number","format":"float"},"customProfile":{"type":"boolean"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the sensor or its profile was last updated (whichever is more recent).\n"}}}}}}
```

## The Subscription object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"Subscription":{"type":"object","required":["subscriptionName","maxClaims","maxShares","maxSharesPerSensor","maxHistoryDays","maxResolutionMinutes","emailAlertAllowed","pushAlertAllowed","telegramAlertAllowed","delayedAlertAllowed","pdfExportAllowed","offlineAlertAllowed","isActive","startTime","endTime","endAt","lastUpdated"],"properties":{"subscriptionName":{"type":"string"},"maxClaims":{"type":"integer"},"maxShares":{"type":"integer"},"maxSharesPerSensor":{"type":"integer"},"maxHistoryDays":{"type":"integer"},"maxResolutionMinutes":{"type":"integer"},"emailAlertAllowed":{"type":"boolean"},"pushAlertAllowed":{"type":"boolean"},"telegramAlertAllowed":{"type":"boolean"},"delayedAlertAllowed":{"type":"boolean"},"pdfExportAllowed":{"type":"boolean"},"offlineAlertAllowed":{"type":"boolean"},"isActive":{"type":"boolean"},"startTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription started."},"endTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription ends."},"endAt":{"type":"string","format":"date-time"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription was last updated. If missing, it is assumed to be the current time.\n"}}}}}}
```

## The AlertType object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"AlertType":{"type":"string","enum":["temperature","pressure","humidity","humidityAbsolute","dewPoint","signal","movement","battery","offline","luminosity","voc","co2","sound","soundInstant","soundAverage","soundPeak","nox","pm10","pm25","pm40","pm100","aqi"],"description":"Type of alert condition"}}}}
```

## The Alert object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"Alert":{"type":"object","required":["type","min","max","counter","delay","enabled","description","triggered","lastUpdated"],"properties":{"type":{"$ref":"#/components/schemas/AlertType"},"min":{"type":"number","format":"double","description":"Lower limit for the alert condition"},"max":{"type":"number","format":"double","description":"Upper limit for the alert condition"},"counter":{"type":"integer","description":"Movement counter threshold (relevant for movement type alerts)"},"delay":{"type":"integer","description":"Delay in minutes before triggering the alert"},"enabled":{"type":"boolean","description":"Whether the alert is active"},"description":{"type":"string","description":"User-provided description of the alert"},"triggered":{"type":"boolean","description":"Whether the alert has been triggered"},"triggeredAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the alert was triggered. '1970-01-01T00:00:00.000Z' indicates the alert has never been triggered"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the alert was last updated"}}},"AlertType":{"type":"string","enum":["temperature","pressure","humidity","humidityAbsolute","dewPoint","signal","movement","battery","offline","luminosity","voc","co2","sound","soundInstant","soundAverage","soundPeak","nox","pm10","pm25","pm40","pm100","aqi"],"description":"Type of alert condition"}}}}
```

## The SensorWithAlerts object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"SensorWithAlerts":{"allOf":[{"$ref":"#/components/schemas/SensorBase"},{"type":"object","required":["subscription","alerts"],"properties":{"subscription":{"$ref":"#/components/schemas/Subscription"},"alerts":{"type":"array","description":"List of alerts configured for this sensor","items":{"$ref":"#/components/schemas/Alert"}}}}]},"SensorBase":{"type":"object","required":["owner","sensor","name","public","canShare","offsetTemperature","offsetHumidity","offsetPressure","customProfile","lastUpdated"],"properties":{"owner":{"type":"string","format":"email"},"sensor":{"type":"string","description":"MAC address of the sensor"},"name":{"type":"string"},"picture":{"type":"string","maxLength":2048},"public":{"type":"boolean"},"canShare":{"type":"boolean"},"offsetTemperature":{"type":"number","format":"float"},"offsetHumidity":{"type":"number","format":"float"},"offsetPressure":{"type":"number","format":"float"},"customProfile":{"type":"boolean"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the sensor or its profile was last updated (whichever is more recent).\n"}}},"Subscription":{"type":"object","required":["subscriptionName","maxClaims","maxShares","maxSharesPerSensor","maxHistoryDays","maxResolutionMinutes","emailAlertAllowed","pushAlertAllowed","telegramAlertAllowed","delayedAlertAllowed","pdfExportAllowed","offlineAlertAllowed","isActive","startTime","endTime","endAt","lastUpdated"],"properties":{"subscriptionName":{"type":"string"},"maxClaims":{"type":"integer"},"maxShares":{"type":"integer"},"maxSharesPerSensor":{"type":"integer"},"maxHistoryDays":{"type":"integer"},"maxResolutionMinutes":{"type":"integer"},"emailAlertAllowed":{"type":"boolean"},"pushAlertAllowed":{"type":"boolean"},"telegramAlertAllowed":{"type":"boolean"},"delayedAlertAllowed":{"type":"boolean"},"pdfExportAllowed":{"type":"boolean"},"offlineAlertAllowed":{"type":"boolean"},"isActive":{"type":"boolean"},"startTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription started."},"endTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription ends."},"endAt":{"type":"string","format":"date-time"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription was last updated. If missing, it is assumed to be the current time.\n"}}},"Alert":{"type":"object","required":["type","min","max","counter","delay","enabled","description","triggered","lastUpdated"],"properties":{"type":{"$ref":"#/components/schemas/AlertType"},"min":{"type":"number","format":"double","description":"Lower limit for the alert condition"},"max":{"type":"number","format":"double","description":"Upper limit for the alert condition"},"counter":{"type":"integer","description":"Movement counter threshold (relevant for movement type alerts)"},"delay":{"type":"integer","description":"Delay in minutes before triggering the alert"},"enabled":{"type":"boolean","description":"Whether the alert is active"},"description":{"type":"string","description":"User-provided description of the alert"},"triggered":{"type":"boolean","description":"Whether the alert has been triggered"},"triggeredAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the alert was triggered. '1970-01-01T00:00:00.000Z' indicates the alert has never been triggered"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the alert was last updated"}}},"AlertType":{"type":"string","enum":["temperature","pressure","humidity","humidityAbsolute","dewPoint","signal","movement","battery","offline","luminosity","voc","co2","sound","soundInstant","soundAverage","soundPeak","nox","pm10","pm25","pm40","pm100","aqi"],"description":"Type of alert condition"}}}}
```

## The AlertsResponse object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"AlertsResponse":{"type":"object","required":["sensors"],"properties":{"sensors":{"type":"array","description":"List of sensors with their alerts","items":{"$ref":"#/components/schemas/SensorWithAlerts"}}}},"SensorWithAlerts":{"allOf":[{"$ref":"#/components/schemas/SensorBase"},{"type":"object","required":["subscription","alerts"],"properties":{"subscription":{"$ref":"#/components/schemas/Subscription"},"alerts":{"type":"array","description":"List of alerts configured for this sensor","items":{"$ref":"#/components/schemas/Alert"}}}}]},"SensorBase":{"type":"object","required":["owner","sensor","name","public","canShare","offsetTemperature","offsetHumidity","offsetPressure","customProfile","lastUpdated"],"properties":{"owner":{"type":"string","format":"email"},"sensor":{"type":"string","description":"MAC address of the sensor"},"name":{"type":"string"},"picture":{"type":"string","maxLength":2048},"public":{"type":"boolean"},"canShare":{"type":"boolean"},"offsetTemperature":{"type":"number","format":"float"},"offsetHumidity":{"type":"number","format":"float"},"offsetPressure":{"type":"number","format":"float"},"customProfile":{"type":"boolean"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the sensor or its profile was last updated (whichever is more recent).\n"}}},"Subscription":{"type":"object","required":["subscriptionName","maxClaims","maxShares","maxSharesPerSensor","maxHistoryDays","maxResolutionMinutes","emailAlertAllowed","pushAlertAllowed","telegramAlertAllowed","delayedAlertAllowed","pdfExportAllowed","offlineAlertAllowed","isActive","startTime","endTime","endAt","lastUpdated"],"properties":{"subscriptionName":{"type":"string"},"maxClaims":{"type":"integer"},"maxShares":{"type":"integer"},"maxSharesPerSensor":{"type":"integer"},"maxHistoryDays":{"type":"integer"},"maxResolutionMinutes":{"type":"integer"},"emailAlertAllowed":{"type":"boolean"},"pushAlertAllowed":{"type":"boolean"},"telegramAlertAllowed":{"type":"boolean"},"delayedAlertAllowed":{"type":"boolean"},"pdfExportAllowed":{"type":"boolean"},"offlineAlertAllowed":{"type":"boolean"},"isActive":{"type":"boolean"},"startTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription started."},"endTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription ends."},"endAt":{"type":"string","format":"date-time"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription was last updated. If missing, it is assumed to be the current time.\n"}}},"Alert":{"type":"object","required":["type","min","max","counter","delay","enabled","description","triggered","lastUpdated"],"properties":{"type":{"$ref":"#/components/schemas/AlertType"},"min":{"type":"number","format":"double","description":"Lower limit for the alert condition"},"max":{"type":"number","format":"double","description":"Upper limit for the alert condition"},"counter":{"type":"integer","description":"Movement counter threshold (relevant for movement type alerts)"},"delay":{"type":"integer","description":"Delay in minutes before triggering the alert"},"enabled":{"type":"boolean","description":"Whether the alert is active"},"description":{"type":"string","description":"User-provided description of the alert"},"triggered":{"type":"boolean","description":"Whether the alert has been triggered"},"triggeredAt":{"type":"string","format":"date-time","description":"ISO 8601 timestamp when the alert was triggered. '1970-01-01T00:00:00.000Z' indicates the alert has never been triggered"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the alert was last updated"}}},"AlertType":{"type":"string","enum":["temperature","pressure","humidity","humidityAbsolute","dewPoint","signal","movement","battery","offline","luminosity","voc","co2","sound","soundInstant","soundAverage","soundPeak","nox","pm10","pm25","pm40","pm100","aqi"],"description":"Type of alert condition"}}}}
```

## The Error object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"Error":{"type":"object","required":["result","error","code"],"properties":{"result":{"type":"string","enum":["error"]},"error":{"type":"string","description":"Human-readable explanation"},"code":{"type":"string","description":"Machine-readable primary error code"},"sub_code":{"nullable":true,"type":"string","description":"Optional secondary code useful for client logic"}}}}}}
```

## The AlertActionResult object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"AlertActionResult":{"type":"object","required":["action"],"properties":{"action":{"type":"string","description":"Result of the alert operation"}}}}}}
```

## The registerResult object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"registerResult":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email","description":"Email registered"}}}}}}
```

## The SettingObject object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"SettingObject":{"type":"object","description":"Sensor settings as flattened key-value pairs.\n- Setting values are stored with keys matching the setting type name.\n- Each setting has a corresponding `{settingName}_lastUpdated` field containing an epoch timestamp.\n- Up to 500 setting pairs (1000 total properties including timestamps).\n- Setting values are strings (max 1000 chars), timestamp fields are integers (epoch seconds).\n\n\n**Property patterns:**\n- Pattern `^(?!.*_lastUpdated$).+$`: Setting value\n- Pattern `^.+_lastUpdated$`: Epoch timestamp in seconds for when the setting was last updated","maxProperties":1000,"additionalProperties":{"oneOf":[{"type":"string","maxLength":1000,"description":"Setting value (matches pattern: ^(?!.*_lastUpdated$).+$)"},{"type":"integer","format":"int64","description":"Epoch timestamp in seconds for when the setting was last updated (matches pattern: ^.+_lastUpdated$)"}]}}}}}
```

## The Settings object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"Settings":{"type":"object","allOf":[{"$ref":"#/components/schemas/SensorBase"},{"type":"object","required":["subscription","settings"],"properties":{"subscription":{"$ref":"#/components/schemas/Subscription"},"settings":{"$ref":"#/components/schemas/SettingObject"}}}]},"SensorBase":{"type":"object","required":["owner","sensor","name","public","canShare","offsetTemperature","offsetHumidity","offsetPressure","customProfile","lastUpdated"],"properties":{"owner":{"type":"string","format":"email"},"sensor":{"type":"string","description":"MAC address of the sensor"},"name":{"type":"string"},"picture":{"type":"string","maxLength":2048},"public":{"type":"boolean"},"canShare":{"type":"boolean"},"offsetTemperature":{"type":"number","format":"float"},"offsetHumidity":{"type":"number","format":"float"},"offsetPressure":{"type":"number","format":"float"},"customProfile":{"type":"boolean"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the sensor or its profile was last updated (whichever is more recent).\n"}}},"Subscription":{"type":"object","required":["subscriptionName","maxClaims","maxShares","maxSharesPerSensor","maxHistoryDays","maxResolutionMinutes","emailAlertAllowed","pushAlertAllowed","telegramAlertAllowed","delayedAlertAllowed","pdfExportAllowed","offlineAlertAllowed","isActive","startTime","endTime","endAt","lastUpdated"],"properties":{"subscriptionName":{"type":"string"},"maxClaims":{"type":"integer"},"maxShares":{"type":"integer"},"maxSharesPerSensor":{"type":"integer"},"maxHistoryDays":{"type":"integer"},"maxResolutionMinutes":{"type":"integer"},"emailAlertAllowed":{"type":"boolean"},"pushAlertAllowed":{"type":"boolean"},"telegramAlertAllowed":{"type":"boolean"},"delayedAlertAllowed":{"type":"boolean"},"pdfExportAllowed":{"type":"boolean"},"offlineAlertAllowed":{"type":"boolean"},"isActive":{"type":"boolean"},"startTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription started."},"endTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription ends."},"endAt":{"type":"string","format":"date-time"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription was last updated. If missing, it is assumed to be the current time.\n"}}},"SettingObject":{"type":"object","description":"Sensor settings as flattened key-value pairs.\n- Setting values are stored with keys matching the setting type name.\n- Each setting has a corresponding `{settingName}_lastUpdated` field containing an epoch timestamp.\n- Up to 500 setting pairs (1000 total properties including timestamps).\n- Setting values are strings (max 1000 chars), timestamp fields are integers (epoch seconds).\n\n\n**Property patterns:**\n- Pattern `^(?!.*_lastUpdated$).+$`: Setting value\n- Pattern `^.+_lastUpdated$`: Epoch timestamp in seconds for when the setting was last updated","maxProperties":1000,"additionalProperties":{"oneOf":[{"type":"string","maxLength":1000,"description":"Setting value (matches pattern: ^(?!.*_lastUpdated$).+$)"},{"type":"integer","format":"int64","description":"Epoch timestamp in seconds for when the setting was last updated (matches pattern: ^.+_lastUpdated$)"}]}}}}}
```

## The SettingGetResult object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"SettingGetResult":{"type":"object","required":["sensors"],"properties":{"sensors":{"type":"array","items":{"$ref":"#/components/schemas/Settings"}}}},"Settings":{"type":"object","allOf":[{"$ref":"#/components/schemas/SensorBase"},{"type":"object","required":["subscription","settings"],"properties":{"subscription":{"$ref":"#/components/schemas/Subscription"},"settings":{"$ref":"#/components/schemas/SettingObject"}}}]},"SensorBase":{"type":"object","required":["owner","sensor","name","public","canShare","offsetTemperature","offsetHumidity","offsetPressure","customProfile","lastUpdated"],"properties":{"owner":{"type":"string","format":"email"},"sensor":{"type":"string","description":"MAC address of the sensor"},"name":{"type":"string"},"picture":{"type":"string","maxLength":2048},"public":{"type":"boolean"},"canShare":{"type":"boolean"},"offsetTemperature":{"type":"number","format":"float"},"offsetHumidity":{"type":"number","format":"float"},"offsetPressure":{"type":"number","format":"float"},"customProfile":{"type":"boolean"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the sensor or its profile was last updated (whichever is more recent).\n"}}},"Subscription":{"type":"object","required":["subscriptionName","maxClaims","maxShares","maxSharesPerSensor","maxHistoryDays","maxResolutionMinutes","emailAlertAllowed","pushAlertAllowed","telegramAlertAllowed","delayedAlertAllowed","pdfExportAllowed","offlineAlertAllowed","isActive","startTime","endTime","endAt","lastUpdated"],"properties":{"subscriptionName":{"type":"string"},"maxClaims":{"type":"integer"},"maxShares":{"type":"integer"},"maxSharesPerSensor":{"type":"integer"},"maxHistoryDays":{"type":"integer"},"maxResolutionMinutes":{"type":"integer"},"emailAlertAllowed":{"type":"boolean"},"pushAlertAllowed":{"type":"boolean"},"telegramAlertAllowed":{"type":"boolean"},"delayedAlertAllowed":{"type":"boolean"},"pdfExportAllowed":{"type":"boolean"},"offlineAlertAllowed":{"type":"boolean"},"isActive":{"type":"boolean"},"startTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription started."},"endTime":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription ends."},"endAt":{"type":"string","format":"date-time"},"lastUpdated":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the subscription was last updated. If missing, it is assumed to be the current time.\n"}}},"SettingObject":{"type":"object","description":"Sensor settings as flattened key-value pairs.\n- Setting values are stored with keys matching the setting type name.\n- Each setting has a corresponding `{settingName}_lastUpdated` field containing an epoch timestamp.\n- Up to 500 setting pairs (1000 total properties including timestamps).\n- Setting values are strings (max 1000 chars), timestamp fields are integers (epoch seconds).\n\n\n**Property patterns:**\n- Pattern `^(?!.*_lastUpdated$).+$`: Setting value\n- Pattern `^.+_lastUpdated$`: Epoch timestamp in seconds for when the setting was last updated","maxProperties":1000,"additionalProperties":{"oneOf":[{"type":"string","maxLength":1000,"description":"Setting value (matches pattern: ^(?!.*_lastUpdated$).+$)"},{"type":"integer","format":"int64","description":"Epoch timestamp in seconds for when the setting was last updated (matches pattern: ^.+_lastUpdated$)"}]}}}}}
```

## The SettingSetResult object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"SettingSetResult":{"type":"object","required":["action"],"properties":{"action":{"type":"string","description":"Outcome of the set setting operation","enum":["added","updated"]}}}}}}
```

## The SensorMeasurement object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"SensorMeasurement":{"type":"object","required":["gwmac","coordinates","rssi","timestamp","data"],"properties":{"gwmac":{"type":"string","description":"MAC address of the gateway that received this measurement","pattern":"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"},"coordinates":{"type":"string","description":"GPS coordinates of the gateway, or \"N/A\" if unavailable"},"rssi":{"type":"integer","description":"Received Signal Strength Indicator in dBm"},"timestamp":{"type":"integer","format":"int64","description":"Unix epoch timestamp in seconds when the measurement was recorded"},"data":{"type":"string","description":"Hex-encoded raw sensor data payload"}}}}}}
```

## The Session object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"Session":{"type":"object","required":["id","createdAt","lastAccessed","current"],"properties":{"id":{"type":"integer","format":"int64","description":"Unique identifier of the active session."},"createdAt":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the session was created."},"lastAccessed":{"type":"integer","format":"int64","description":"Epoch timestamp in seconds when the session was last used."},"current":{"type":"boolean","description":"Whether this session authorized the current request."}}}}}}
```

## The ShareResult object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"ShareResult":{"type":"object","required":["sensor"],"properties":{"sensor":{"type":"string","description":"MAC address of the shared sensor","pattern":"^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"},"invited":{"type":"boolean","description":"True if the target user did not exist and an invitation email was sent. False if the target user already has an account.\n"}}}}}}
```

## The UserSettingObject object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"UserSettingObject":{"type":"object","description":"User settings as flattened key-value pairs.\n- Setting values are returned with uppercase setting names.\n- Each setting has a corresponding `{settingName}_lastUpdated` field containing an epoch timestamp.\n- Setting values are strings, timestamp fields are integers (epoch seconds).\n\n\n**Property patterns:**\n- Pattern `^(?!.*_lastUpdated$).+$`: Setting value\n- Pattern `^.+_lastUpdated$`: Epoch timestamp in seconds for when the setting was last updated","propertyNames":{"pattern":"^(?!.*_lastUpdated_lastUpdated$)(?!.*(?:^|_)[A-F0-9]{16,}(?:_|_lastUpdated$|$))(?!.*(?:^|_)\\d{8,}(?:_|_lastUpdated$|$))[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+(?:_lastUpdated)?$"},"additionalProperties":{"oneOf":[{"type":"string","maxLength":65535,"description":"Setting value (matches pattern: ^(?!.*_lastUpdated$).+$)"},{"type":"integer","format":"int64","description":"Epoch timestamp in seconds for when the setting was last updated (matches pattern: ^.+_lastUpdated$)"}]}}}}}
```

## The verifyResult object

```json
{"openapi":"3.0.3","info":{"title":"Ruuvi Cloud - User API","version":"1.0.0"},"components":{"schemas":{"verifyResult":{"type":"object","required":["email","accessToken","newUser"],"properties":{"email":{"type":"string","format":"email","description":"Email verified"},"accessToken":{"type":"string","description":"Access token for the verified user"},"newUser":{"type":"boolean","description":"Indicates if the user is new"}}}}}}
```


# Gateway API

API for the Ruuvi Gateway

Gateway API uses a JSON based API to facilitate communication between Ruuvi Gateways and the databases by validating and forwarding the data.

## Send sensor data

<mark style="color:green;">`POST`</mark> `https://api.placeholder.com/dev/record`

Sends a bulk of data to Ruuvi Network to be processed and stored.

#### Headers

| Name              | Type   | Description                                                  |
| ----------------- | ------ | ------------------------------------------------------------ |
| Ruuvi-HMAC-SHA256 | string | Signature for the payload, signed with device specific keys. |

#### Request Body

| Name | Type   | Description                                                                            |
| ---- | ------ | -------------------------------------------------------------------------------------- |
| data | object | Data object contains a formatted JSON blob of sensor data to be stored. Example below. |

{% tabs %}
{% tab title="200 " %}

```
```

{% endtab %}

{% tab title="403 In case of an invalid signature, you will receive a 403: Forbidden." %}

```
```

{% endtab %}
{% endtabs %}

Data payload consists of gateway information and an array of tag measurements as illustrated below.

```
{
	"data":	{
		"coordinates": "",
		"timestamp": <UNIX TIMESTAMP>,
		"gwmac":	"<GATEWAY MAC ADDRESS>",
		"tags":	{
			"<TAG ID 1>": {
				"rssi": <RSSI VALUE>,
				"timestamp": <MEASUREMENT TIMESTAMP>,
				"data":	"<HEX ENCODED DATA>"
			},
			"<TAG ID 2>": {
				"rssi": <RSSI VALUE>,
				"timestamp": <MEASUREMENT TIMESTAMP>,
				"data":	"<HEX ENCODED DATA>"
			},
			...
		}
	}
}
```

MAC address format: XX:XX:XX:XX:XX:XX (where XX is a hexadecimal digit in upper case).\
Example: "11:22:33:AA:BB:CC"\
MAC address is used in "\<GATEWAY MAC ADDRESS>" and in "\<TAG ID 1>", "\<TAG ID 2>", ...

The signature is a HMAC (hash-based message authentication code) which is calculated using sha256 algorithm from a combination of headers and the message body. Ruuvi network validates the signature against the whitelisted gateways (see: [Internal API](/communicate-with-ruuvi-cloud/cloud/internal-api#whitelist)).

Below is an example code for calculating the secret:

```javascript
// Secret information is only existent on the device and Ruuvi Network
// but is not a part of the payload.
const secret = deviceId + deviceAddr;

// Signature body consists of the secret, random nonce, timestamp and message body
// These need to match the corresponding headers sent to Ruuvi network:
//   x-ruuvi-timestamp
//   x-ruuvi-nonce
const nonce = 'RANDOMLY GENERATED STRING';
const timestamp = Date.now();
const signatureBody = secret + nonce + timestamp + messageBody;

// Signature can then be calculated using the 'crypto' library. The finalized
// signature will be passed in the header:
//   x-ruuvi-signature
const crypto = require('crypto');
const signature = crypto.createHmac('sha256', secret)
    .update(signatureBody)
    .digest('hex');
```




---

[Next Page](/llms-full.txt/1)

