Complete reference for writing data from devices, reading history, and querying your Virtuino Cloud account.
https://api.virtuino.com/api/data
esp32/temperature. So the simplest body is just a field and a value:
{ "field": "esp32/temperature", "value": 23.5 }
When several fields share the same beginning, you can send it once as path: the platform joins it to each field name with a /, so {"path":"esp32","field":"temperature"} writes to exactly the same field as the line above.
Every request must include your API Key. You can pass it in any of the three ways below — choose whichever fits your client best.
Your API Key is found in Console → Keys & Sub Users → HTTP API Keys.
Sends one or more field values to the platform. If publish: true is set, the value is also forwarded to the MQTT broker automatically (requires Essential+ plan).
https://api.virtuino.com/api/data/write
x-api-key: YOUR_API_KEY Content-Type: application/json
{
"client_id": "esp32-kitchen",
"field": "esp32/temperature",
"value": 23.75
}
// field = the full field name, exactly as in Console → Fields
// client_id = optional; names this board in My Virtuino World
{
"success": true,
"count": 1,
"total": 1,
"field": "esp32/temperature",
"client_id": "esp32-kitchen"
}
https://api.virtuino.com/api/data/write
x-api-key: YOUR_API_KEY Content-Type: application/json
{
"client_id": "greenhouse-board",
"path": "greenhouse_01",
"data": [
{ "field": "temperature", "value": 24.5 },
{ "field": "humidity", "value": 65 },
{ "field": "soil", "value": 42 }
]
}
// path = optional common prefix, joined to each field with a /
// (greenhouse_01/temperature, greenhouse_01/humidity, ...)
// It may have several levels, e.g. "farm/greenhouse_01".
// Without path, write the full name in every field.
{
"success": true,
"count": 3,
"total": 3,
"fields": ["greenhouse_01/temperature", "greenhouse_01/humidity", "greenhouse_01/soil"],
"path": "greenhouse_01",
"client_id": "greenhouse-board"
}
https://api.virtuino.com/api/data/write
x-api-key: YOUR_API_KEY Content-Type: application/json
{
"path": "esp32",
"time": "2026-06-12T10:00:00Z", // ISO 8601 UTC
"data": [
{ "field": "temperature", "value": 22.1 },
{ "field": "humidity", "value": 58.4 }
]
}
{
"success": true,
"count": 2,
"total": 2,
"fields": ["esp32/temperature", "esp32/humidity"],
"path": "esp32"
}
https://api.virtuino.com/api/data/write
x-api-key: YOUR_API_KEY Content-Type: application/json
{
"path": "esp32",
"data": [
{ "field": "relay1", "value": 1, "publish": true },
{ "field": "relay2", "value": 0, "publish": true }
]
}
// publish:true → platform also publishes the value out over MQTT (e.g. esp32/relay1)
// Requires Essential+ plan (api_gateway feature)
{
"success": true,
"count": 2,
"total": 2,
"fields": ["esp32/relay1", "esp32/relay2"],
"path": "esp32"
}
{
"path": "esp32",
"data": [
{ "field": "temperature", "value": 23.5 },
{ "field": "nonexistent", "value": 99 }
]
}
{
"success": true,
"count": 1,
"total": 2,
"fields": ["esp32/temperature"],
"path": "esp32",
"skipped_fields": ["esp32/nonexistent"],
"note": "Some fields do not exist. Create them in the console first."
}
"publish": true to any field entry causes the platform to publish that value over MQTT automatically — no MQTT client needed on the device. See the MQTT documentation for topic format details.
Returns records for a single field. Put the field's full name in the URL, exactly as in Console → Fields — slashes included: /field/esp32/temperature.
https://api.virtuino.com/api/data/field/esp32/temperature
api_key = YOUR_API_KEY
limit = 100 // max 5000, default 100
Full URL Example
https://api.virtuino.com/api/data/field/esp32/temperature?api_key=YOUR_API_KEY&limit=100
{
"success": true,
"field": "esp32/temperature",
"count": 100,
"limit_applied": 100,
"data": [
{ "time": "2026-06-12T10:05:00Z", "value": "23.75", "source": "HTTP" },
// …
]
}
https://api.virtuino.com/api/data/field/esp32/temperature
api_key = YOUR_API_KEY latest = trueFull URL Example
https://api.virtuino.com/api/data/field/esp32/temperature?api_key=YOUR_API_KEY&latest=true
{
"success": true,
"field": "esp32/temperature",
"latest_entry": {
"time": "2026-06-12T10:05:00Z",
"value": "23.75",
"source": "HTTP"
}
}
https://api.virtuino.com/api/data/field/esp32/temperature
api_key = YOUR_API_KEY
from = 2026-06-12T00:00:00Z // ISO 8601 UTC
to = 2026-06-12T23:59:59Z
limit = 15
Full URL Example
https://api.virtuino.com/api/data/field/esp32/temperature?api_key=YOUR_API_KEY&from=2026-06-12T00:00:00Z&to=2026-06-12T23:59:59Z&limit=15
{
"success": true,
"field": "esp32/temperature",
"count": 15,
"limit_applied": 15,
"data": [ /* … */ ]
}
404 with "error": "Field not found…". Check the name in Console → Fields; upper and lower case matter.
Returns every field whose name starts with {path}/ — for example all esp32/… fields — in one request. The path may have several levels: /path-history/farm/greenhouse_01.
https://api.virtuino.com/api/data/path-history/esp32
api_key = YOUR_API_KEY latest = trueFull URL Example
https://api.virtuino.com/api/data/path-history/esp32?api_key=YOUR_API_KEY&latest=true
{
"success": true,
"path": "esp32",
"fields": [
{ "field": "esp32/humidity", "value": "61.2", "time": "2026-06-12T10:04:45Z", "source": "HTTP" },
{ "field": "esp32/temperature", "value": "23.75", "time": "2026-06-12T10:05:00Z", "source": "HTTP" }
]
}
// Each field comes with its own latest value and time, even if they were written at different moments.
https://api.virtuino.com/api/data/path-history/esp32
api_key = YOUR_API_KEY limit = 50 // max 5000, default 100 from = 2026-06-12T00:00:00Z // optional to = 2026-06-12T23:59:59Z // optionalFull URL Example
https://api.virtuino.com/api/data/path-history/esp32?api_key=YOUR_API_KEY&limit=50&from=2026-06-12T00:00:00Z&to=2026-06-12T23:59:59Z
{
"success": true,
"path": "esp32",
"count": 50,
"limit_applied": 50,
"history": [
{
"time": "2026-06-12T10:05:00Z",
"source": "HTTP",
"fields": [
{ "field": "esp32/humidity", "value": "61.2" },
{ "field": "esp32/temperature", "value": "23.75" }
]
},
// … more entries
]
}
Returns a flat list of the latest records across all fields of the account, newest first. Each row has the full field name, value, time and source.
https://api.virtuino.com/api/data/history
api_key = YOUR_API_KEY
limit = 50 // default 50, max 5000
Full URL Example
https://api.virtuino.com/api/data/history?api_key=YOUR_API_KEY&limit=50
{
"success": true,
"count": 50,
"limit_applied": 50,
"data": [
{
"time": "2026-06-12T10:05:00Z",
"field": "esp32/temperature",
"value": "23.75",
"source": "HTTP"
},
// …
]
}
https://api.virtuino.com/api/data/history
api_key = YOUR_API_KEY from = 2026-06-12T00:00:00Z to = 2026-06-12T23:59:59Z limit = 100Full URL Example
https://api.virtuino.com/api/data/history?api_key=YOUR_API_KEY&from=2026-06-12T00:00:00Z&to=2026-06-12T23:59:59Z&limit=100
{
"success": true,
"count": 100,
"limit_applied": 100,
"data": [ /* … */ ]
}
Lists the fields of your account with their latest value. Use it to discover which fields exist, or add path to list only the fields under it.
https://api.virtuino.com/api/data/fields
api_key = YOUR_API_KEYFull URL Example
https://api.virtuino.com/api/data/fields?api_key=YOUR_API_KEY
{
"success": true,
"count": 3,
"fields": [
{
"field": "esp32/humidity",
"description": "",
"historical": true,
"last_value": "61.2",
"last_time": "2026-06-12T10:04:45Z"
},
// … one entry per field, sorted by name
]
}
https://api.virtuino.com/api/data/fields
api_key = YOUR_API_KEY path = esp32Full URL Example
https://api.virtuino.com/api/data/fields?api_key=YOUR_API_KEY&path=esp32
{
"success": true,
"path": "esp32",
"count": 2,
"fields": [ /* esp32/humidity, esp32/temperature */ ]
}
Full working examples using the raw HTTP API directly — no external library required. Each example writes two fields and reads back the latest snapshot.
HTTPClient and ArduinoJson: read a relay, upload sensor values, control a motor from the dashboard, upload with a timestamp, read the last 100 values, and a read-and-write thermostat loop.
// Required libraries — install via Arduino IDE → Library Manager: // ArduinoJson (by Benoit Blanchon) // WiFi and HTTPClient are built-in for ESP32 — no installation needed. #include <WiFi.h> #include <HTTPClient.h> #include <ArduinoJson.h> // ── Configuration — replace with your own values ───────────────────── const char* ssid = "YOUR_WIFI_SSID"; const char* password = "YOUR_WIFI_PASSWORD"; const char* apiKey = "YOUR_API_KEY"; // Console → Keys & Sub Users const char* writeUrl = "https://api.virtuino.com/api/data/write"; const char* historyUrl = "https://api.virtuino.com/api/data/path-history/esp32?latest=true"; // historyUrl returns the latest value of every field whose name starts with "esp32/". // ── Send two sensor readings to the platform ────────────────────────── void writeData(float temp, float hum) { if (WiFi.status() != WL_CONNECTED) return; HTTPClient http; http.begin(writeUrl); http.addHeader("Content-Type", "application/json"); // Build JSON body — "path" is joined to each "field" with a / — together they must match a field name in the Console StaticJsonDocument<256> doc; doc["api_key"] = apiKey; doc["client_id"] = "esp32-01"; // optional: names this board in My Virtuino World doc["path"] = "esp32"; // esp32/temperature, esp32/humidity JsonArray data = doc.createNestedArray("data"); JsonObject f1 = data.createNestedObject(); f1["field"] = "temperature"; f1["value"] = temp; f1["publish"] = true; // publish:true also sends to MQTT JsonObject f2 = data.createNestedObject(); f2["field"] = "humidity"; f2["value"] = hum; f2["publish"] = true; String body; serializeJson(doc, body); int code = http.POST(body); Serial.println("Write → HTTP " + String(code) + " " + http.getString()); // 200 = success; the reply lists any field that does not exist http.end(); } // ── Read the latest value of every field under the path ─────────────── void readLatest() { if (WiFi.status() != WL_CONNECTED) return; HTTPClient http; http.begin(historyUrl); http.addHeader("x-api-key", apiKey); // pass API key as header for GET requests int code = http.GET(); String payload = http.getString(); http.end(); Serial.println("Read → HTTP " + String(code)); if (code != 200) { Serial.println("Error: " + payload); // print server error message return; } // Parse the JSON response StaticJsonDocument<1024> doc; DeserializationError err = deserializeJson(doc, payload); if (err) { Serial.println("JSON parse error: " + String(err.c_str())); return; } // Iterate the "fields" array and print each name/value pair // You can also read specific fields: doc["fields"][0]["value"].as<float>() JsonArray fields = doc["fields"]; for (JsonObject f : fields) { const char* name = f["field"]; const char* value = f["value"]; Serial.printf(" %s = %s\n", name, value); } } void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWi-Fi connected"); } void loop() { static unsigned long last = 0; if (millis() - last > 15000) { // send every 15 seconds last = millis(); writeData(random(200, 300) / 10.0f, random(400, 700) / 10.0f); readLatest(); } }
// Required libraries — install via Arduino IDE → Library Manager: // ArduinoJson (by Benoit Blanchon) // Board support: Arduino IDE → File → Preferences → Additional boards manager URLs: // https://arduino.esp8266.com/stable/package_esp8266com_index.json // Then: Tools → Board → Boards Manager → "esp8266 by ESP8266 Community" → install/update to version 3.x // setInsecure() requires ESP8266 core ≥ 2.5.0. If you get a compile error, update the board package. // ESP8266WiFi, ESP8266HTTPClient, and WiFiClientSecure are part of the ESP8266 core — no separate install needed. #include <ESP8266WiFi.h> #include <ESP8266HTTPClient.h> #include <WiFiClientSecure.h> // required for HTTPS on ESP8266 — ESP32 handles it natively #include <ArduinoJson.h> // ── Configuration — replace with your own values ───────────────────── const char* ssid = "YOUR_WIFI_SSID"; const char* password = "YOUR_WIFI_PASSWORD"; const char* apiKey = "YOUR_API_KEY"; // Console → Keys & Sub Users const char* writeUrl = "https://api.virtuino.com/api/data/write"; const char* historyUrl = "https://api.virtuino.com/api/data/path-history/esp8266?latest=true"; // historyUrl returns the latest value of every field whose name starts with "esp8266/". // ESP8266 needs a WiFiClientSecure object passed to http.begin() for HTTPS WiFiClientSecure client; // ── Send two sensor readings to the platform ────────────────────────── void writeData(float temp, float hum) { if (WiFi.status() != WL_CONNECTED) return; client.setInsecure(); // skip TLS certificate verification — simplest setup for IoT HTTPClient http; http.begin(client, writeUrl); // ESP8266 requires the client object; ESP32 does not http.addHeader("Content-Type", "application/json"); // Build JSON body — "path" is joined to each "field" with a / — together they must match a field name in the Console StaticJsonDocument<256> doc; doc["api_key"] = apiKey; doc["client_id"] = "esp8266-01"; // optional: names this board in My Virtuino World doc["path"] = "esp8266"; // esp8266/temperature, esp8266/humidity JsonArray data = doc.createNestedArray("data"); JsonObject f1 = data.createNestedObject(); f1["field"] = "temperature"; f1["value"] = temp; f1["publish"] = true; // publish:true also sends to MQTT JsonObject f2 = data.createNestedObject(); f2["field"] = "humidity"; f2["value"] = hum; f2["publish"] = true; String body; serializeJson(doc, body); int code = http.POST(body); Serial.println("Write → HTTP " + String(code) + " " + http.getString()); // 200 = success; the reply lists any field that does not exist http.end(); } // ── Read the latest value of every field under the path ─────────────── void readLatest() { if (WiFi.status() != WL_CONNECTED) return; client.setInsecure(); HTTPClient http; http.begin(client, historyUrl); http.addHeader("x-api-key", apiKey); // pass API key as header for GET requests int code = http.GET(); String payload = http.getString(); http.end(); Serial.println("Read → HTTP " + String(code)); if (code != 200) { Serial.println("Error: " + payload); // print server error message return; } // Parse the JSON response StaticJsonDocument<1024> doc; DeserializationError err = deserializeJson(doc, payload); if (err) { Serial.println("JSON parse error: " + String(err.c_str())); return; } // Iterate the "fields" array and print each name/value pair JsonArray fields = doc["fields"]; for (JsonObject f : fields) { const char* name = f["field"]; const char* value = f["value"]; Serial.printf(" %s = %s\n", name, value); } } void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWi-Fi connected"); } void loop() { static unsigned long last = 0; if (millis() - last > 15000) { // send every 15 seconds last = millis(); writeData(random(200, 300) / 10.0f, random(400, 700) / 10.0f); readLatest(); } }
// Required libraries — install via Arduino IDE → Library Manager: // WiFiNINA (by Arduino) // ArduinoHttpClient (by Arduino) // ArduinoJson (by Benoit Blanchon) // Compatible boards: Arduino Uno WiFi Rev2, Arduino MKR WiFi 1010 #include <WiFiNINA.h> #include <ArduinoHttpClient.h> #include <ArduinoJson.h> // ── Configuration — replace with your own values ───────────────────── const char* ssid = "YOUR_WIFI_SSID"; const char* password = "YOUR_WIFI_PASSWORD"; const char* apiKey = "YOUR_API_KEY"; // Console → Keys & Sub Users const char* server = "api.virtuino.com"; // hostname only — port/path set below // WiFiSSLClient handles TLS; HttpClient wraps it with the hostname and port WiFiSSLClient wifiClient; HttpClient http(wifiClient, server, 443); // ── Send two sensor readings to the platform ────────────────────────── void writeData(float temp, float hum) { // Build JSON body — "path" is joined to each "field" with a / — together they must match a field name in the Console StaticJsonDocument<256> doc; doc["api_key"] = apiKey; doc["client_id"] = "uno-01"; // optional: names this board in My Virtuino World doc["path"] = "arduino_uno"; // arduino_uno/temperature, arduino_uno/humidity JsonArray data = doc.createNestedArray("data"); JsonObject f1 = data.createNestedObject(); f1["field"] = "temperature"; f1["value"] = temp; f1["publish"] = true; // publish:true also sends to MQTT JsonObject f2 = data.createNestedObject(); f2["field"] = "humidity"; f2["value"] = hum; f2["publish"] = true; String body; serializeJson(doc, body); // ArduinoHttpClient requires an explicit beginRequest/endRequest sequence http.beginRequest(); http.post("/api/data/write"); http.sendHeader("Content-Type", "application/json"); http.sendHeader("Content-Length", body.length()); http.endRequest(); http.print(body); int code = http.responseStatusCode(); String reply = http.responseBody(); // always read the reply before the next request Serial.println("Write → HTTP " + String(code) + " " + reply); // 200 = success; the reply lists any field that does not exist } // ── Read the latest value of every field under the path ─────────────── void readLatest() { // Simplest with ArduinoHttpClient: pass the API key as a query parameter String path = String("/api/data/path-history/arduino_uno?api_key=") + apiKey + "&latest=true"; http.get(path); int code = http.responseStatusCode(); String payload = http.responseBody(); Serial.println("Read → HTTP " + String(code)); if (code != 200) { Serial.println("Error: " + payload); return; } // Parse JSON and print each field/value pair StaticJsonDocument<1024> doc; if (deserializeJson(doc, payload)) { Serial.println("JSON parse error"); return; } JsonArray fields = doc["fields"]; for (JsonObject f : fields) { // Serial.printf does not exist on these boards Serial.print(" "); Serial.print((const char*)f["field"]); Serial.print(" = "); Serial.println((const char*)f["value"]); } } void setup() { Serial.begin(9600); while (!Serial); // wait for Serial Monitor to open (needed on Uno WiFi Rev2) while (WiFi.begin(ssid, password) != WL_CONNECTED) { delay(1000); } Serial.println("Wi-Fi connected"); } void loop() { static unsigned long last = 0; if (millis() - last > 15000) { // send every 15 seconds last = millis(); writeData(random(200, 300) / 10.0f, random(400, 700) / 10.0f); readLatest(); } }
# pip install requests import requests # ── Configuration — replace with your own values ────────────────────── API_KEY = "YOUR_API_KEY" # Console → Keys & Sub Users BASE_URL = "https://api.virtuino.com/api/data" PATH = "esp32" # joined to each field name: esp32/temperature, esp32/humidity # ── Write two fields ────────────────────────────────────────────────── # publish:True also pushes the value to the MQTT broker (Essential+ plan) r = requests.post(f"{BASE_URL}/write", json={ "api_key": API_KEY, "client_id": "python-script", # optional "path": PATH, "data": [ { "field": "temperature", "value": 23.75, "publish": True }, { "field": "humidity", "value": 61.2, "publish": True } ] }) print("Write:", r.json()) # {"success": true, "count": 2, "total": 2, "fields": ["esp32/temperature", "esp32/humidity"], ...} # ── Read the latest value of every field under the path ─────────────── # Returns {"success": true, "path": "esp32", "fields": [{"field": "esp32/temperature", "value": ..., "time": ...}, ...]} r = requests.get(f"{BASE_URL}/path-history/{PATH}", params={ "api_key": API_KEY, "latest": "true" }) print("Latest:", r.json()) # ── Read last 15 records for one field in a time range ──────────────── # Timestamps must be in ISO 8601 UTC format: YYYY-MM-DDTHH:MM:SSZ r = requests.get(f"{BASE_URL}/field/{PATH}/temperature", params={ "api_key": API_KEY, "from": "2026-06-12T00:00:00Z", "to": "2026-06-12T23:59:59Z", "limit": 15 }) print("History:", r.json())
// ── Configuration — replace with your own values ───────────────────── const API_KEY = "YOUR_API_KEY"; // Console → Keys & Sub Users const BASE_URL = "https://api.virtuino.com/api/data"; // ── Write two fields ────────────────────────────────────────────────── // api_key goes in the JSON body for POST requests async function writeData() { const res = await fetch(`${BASE_URL}/write`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ api_key: API_KEY, path: "esp32", // joined to each field name with a / data: [ { field: "temperature", value: 23.75, publish: true }, // publish:true also sends to MQTT { field: "humidity", value: 61.2, publish: true } ] }) }); const data = await res.json(); console.log("Write:", data); // {success: true, count: 2, total: 2, fields: ["esp32/temperature", "esp32/humidity"], ...} } // ── Read the latest value of every field under the path ─────────────── // For GET requests, pass the API key as a query parameter async function readLatest() { const res = await fetch(`${BASE_URL}/path-history/esp32?api_key=${API_KEY}&latest=true`); const data = await res.json(); console.log("Latest:", data); // Access individual fields: data.fields[0].value, data.fields[1].value, etc. } writeData(); readLatest();
# ── Write two fields ────────────────────────────────────────────────── # API key goes in the x-api-key header for POST; "path" + "/" + "field" must match a field name in the Console # publish:true also pushes the value to the MQTT broker (Essential+ plan) curl -X POST https://api.virtuino.com/api/data/write \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "path": "esp32", "data": [ {"field":"temperature","value":23.75,"publish":true}, {"field":"humidity","value":61.2,"publish":true} ] }' # ── Read the latest value of every field under the path ─────────────── # For GET requests, pass the API key as a query parameter curl "https://api.virtuino.com/api/data/path-history/esp32?api_key=YOUR_API_KEY&latest=true" # ── Read last 15 temperature records in a time range ───────────────── # Timestamps in ISO 8601 UTC: YYYY-MM-DDTHH:MM:SSZ curl "https://api.virtuino.com/api/data/field/esp32/temperature?api_key=YOUR_API_KEY&from=2026-06-12T00:00:00Z&to=2026-06-12T23:59:59Z&limit=15" # ── List all fields ────────────────────────────────────────────────── # Returns every field of the account with its latest value and time curl "https://api.virtuino.com/api/data/fields?api_key=YOUR_API_KEY"
All error responses follow the format { "success": false, "error": "…" }.