HTTP REST API Guide

Complete reference for writing data from devices, reading history, and querying your Virtuino Cloud account.

Base URL
All endpoints are relative to: https://api.virtuino.com/api/data
Fields must exist first. The API does not create fields automatically. Before sending data, create every field in Console → Fields. A field's name is its full topic, so the name you send must match it exactly.
A field's name is its full topic.
It is written in one piece — 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.
MethodEndpointDescription
POST/writeWrite one or more field values
GET/field/{field name}History or latest value of one field — full name, e.g. /field/esp32/temperature
GET/path-history/{path}Every field whose name starts with {path}/
GET/historyFlat history across all fields
GET/fieldsField list with each field's latest value (optionally ?path=)

1Authentication

Every request must include your API Key. You can pass it in any of the three ways below — choose whichever fits your client best.

MethodLocationExample
x-api-keyHTTP Headerx-api-key: YOUR_API_KEY
api_keyQuery string?api_key=YOUR_API_KEY
api_keyJSON body{ "api_key": "YOUR_API_KEY", … }

Your API Key is found in Console → Keys & Sub Users → HTTP API Keys.

Keep your API Key secret. Do not embed it in public source code or client-side JavaScript. Use a separate, read-only key for dashboards and a write-capable key only on trusted devices.

2Write Data   600 req / min per API key

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).

POST Write a single field value
URL
https://api.virtuino.com/api/data/write
Headers
x-api-key: YOUR_API_KEY
Content-Type: application/json
Body (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
Response
{
  "success":   true,
  "count":     1,
  "total":     1,
  "field":     "esp32/temperature",
  "client_id": "esp32-kitchen"
}
POST Write multiple fields in one request (data array)
URL
https://api.virtuino.com/api/data/write
Headers
x-api-key: YOUR_API_KEY
Content-Type: application/json
Body (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.
Response
{
  "success":   true,
  "count":     3,
  "total":     3,
  "fields":    ["greenhouse_01/temperature", "greenhouse_01/humidity", "greenhouse_01/soil"],
  "path":      "greenhouse_01",
  "client_id": "greenhouse-board"
}
POST Write with custom timestamp
URL
https://api.virtuino.com/api/data/write
Headers
x-api-key: YOUR_API_KEY
Content-Type: application/json
Body (JSON)
{
  "path": "esp32",
  "time": "2026-06-12T10:00:00Z",  // ISO 8601 UTC
  "data": [
    { "field": "temperature", "value": 22.1 },
    { "field": "humidity",    "value": 58.4 }
  ]
}
Response
{
  "success": true,
  "count":   2,
  "total":   2,
  "fields":  ["esp32/temperature", "esp32/humidity"],
  "path":    "esp32"
}
POST Write with MQTT publish (HTTP → MQTT Gateway)
URL
https://api.virtuino.com/api/data/write
Headers
x-api-key: YOUR_API_KEY
Content-Type: application/json
Body (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)
Response
{
  "success": true,
  "count":   2,
  "total":   2,
  "fields":  ["esp32/relay1", "esp32/relay2"],
  "path":    "esp32"
}
POST Response when a field does not exist in the Console
Body sent
{
  "path": "esp32",
  "data": [
    { "field": "temperature", "value": 23.5 },
    { "field": "nonexistent",  "value": 99   }
  ]
}
Response
{
  "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."
}
HTTP → MQTT Gateway: Adding "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.

3Field History

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.

GET Get the last 100 records of a field
URL
https://api.virtuino.com/api/data/field/esp32/temperature
Query Parameters
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
Response
{
  "success":       true,
  "field":         "esp32/temperature",
  "count":         100,
  "limit_applied": 100,
  "data":          [
    { "time": "2026-06-12T10:05:00Z", "value": "23.75", "source": "HTTP" },
    // …
  ]
}
GET Get the latest value of a field
URL
https://api.virtuino.com/api/data/field/esp32/temperature
Query Parameters
api_key = YOUR_API_KEY
latest  = true
Full URL Example
https://api.virtuino.com/api/data/field/esp32/temperature?api_key=YOUR_API_KEY&latest=true
Response
{
  "success":      true,
  "field":        "esp32/temperature",
  "latest_entry": {
    "time":   "2026-06-12T10:05:00Z",
    "value":  "23.75",
    "source": "HTTP"
  }
}
GET Get records within a time range
URL
https://api.virtuino.com/api/data/field/esp32/temperature
Query Parameters
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
Response
{
  "success":       true,
  "field":         "esp32/temperature",
  "count":         15,
  "limit_applied": 15,
  "data":          [ /* … */ ]
}
Field not found. If no field has that exact name, the response is 404 with "error": "Field not found…". Check the name in Console → Fields; upper and lower case matter.

4Path History

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.

GET Get the latest value of every field under a path
URL
https://api.virtuino.com/api/data/path-history/esp32
Query Parameters
api_key = YOUR_API_KEY
latest  = true
Full URL Example
https://api.virtuino.com/api/data/path-history/esp32?api_key=YOUR_API_KEY&latest=true
Response
{
  "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.
GET Get the last records under a path, grouped by time
URL
https://api.virtuino.com/api/data/path-history/esp32
Query Parameters
api_key = YOUR_API_KEY
limit   = 50   // max 5000, default 100
from    = 2026-06-12T00:00:00Z   // optional
to      = 2026-06-12T23:59:59Z   // optional
Full 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
Response
{
  "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
  ]
}

5Flat History

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.

GET Get the last 50 records across everything
URL
https://api.virtuino.com/api/data/history
Query Parameters
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
Response
{
  "success":       true,
  "count":         50,
  "limit_applied": 50,
  "data":          [
    {
      "time":   "2026-06-12T10:05:00Z",
      "field":  "esp32/temperature",
      "value":  "23.75",
      "source": "HTTP"
    },
    // …
  ]
}
GET Get records within a time range
URL
https://api.virtuino.com/api/data/history
Query Parameters
api_key = YOUR_API_KEY
from    = 2026-06-12T00:00:00Z
to      = 2026-06-12T23:59:59Z
limit   = 100
Full 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
Response
{
  "success":       true,
  "count":         100,
  "limit_applied": 100,
  "data":          [ /* … */ ]
}

6Field List

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.

GET Get all fields
URL
https://api.virtuino.com/api/data/fields
Query Parameters
api_key = YOUR_API_KEY
Full URL Example
https://api.virtuino.com/api/data/fields?api_key=YOUR_API_KEY
Response
{
  "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
  ]
}
GET Get the fields under a path
URL
https://api.virtuino.com/api/data/fields
Query Parameters
api_key = YOUR_API_KEY
path    = esp32
Full URL Example
https://api.virtuino.com/api/data/fields?api_key=YOUR_API_KEY&path=esp32
Response
{
  "success": true,
  "path":    "esp32",
  "count":   2,
  "fields":  [ /* esp32/humidity, esp32/temperature */ ]
}

7Code Examples

Full working examples using the raw HTTP API directly — no external library required. Each example writes two fields and reads back the latest snapshot.

Looking for more examples?
The VirtuinoCloud Arduino Library wraps the HTTP API in a clean, concise interface. It includes 8 full examples covering block write, relay control, motor control, NTP timestamps, history analysis, thermostat logic and more — with ESP32, ESP8266 and WiFiNINA tabs for each.
View Library Examples
Prefer no library?
Six ready-to-use ESP32 and ESP8266 sketches that call the API directly with 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.
View Examples Without Library
ESP32
ESP8266
Arduino Uno WiFi
Python
JavaScript
cURL
// 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"

8Error Codes

All error responses follow the format { "success": false, "error": "…" }.

HTTP StatusMeaningHow to fix
400 Bad Request Invalid JSON, missing required field, or invalid characters (+ or #) in path. Check the request body and URL for typos.
401 Unauthorized No API key provided. Add x-api-key header, ?api_key=, or "api_key" in body.
403 Forbidden API key is invalid or does not have write permission. Check your key in Console → Keys & Sub Users.
404 Not Found The field does not exist. Create a field with exactly that topic in Console → Fields first — or keep Auto-create fields on there (on by default for new accounts), and writes create their field automatically, up to the fields limit of your plan.
429 Too Many Requests Rate limit exceeded (600 writes/min per API key). Wait the number of seconds in the Retry-After response header.
500 Server Error Internal error on the platform. Retry after a short delay. Contact support if the issue persists.