Store and Forward

A device that loses its connection does not have to lose its measurements. Keep them on the device while the link is down, then send them afterwards with the times they were actually taken. The history fills in as if nothing had happened.

Nothing to enable. The platform already accepts a timestamp with every write. This page is about the part that lives in your firmware: buffering while offline, and replaying afterwards.

1Why it matters

Without buffering, an outage leaves a hole. Worse, the hole is invisible: the chart simply has no points between two timestamps, and nothing on the page says whether the sensor was quiet, the device was off, or the network was down.

With buffering, the same outage costs nothing. The readings arrive late but land in the right place, and the record is continuous — which matters when the data is not just a dashboard but a quality record, a billing input, or the evidence for what a machine did last Tuesday.

This is different from knowing that the device dropped. Connection state tells you the device went away. Store and forward is what stops the measurements from disappearing with it. They solve different halves of the same problem — see Device Online/Offline Detection for the other half.

2How it works

Every write to POST /api/data/write may carry an optional time field. When it is present and valid, the reading is stored at that time. When it is absent, the reading is stored at the moment it arrived.

That single field is the whole mechanism. A device that has been buffering simply sends its backlog one request at a time, each with the timestamp it recorded.

FieldMeaning
time When the reading was taken. Any format JavaScript's Date understands — an ISO 8601 string such as 2026-09-07T14:32:05Z is the safe choice. Use UTC. An unparseable value is not an error: the reading is stored with the arrival time instead, silently.
device_name The device id as created in the console. It must already exist — the API never creates devices, and an unknown name returns 404.
field + value A single reading.
data An array of { field, value } — several fields at the same timestamp. See the limitation below.
One timestamp per request. The time applies to the whole request. The data array sends many fields at one moment — it does not send many moments. To replay a backlog of ten readings you send ten requests, each with its own time.

3Sending a buffered reading

A normal write, and the same write with a timestamp:

// Live reading — stored at the moment it arrives
POST /api/data/write
x-api-key: YOUR_API_KEY

{
  "device_name": "gateway_1",
  "field": "temperature",
  "value": 21.4
}


// Buffered reading — stored where it belongs in the history
POST /api/data/write
x-api-key: YOUR_API_KEY

{
  "device_name": "gateway_1",
  "field": "temperature",
  "value": 21.4,
  "time": "2026-09-07T14:32:05Z"
}

Several fields recorded at the same instant travel together:

{
  "device_name": "gateway_1",
  "time": "2026-09-07T14:32:05Z",
  "data": [
    { "field": "temperature", "value": 21.4 },
    { "field": "humidity",    "value": 63   }
  ]
}

4The firmware side

The device needs three things: somewhere to keep readings, a rule for when to keep them, and a replay loop for when the link returns.

Where to keep them

On an ESP32, a file in SPIFFS or LittleFS is usually enough — one line per reading, timestamp first. A file survives a reset, which RAM does not, and a reset is exactly what tends to happen during a power problem. Decide a maximum size and drop the oldest lines when you reach it: a device that fills its flash during a long outage is worse than one that loses the oldest hour.

Getting the time right

Buffering is only useful if the device knows what time it is. Sync with NTP while the network is up and keep the value across the outage. A device with no clock should not buffer — it would replay readings with invented times, which is worse than a visible gap.

Never send a guessed timestamp. If the clock was not set, send the readings without time and accept that they land at arrival time — or discard them. A history that looks continuous but is wrong cannot be detected later by anyone.

Replaying

When the connection returns, send the oldest reading first and delete each line only after the server has answered 200. If a request fails, stop and try again later — do not skip ahead, or you will create a gap inside the replay itself.

while (buffer.hasLines() && wifiConnected) {
    Line line = buffer.peekOldest();

    int code = postReading(line.field, line.value, line.timestamp);

    if (code == 200) buffer.removeOldest();
    else break;                 // retry later, keep the order

    delay(150);                  // stay under the rate limit
}
Pace the replay. Writes are limited to 600 per minute per API key — ten per second. Going over returns 429 with a Retry-After header. A short delay between requests, as above, keeps a long backlog well inside the limit. Treat 429 like a failure: wait and resume from the same reading.

5Three things to know before you rely on it

The field must be historical

A field that is not marked as historical keeps only its latest value — each write replaces the previous one. Replaying a backlog into such a field does not build a history; it leaves you with whichever reading happened to be sent last, which during a replay is an old one. Open the field in the console and make sure history is enabled before you buffer anything for it.

MQTT does not carry a timestamp

Readings that arrive over MQTT are stored at the moment they arrive. There is no timestamp in the topic or the payload, so a backlog cannot be replayed that way. A device that uses MQTT for live values can still use POST /api/data/write for its backlog — the two paths write to the same fields, and mixing them is fine.

This is true of the Virtuino Cloud platform, and it is the opposite of what the standalone Virtuino IoT app does. That app is a separate product with its own local storage, and there the timestamp travels in the MQTT payload. If that is what you are building against, follow Store and Forward for the Virtuino IoT app instead — the two pages describe different systems and the settings do not transfer.

Very old data is still subject to retention

Stored readings are removed once they pass the retention window of your plan. Replaying data from an outage of hours or days is unaffected. Importing history that is older than your retention window is not useful: it will be cleaned up on the next pass.

For the full write API — authentication, every field, and the error responses — see the HTTP API documentation. For alerting when a device stops reporting at all, see Device Online/Offline Detection.