VegeCloud Hub Configuration API — Version 2

This API is implemented on the VegeCloud server. The VegeHub firmware uses it to do two things: upload sensor readings and synchronize its configuration with the server. The two functions use separate endpoints and separate request/response formats, described below.

Implementing the configuration sync portion of this API is optional. If your server only needs to receive sensor data, you only need to handle the Data Update endpoint.

Authentication

Every request from the hub includes two credentials:

  • api_key — A 16-character string assigned to the hub. The server uses this to verify that a request belongs to a known hub.
  • route_key — A 16-character routing identifier that appears in the URL path. It directs the request to the correct account or device group on the server.

1. Data Update Endpoint

URL

POST https://api.vegecloud.com/v2/in/<RouteKey>

Purpose

The hub calls this endpoint every time it has new sensor readings to report. The hub sends a JSON payload containing the readings, and the server's response tells the hub whether a configuration sync is needed.

Request Payload

The hub sends a JSON object with the following fields. All fields are always present unless noted as conditional.

Field Type Description
api_key string Hub API key (16 chars).
mac string Hub MAC address, uppercase, no colons (e.g. "F8F0056BB15D").
error_code integer Always 0 in normal operation.
sensors array Array of sensor channel objects. May be empty if there are no new readings to send. See Sensor Array Format below.
send_time string Unix timestamp (seconds since epoch) at the time the request was sent, encoded as a numeric string (e.g. "1717776120").
wifi_str integer WiFi RSSI value in dBm at the time of transmission (e.g. -65).
voltage (conditional) float Battery voltage in volts. Only present on battery-powered hubs; absent on AC-powered hubs. On hub/greenhouse boards the battery reading also appears in the sensors array at slot N+1. On battery-powered sprinkler boards it appears at slot 1 (stored in the same channel buffer as actuator states).

Sensor Array Format

Each element in the sensors array represents one data channel. Slots are 1-based. What occupies each slot depends on the board type:

Hub/Greenhouse boards
Slot Content Value
1 … N Sensor input channels (N = number of sensor channels) Voltage in volts (float)
N+1 Battery voltage (battery-powered hubs only) Voltage in volts (float)
Sprinkler boards (AC-powered)
Slot Content Value
1 … M Actuator states (M = number of actuators) 0.0 = off, 1.0 = on
Sprinkler boards (battery-powered)
Slot Content Value
1 Battery voltage Voltage in volts (float)
2 … M+1 Actuator states (M = number of actuators) 0.0 = off, 1.0 = on

Each channel object has the same structure regardless of what it contains:

{
  "slot": 1,           // Channel slot number (1-based)
  "samples": [
    {
      "v": 0.332,      // Value for this channel (see tables above for units/meaning)
      "t": "2024-06-07T16:02:00Z"  // ISO 8601 UTC timestamp
    }
    // ... additional samples for this channel
  ]
}

Multiple samples may be present in a single update when the hub has accumulated backlogged readings from a period without network connectivity. Samples within a channel are in chronological order (oldest first). If the hub's backlog is too large to fit in one request, it sends the oldest readings first and continues in subsequent updates.

Example Request

POST /v2/in/myRouteKey HTTP/1.1
Host: api.vegecloud.com
Content-Type: application/json

{
  "api_key": "2813099150",
  "mac": "F8F0056BB15D",
  "error_code": 0,
  "sensors": [
    {"slot": 1, "samples": [{"v": 0.332, "t": "2024-06-07T16:02:00Z"}]},
    {"slot": 2, "samples": [{"v": 0.329, "t": "2024-06-07T16:02:00Z"}]},
    {"slot": 3, "samples": [{"v": 0.467, "t": "2024-06-07T16:02:00Z"}]},
    {"slot": 4, "samples": [{"v": 0.063, "t": "2024-06-07T16:02:00Z"}]},
    {"slot": 5, "samples": [{"v": 9.015, "t": "2024-06-07T16:02:00Z"}]}
  ],
  "send_time": "1717776120",
  "wifi_str": -65
}

Server Response

The server must respond with HTTP 200 and a JSON object. The key field is who_updated, which controls whether the hub initiates a configuration sync after this update.

who_updated value Meaning Hub behavior
0 Server has no record of this hub. Hub immediately sends its full configuration to the server via the Config In endpoint.
1 Server's stored configuration was last updated by the hub itself. Hub takes no action unless it has local changes (i.e., settings were changed on the hub since the last sync). If local changes exist, the hub sends its full configuration.
2 Server's stored configuration was last updated via the server or web interface. Hub fetches the new configuration from the server via the Config Out endpoint, then sends its own updated configuration back.
99 Server is requesting a firmware upgrade. Hub checks the firmware server for a newer version and initiates an OTA update if one is found. After the upgrade, the hub sends its full configuration to confirm.

If the who_updated field is absent from the response, the hub takes no configuration sync action.

Example Responses

// No configuration record on file — hub will send its full config:
{"who_updated": 0}

// Hub was last to update — no sync needed:
{"who_updated": 1}

// Server has newer config — hub will fetch it:
{"who_updated": 2}

2. Configuration Sync

Configuration sync uses two separate endpoints. The server triggers a sync by sending a non-zero who_updated value in its response to a Data Update request (see above). What the hub does next depends on the value:

  • who_updated: 0 — Hub sends its full configuration to the server immediately. No fetch step.
  • who_updated: 1 — Hub was the last to update. No full sync needed. However, if the data update response also contains a schedule_overrides key, the hub will make a targeted Config Out request for just those overrides, then apply them. This is how the server delivers on-demand actuator commands (e.g. "turn on valve 2 now") without triggering a full config exchange.
  • who_updated: 2 — Server has newer settings. Hub fetches all config sections (hub, sensors, actuators, schedules, schedule_overrides) from the Config Out endpoint, applies any that are newer than its own copy, then uploads its complete configuration back to the Config In endpoint.

Note on web_conditions: Web conditions are never uploaded to the server (they are stripped from Config In payloads). However, the hub's web UI does query them from the Config Out endpoint on demand — when a user is setting up an actuator condition and wants to browse available VegeCloud virtual sensors. In that case the hub sends a Config Out request with just "web_conditions": [], and the server returns the list of available conditions for that route key. The results are displayed to the user for selection but are not stored in the config file. This discovery call is entirely separate from the normal config sync cycle.

2a. Config Out Endpoint (Hub Fetches Config from Server)

URL

POST https://api.vegecloud.com/v2/hubconfig/out/<RouteKey>

Request Payload

The hub sends a JSON object identifying itself and the configuration sections it wants. A section is requested by including its key with an empty object or empty array. schedule_overrides is always requested. The other sections are included based on the board's hardware (e.g. schedules only on sprinkler boards) and are omitted when only overrides are being fetched.

{
  "api_key": "2813099150",
  "mac": "F8F0056BB15D",
  "hub": {},                  // (conditional) Request hub-level settings
  "sensors": [],              // (conditional) Request sensor channel settings
  "actuators": [],            // (conditional) Request actuator settings
  "schedules": [],            // (conditional) Request schedule settings — sprinkler boards only
  "schedule_overrides": [],   // Always included — request any pending schedule overrides
  "web_conditions": []        // (UI only) Request available VegeCloud virtual sensors —
                              // sent only during condition setup in the web UI, not during
                              // normal config sync
}

Only the sections whose keys are present in the request will be populated in the response.

Server Response

The server returns the requested sections populated with their stored values, along with an updated timestamp and the hub's credentials for verification.

{
  "api_key": "2813099150",
  "mac": "F8F0056BB15D",
  "updated": "2024-06-07T16:03:20Z",  // UTC timestamp of last server-side config change
  "hub": { ... },
  "sensors": [ ... ],
  "actuators": [ ... ],
  "schedules": [ ... ],
  "schedule_overrides": [ ... ]
}

The hub compares the updated timestamp from the server against its own stored timestamp. If the server's timestamp is more than 30 seconds newer, the hub applies the received settings. If the hub's settings are equal or newer, it keeps its own settings and will overwrite the server's copy in the next step.

2b. Config In Endpoint (Hub Sends Config to Server)

URL

POST https://api.vegecloud.com/v2/hubconfig/in/<RouteKey>

Purpose

After a sync (or whenever settings have changed locally), the hub uploads its complete current configuration to this endpoint so the server has an authoritative copy.

Request Payload

The hub sends the full configuration JSON. Key top-level fields:

Field Type Description
api_key string Hub API key.
route_key string Routing key (also in URL path).
mac string Hub MAC address, no colons.
api_version string Always "2".
id string Legacy identifier field. Always an empty string (""); present for protocol compatibility.
who_updated integer Always 1 — indicates the hub sent this configuration.
updated string ISO 8601 UTC timestamp of when the hub's configuration was last changed (e.g. "2024-06-07T16:03:20Z").
hub object Hub-level settings. See Hub Object.
sensors array Sensor channel settings. See Sensor Config Object.
actuators array Actuator settings (if the hub has actuators). See Actuator Object.
schedules array Schedule settings (sprinkler hubs only). See Schedule Object.
schedule_overrides array Active schedule overrides. See Schedule Override Object.

The following fields are stripped before upload and never sent to the server: wifi, special, private, endpoints. web_conditions is also stripped — it is a local-only configuration that is never synced with the server in either direction.

Server Response

The server should respond with HTTP 200. After storing the configuration, the server should update its stored who_updated value to 1, so that the next data update response tells the hub that its own settings are already current.

{"who_updated": 1}

3. Complete Configuration Object Reference

This section documents every field used in configuration payloads (Config In and Config Out). Two annotations are used throughout:

  • optional — the field may be absent; the server must handle its omission gracefully. Fields not labeled optional should be treated as required.
  • read-only — the field is set by the hub at upload time; the server should store it but never modify it.

Overall Structure

The following skeleton shows the complete top-level layout of a configuration payload and where every object is nested. Each named section is detailed individually below.

{
  // --- Top-level identification and authentication ---
  "api_key":      "2813099150",         // Hub API key
  "route_key":    "myRouteKey",         // Routing key (Config In only)
  "mac":          "F8F0056BB15D",       // Hub MAC address, no colons
  "id":           "",                   // Legacy field, always empty string
  "api_version":  "2",                  // Always "2" (Config In only)
  "who_updated":  1,                    // Always 1 when sent by hub (Config In only)
  "updated":      "2024-06-07T16:03:20Z", // Timestamp of last config change

  // --- Configuration objects ---
  "hub": {                              // Hub-level settings — see Hub Object below
    ...
  },

  "sensors": [                          // One entry per sensor channel — see Sensor Config Object below
    { ... },
    { ... }
  ],

  "actuators": [                        // One entry per actuator — see Actuator Object below
    {
      ...
      "conditions": [                   // Nested inside each actuator — see Condition Object below
        { ... },
        { ... }
      ]
    }
  ],

  "schedules": [                        // Sprinkler boards only — see Schedule Object below
    {
      ...
      "actions": [                      // Nested inside each schedule — see Schedule Action below
        { ... }
      ]
    }
  ],

  "schedule_overrides": [               // Pending actuator commands — see Schedule Override Object below
    { ... }
  ]
}

Hub Object  hub

{
  // --- Configurable settings (server may update these) ---
  "name":                   string,   // Display name for this hub (max 32 chars)
  "firmware_url":           string,   // URL the hub checks for OTA firmware updates (e.g. "https://updates.vegecloud.com/latest.json")
  "utc_offset":             integer,  // Local time offset from UTC in seconds (e.g. -25200 for UTC-7, 3600 for UTC+1, 0 for UTC)
  "sample_period":          integer,  // How often the hub takes sensor readings, in seconds (e.g. 900 = every 15 minutes)
  "update_period":          integer,  // How often the hub sends data to the server, in seconds (e.g. 900 = every 15 minutes)
  "blink_update":           integer,  // LED feedback level:
                                      //   1 = full (startup, updates, status) — default
                                      //   2 = limited (updates and errors only)
                                      //   0 = minimal (errors and button feedback only)
                                      //   3 = off (LED disabled; errors and button still show)
                                      // Note: values 2 and 3 are only supported by newer firmware.
                                      // Behavior on older firmware is undefined. Use only 0 or 1
                                      // if the hub may be running older firmware.
  "report_voltage":         integer,  // Include battery voltage in updates: 0 = no, 1 = yes
  "power_mode":             integer,  // Power source: 0 = battery, 1 = AC/wall power
  "agenda":                 integer,  // Operating mode: 0 = Sprinkler, 1 = Hub/Greenhouse
  "server_url":             string,   // Primary data reporting server URL (e.g. "https://api.vegecloud.com/v2")
  "update_urls":            array,    // Additional URLs to mirror data to (e.g. ["http://192.168.1.10/update"])
  "static_ip_addr":         string,   // Static IP address, empty string = use DHCP (e.g. "192.168.1.50")
  "dns":                    string,   // DNS server address (e.g. "8.8.8.8")
  "subnet":                 string,   // Subnet mask (e.g. "255.255.255.0")
  "gateway":                string,   // Default gateway address (e.g. "192.168.1.1")
  "onboard_sensor_poll_rate": integer, // How often actuator conditions re-poll onboard sensors, in seconds (e.g. 60)
  "remote_sensor_poll_rate":  integer, // How often actuator conditions re-poll remote/web sensors, in seconds (e.g. 60)

  // --- Read-only fields (set by hub at upload time; server should store but never modify) ---
  "model":                     string,   // Hardware model identifier, e.g. "VG-HUB4", "VG-SPRINKLER4"
  "firmware_version":          string,   // Installed firmware version string
  "previous_firmware_version": string,   // (optional) Version that was running before the last OTA upgrade.
                                         // Written into the config on first boot after an upgrade and
                                         // persists until the next upgrade. Absent on hubs that have
                                         // never been upgraded.
  "current_ip_addr":           string,   // Hub's current IP address at time of upload
  "flash_size":                integer,  // Physical flash chip size in MB (read from hardware)

  // --- Manufacturing info (read-only; loaded from /config/mfg_info.json on the hub) ---
  "hw_revision":            string,   // Hardware board revision string (max 16 chars)
  "manufacture_date":       string,   // Date the unit was manufactured, format "YYYY-MM-DD" (e.g. "2024-03-15")
  "manufactured_by":        string    // Manufacturer identifier string (max 16 chars)
}

Sensor Config Object  sensors[]

{
  "slot":               integer,  // Physical input channel number, 0-based
  "name":               string,   // Display name for this sensor (max 32 chars)
  "mode":               integer,  // Input mode: 0 = analog sensor, 1 = digital edge trigger
  "warm_up":            float,    // Time in seconds to power the sensor before sampling (e.g. 1.5)
  "pull_up":            integer,  // Enable internal pull-up resistor: 0 = no, 1 = yes
  "always_power":       integer,  // Keep sensor powered continuously: 0 = no, 1 = yes
  "update_on_trigger":  integer,  // Send immediate update on edge event: 0 = no, 1 = yes
  "edge":               integer,  // Edge direction to detect: 0 = falling, 1 = rising, 2 = both
  "transform":          integer   // Sensor type transform index (0 = none/raw voltage).
                                  // Non-zero values map voltage to physical units using
                                  // a Vegetronix sensor calibration curve.
}

Actuator Object  actuators[]

{
  "slot":         integer,  // Physical actuator channel number, 0-based
  "name":         string,   // Display name (max 32 chars)
  "type":         integer,  // Actuator type: 0 = undefined, 1 = latching, 2 = on/off, 3 = PWM, 4 = DAC
  "pulse_width":  integer,  // Latching valve pulse duration in milliseconds, type 1 only (e.g. 200)
  "polarity":     integer,  // Output polarity: 0 = normal, 1 = inverted
  "enabled":      integer,  // 0 = disabled, 1 = enabled
  "turn_on":      integer,  // Manual override state: 0 = off, 1 = on
  "start_time":   string,   // Default schedule start time, format "HH:MM" (e.g. "06:30")
  "period":       integer,  // Seconds between activation windows
  "duration":     integer,  // Seconds the actuator stays on per activation window
  "conditions":   array     // Array of Condition Objects (see below)
}

Condition Object  actuators[].conditions[]

Each actuator has its own conditions array. Conditions are evaluated together to decide whether the actuator should be active.

{
  // --- Required fields ---
  "sequence":        integer,  // Evaluation order (0-based)
  "type":            integer,  // Condition type:
                               //   0 = none
                               //   1 = onboard sensor
                               //   2 = time window
                               //   3 = local network sensor
                               //   4 = web JSON value
                               //   5 = VegeCloud virtual sensor
  "name":            string,   // Display name (max 32 chars)
  "bool_operator":   integer,  // How to chain with previous condition:
                               //   0 = none (first condition), 1 = AND, 2 = OR

  // --- Optional fields — present only when relevant to the condition type ---
  "slot":            integer,  // optional — sensor channel slot number (types 1 and 3)
  "range_lower":     float,    // optional — lower bound of comparison range (types 1, 3, 4, 5)
  "range_upper":     float,    // optional — upper bound of comparison range (types 1, 3, 4, 5)
  "range_hysteresis":float,    // optional — hysteresis band to prevent rapid switching (types 1, 3, 4, 5)
  "range_operator":  integer,  // optional — comparison operator (types 1, 3, 4, 5):
                               //   0 = greater than, 1 = less than
                               //   2 = inside range, 3 = outside range
                               //   4 = true, 5 = false
  "fallback":        integer,  // optional — state to assume if web source is unreachable (types 4 and 5):
                               //   0 = false, 1 = true
  "url":             string,   // optional — target URL, max 256 chars (types 3, 4, 5)
  "url_param":       string,   // optional — JSON path to extract value from response (types 4 and 5)
                               //   max 256 chars, format: "\"key\"[index].\"subkey\""
  "start_time":      string,   // optional — window start time, format "HH:MM" e.g. "06:00" (type 2)
  "end_time":        string,   // optional — window end time, format "HH:MM" e.g. "22:00" (type 2)
  "days_of_week":    integer   // optional — bitmask of active days (type 2):
                               //   0 = never, 1 = Sun, 2 = Mon, 4 = Tue, 8 = Wed,
                               //   16 = Thu, 32 = Fri, 64 = Sat, 127 = every day
                               //   (e.g. 42 = Mon+Wed+Fri, 62 = Mon–Fri)
}

Schedule Object  schedules[] — sprinkler boards only

{
  "idx":          integer,  // Schedule index, 0-based
  "name":         string,   // Display name (max 32 chars)
  "enabled":      integer,  // 0 = disabled, 1 = enabled
  "mode":         integer,  // 0 = calendar (days of week), 1 = periodic (fixed interval)
  "days_of_week": integer,  // Calendar mode: bitmask (same as condition days_of_week above)
  "period":       integer,  // Periodic mode: minutes between schedule runs (e.g. 1440 = once per day)
  "start_time":   string,   // Start time, format "HH:MM" (e.g. "07:00")
  "actions": [              // Nested inside each schedule — one entry per actuator on the board
    {
      "enabled":       integer,  // 0 = disabled, 1 = enabled
      "actuator_slot": integer,  // Target actuator channel (0-based)
      "duration":      integer   // How long to run the actuator, in minutes
    }
  ]
}

Schedule Override Object  schedule_overrides[]

{
  "id":            integer,  // Unique identifier for this override (e.g. 1, 2, 3 — server-assigned)
  "start_time":    string,   // When to execute: ISO 8601 UTC datetime string (e.g. "2024-06-07T18:00:00Z").
                             // Use "0000-00-00 00:00:00" to execute immediately.
  "duration":      integer,  // How long to run the actuator, in seconds
  "actuator_slot": integer,  // Target actuator channel (0-based)
  "action_type":   integer   // 1 = turn on, 2 = turn off, 3 = cancel active run
}

4. Full Transaction Example

The following shows a complete hub-to-server exchange where the server has no record of the hub (who_updated: 0), triggering the hub to upload its full configuration.

Step 1 — Hub sends sensor data

POST /v2/in/myRouteKey HTTP/1.1
Content-Type: application/json

{
  "api_key": "2813099150",
  "mac": "F8F0056BB15D",
  "error_code": 0,
  "sensors": [
    {"slot": 1, "samples": [{"v": 0.332, "t": "2024-06-07T16:02:00Z"}]},
    {"slot": 2, "samples": [{"v": 0.329, "t": "2024-06-07T16:02:00Z"}]}
  ],
  "send_time": "1717776120",
  "wifi_str": -72
}

Step 2 — Server replies: no record on file

HTTP/1.1 200 OK
Content-Type: application/json

{"who_updated": 0}

Step 3 — Hub uploads its full configuration

POST /v2/hubconfig/in/myRouteKey HTTP/1.1
Content-Type: application/json

{
  "api_key": "2813099150",
  "route_key": "myRouteKey",
  "mac": "F8F0056BB15D",
  "id": "",
  "api_version": "2",
  "who_updated": 1,
  "updated": "2024-06-07T16:03:20Z",
  "hub": {
    "name": "Garden Hub",
    "model": "VG-HUB4",
    "firmware_version": "5.4.2",
    "sample_period": 900,
    "update_period": 900,
    "power_mode": 0,
    ...
  },
  "sensors": [
    {"slot": 0, "name": "Soil Moisture 1", "mode": 0, "transform": 5, ...},
    {"slot": 1, "name": "Soil Moisture 2", "mode": 0, "transform": 5, ...}
  ]
}

Step 4 — Server confirms receipt

HTTP/1.1 200 OK
Content-Type: application/json

{"who_updated": 1}

From this point on, the server should return {"who_updated": 1} in response to data updates until settings are changed on the server side. When settings are changed server-side, update the stored timestamp to the current UTC time and begin returning {"who_updated": 2} to signal the hub to fetch the new settings.





VegeCloud Terms of Use

Copyright © 2024 Vegetronix, Inc. All rights Reservered.