Send Your Data — Ingest API

Stream readings from your own hardware into Jumboshack. No Jumboshack software required — just an account, a device token, and JSON over HTTPS.

1 · Get a token

Create an account at app.jumboshack.com/register and verify your email.

Add a device on the Account page (a Location, then a Device). You'll be shown a device token (jsk_…) once — copy it. That token authenticates your data and attributes it to your account.

2 · Post a reading

Send one reading per request as JSON, with the token as a Bearer header:

# POST one reading
curl -X POST https://ingest.jumboshack.com/api/ingest/geothermal \
  -H "Authorization: Bearer jsk_YOUR_DEVICE_TOKEN" \
  -H "Content-Type: application/json" \
  -d @reading.json

Success returns {"ok": true, "written": 1, ...}. Rate limits: 120 requests/min per device, 300/min per IP — a reading every 15–60s is ideal.

3 · Test first with a dry run recommended

Add ?validate=1 to check your payload without storing anything. It echoes back exactly which fields were understood, so you can confirm your format before going live:

curl -X POST "https://ingest.jumboshack.com/api/ingest/geothermal?validate=1" \
  -H "Authorization: Bearer jsk_YOUR_DEVICE_TOKEN" \
  -H "Content-Type: application/json" -d @reading.json

# → {"ok":true,"validate":true,"parsed_time_utc":"2026-08-22T17:00:31+00:00",
#     "field_count":11,"fields":{...},"tags":{...},"note":"dry run — nothing was stored"}

Minimal payload

Only the timestamp is required. Everything else is optional — send what your equipment reports; missing fields simply don't render.

{
  "metadata": { "unit": "1", "unit_location": "Basement", "time": "2026-08-22T17:00:31Z" },
  "heat_pump": {
    "total_watts": 1462,
    "water": { "entering_f": 78.1, "leaving_f": 86.0 },
    "air": { "return_f": 72.0, "supply_f": 58.0 }
  }
}

Full payload

{
  "metadata": {
    "unit": "1",
    "unit_location": "Basement",
    "time": "2026-08-22T17:00:31Z",
    "software_version": "my-rig-0.1"
  },
  "thermostat": { "ambient_temperature_f": 71.5, "relative_humidity_pct": 48,
                   "heat_setpoint_f": 68, "cool_setpoint_f": 74, "mode": "cooling" },
  "heat_pump": {
    "total_watts": 1462,
    "water": { "entering_f": 78.1, "leaving_f": 86.0, "flow_gpm": 9.2,
               "heat_rejection_btuh": 21000, "heat_extraction_btuh": 0 },
    "air": { "return_f": 72.0, "supply_f": 58.0 },
    "blower": { "watts": 190, "speed": 5 },
    "loop_pump": { "watts": 80, "flow_gpm": 9.2 },
    "compressor": { "watts": 1400, "stage": 1, "superheat_f": 12.5,
                     "suction_temp_f": 45, "suction_pressure_psi": 130,
                     "discharge_pressure_psi": 320, "discharge_temp_calculated_f": 180,
                     "reversing_valve_mode": "Cool" },
    "domestic_hot_water": { "setpoint_f": 120, "temp_f": 118 }
  },
  "humidity_control": { "dehumidify_active": false, "relay_active": false },
  "additional_sensors": { "outdoor_temp_f": { "value": 84.0 } }
}

Multiple thermostat zones one machine

Have one geothermal machine driving several thermostats (e.g. WaterFurnace IntelliZone2)? It's still one unit — don't send it as two devices, or your energy and cost would be double-counted. Instead, add a zones array: the machine-level fields (heat_pump: compressor, loop, power) stay exactly where they are, and each zone carries its own thermostat readings.

Keep it one device. All the heat_pump data belongs to the single machine; zones just adds per-thermostat comfort data. Zone data is stored separately from machine data, so energy and cost always aggregate per machine — never per zone.
{
  "metadata": { "unit": "1", "unit_location": "Main", "time": "2026-08-22T17:00:31Z" },
  "heat_pump": {
    "total_watts": 1462,
    "water": { "entering_f": 78.1, "leaving_f": 86.0, "flow_gpm": 9.2 },
    "compressor": { "watts": 1400, "stage": 1 }
  },
  "zones": [
    { "zone": 1, "ambient_temperature_f": 72.0, "relative_humidity_pct": 45,
      "heat_setpoint_f": 68, "cool_setpoint_f": 74, "mode": "cooling", "call": "cooling" },
    { "zone": 2, "ambient_temperature_f": 70.0,
      "heat_setpoint_f": 67, "cool_setpoint_f": 73, "mode": "cooling", "call": "standby" }
  ]
}

Only zone and ambient_temperature_f are required per entry; the rest are optional. A single-thermostat system just omits zones and uses the top-level thermostat (as in the examples above). A zone with a zero/missing ambient is treated as "no thermostat on that channel" and skipped. Verify with ?validate=1 — the response echoes a zones array of exactly what was parsed.

Air quality a second module

Register a device with the air quality type (Account → Add device → air quality) and post to /api/ingest/airquality. One Jumboshack device represents your whole set of monitors — each physical sensor is a room, identified by a unique serial. Put every room in one rooms array (or post a single room at the top level).

Any monitor works. The only requirements per room are a serial, a timestamp, and at least one numeric reading. Everything else is optional, so you send only the metrics your device measures — a radon-only sensor sends radon, a CO₂ meter sends CO₂, a particulate monitor sends PM. Unknown fields are ignored; missing ones simply don't render. Radon, CO₂, VOC, particulates, temperature and humidity each get their own color-banded thresholds in the UI.
curl -X POST https://ingest.jumboshack.com/api/ingest/airquality \
  -H "Authorization: Bearer jsk_your_airquality_device_token" \
  -H "Content-Type: application/json" \
  -d @reading.json

Full multi-sensor

Radon + CO₂ + VOC + particulates + temp/humidity/pressure — e.g. Airthings View Plus or Wave Plus, Awair Element:

{
  "metadata": { "time": "2026-08-30T17:00:31Z", "software_version": "aq-push/1.0" },
  "rooms": [
    {
      "serial": "2960123636", "name": "Living Room", "floor": "Main Floor",
      "floor_order": 2, "device_type": "Airthings View Plus", "recorded": "2026-08-30T17:00:31Z",
      "radon_pci": 1.8, "radon_bq": 66.6, "temp_f": 71.2, "humidity_pct": 44,
      "co2_ppm": 720, "voc_ppb": 140, "pm25_ugm3": 6, "pm1_ugm3": 4,
      "pressure_mbar": 1013, "battery_pct": 88
    }
  ]
}

Radon-only

Radon (+ often temp/humidity) — e.g. Airthings View Radon, Wave, Corentium Home, Ecosense:

{
  "metadata": { "time": "2026-08-30T17:00:31Z" },
  "rooms": [
    { "serial": "2989036984", "name": "Basement", "floor": "Basement", "floor_order": 3,
      "device_type": "Airthings View Radon", "radon_pci": 3.9, "temp_f": 66.0, "humidity_pct": 52 }
  ]
}

CO₂ / VOC monitor

Carbon dioxide + VOCs + temp/humidity, no radon or PM — e.g. Aranet4, Airthings Wave Mini, any SCD40/SGP-based sensor:

{
  "metadata": { "time": "2026-08-30T17:00:31Z" },
  "rooms": [
    { "serial": "aranet-9F2C", "name": "Office", "floor": "Main Floor", "floor_order": 2,
      "device_type": "Aranet4", "co2_ppm": 1180, "voc_ppb": 240, "temp_f": 73.4, "humidity_pct": 41 }
  ]
}

Particulate (PM) monitor

Fine-particle sensors — e.g. PurpleAir, IKEA VINDRIKTNING, a PMS5003, or an air purifier's built-in sensor:

{
  "metadata": { "time": "2026-08-30T17:00:31Z" },
  "rooms": [
    { "serial": "pa-104711", "name": "Kids Room", "floor": "Level 2", "floor_order": 1,
      "device_type": "PurpleAir", "pm25_ugm3": 11.3, "pm1_ugm3": 7.1, "temp_f": 70.0 }
  ]
}

Whole house — mixed devices in one POST

Send every room together, even if they're different models. The Diagram groups them by floor (top→bottom by floor_order); each keeps its own metrics:

{
  "metadata": { "time": "2026-08-30T17:00:31Z" },
  "rooms": [
    { "serial": "2960125816", "name": "Master Bedroom", "floor": "Level 2", "floor_order": 1,
      "device_type": "View Plus", "radon_pci": 0.8, "co2_ppm": 640, "pm25_ugm3": 3, "temp_f": 69, "humidity_pct": 46 },
    { "serial": "2960123636", "name": "Main Floor", "floor": "Main Floor", "floor_order": 2,
      "device_type": "Aranet4", "co2_ppm": 810, "voc_ppb": 190, "temp_f": 71, "humidity_pct": 43 },
    { "serial": "2989036984", "name": "Basement", "floor": "Basement", "floor_order": 3,
      "device_type": "View Radon", "radon_pci": 3.9, "temp_f": 66, "humidity_pct": 53 }
  ]
}

One sensor — flat form

With a single monitor you can skip the array and post the room at the top level:

{
  "metadata": { "time": "2026-08-30T17:00:31Z" },
  "serial": "aranet-9F2C", "name": "Office", "co2_ppm": 900, "temp_f": 72
}

Air-quality fields

Per room. serial + a timestamp + one numeric field are required; the rest are optional. Temperatures are °F (convert if your device reports °C). Radon: send radon_pci (that's what the gauges use) — if your device only reports Bq/m³, convert with pCi/L = Bq/m³ ÷ 37.

FieldTypeUnitNotes
metadata.timestring—Default reading time (ISO-8601). Used when a room has no recorded.
rooms[].serial *requiredstring—Unique id per physical sensor/room. Ties readings to one room over time.
rooms[].recordedstring—This sensor's own reading time (ISO-8601). Falls back to metadata.time. Idempotent by timestamp.
rooms[].namestring—Room label shown on cards/calendar (e.g. "Living Room").
rooms[].floorstring—Floor grouping on the Diagram (e.g. "Main Floor", "Basement").
rooms[].floor_ordernumber—Top→bottom sort (1 = highest floor). Orders the house Diagram.
rooms[].device_typestring—Free text — your model (e.g. "Airthings View Plus", "Aranet4").
rooms[].radon_pcinumberpCi/LRadon — headline metric. EPA action level 4.0. Drives the radon color bands.
rooms[].radon_bqnumberBq/m³Radon in SI units (stored alongside; pCi/L = Bq/m³ ÷ 37).
rooms[].co2_ppmnumberppmCarbon dioxide. Bands: <800 good … ≥1500 high.
rooms[].voc_ppbnumberppbTotal VOCs.
rooms[].pm25_ugm3numberµg/m³PM2.5 fine particulates. Bands: <12 good … ≥55 high.
rooms[].pm1_ugm3numberµg/m³PM1 particulates.
rooms[].temp_fnumber°FTemperature.
rooms[].humidity_pctnumber%Relative humidity. Bands target 40–60%.
rooms[].pressure_mbarnumbermbarBarometric pressure.
rooms[].battery_pctnumber%Sensor battery.

Verify any payload with ?validate=1 — the response echoes the parsed rooms and fields so you can confirm your format before going live.

Custom — any numbers you want schemaless

Have data that isn't a heat pump or an air-quality monitor? Register a device with the custom type (Account → Add device → custom) and post to /api/ingest/custom. Send a flat metrics map of whatever numbers you have — a computer's GPU/CPU/RAM, a server's load, a water tank level, a solar inverter, anything. There's no fixed schema: each metric becomes its own field and auto-renders on the Custom tab as a card plus a hover trend chart, grouped by device. No pre-registration of metric names.

Only two things are required: metadata.time and at least one numeric metrics entry. Non-numeric values are ignored (so a stray string won't break a POST), metric names are normalized to lower_snake_case, and up to 100 metrics per reading are kept.
curl -X POST https://ingest.jumboshack.com/api/ingest/custom \
  -H "Authorization: Bearer jsk_your_custom_device_token" \
  -H "Content-Type: application/json" \
  -d @reading.json

Example — a computer / GPU box

{
  "metadata": { "time": "2026-09-06T17:00:31Z", "software_version": "host-agent/1.0" },
  "metrics": {
    "gpu_util": 73, "gpu_temp": 64, "vram_gb": 18.4,
    "ram_pct": 41.2, "system_load": 2.1, "disk_root_pct": 58,
    "swap_pct": 3, "processes": 412
  }
}

Optional: add "stream": "gpu0" (top level or in metadata) to keep several sources apart on one device.

Auto-formatting

The Custom tab infers a sensible display from each metric's name — no configuration: names containing util/usage/_pct render as % with green/amber/red bands; _gb/_mb as data sizes; temp as degrees; processes/_count as integers; anything else as a plain number. So gpu_util shows as “GPU Util 73%”. Pick descriptive names and it just works.

Custom fields

FieldTypeNotes
metadata.time *requiredstringReading time (ISO-8601). Aliases: timestamp, recorded. Idempotent by timestamp.
metadata.software_versionstringYour agent/firmware version — shown in your account.
metrics.<name> *≥1 requirednumberAny numeric metric. Name → lower_snake_case; non-numeric values ignored; ≤100 per reading.
streamstringOptional label to separate multiple sources on one device.

Verify with ?validate=1 — the response echoes the metrics that were understood before anything is stored.

Weather a typed module

Register a device with the weather type (Account → Add device → weather) and post to /api/ingest/weather — a flat single-station reading, or a stations array. It renders on the Weather tab with units applied automatically.

curl -X POST https://ingest.jumboshack.com/api/ingest/weather \
  -H "Authorization: Bearer jsk_your_weather_device_token" \
  -H "Content-Type: application/json" -d @reading.json
{
  "metadata": { "time": "2026-09-07T17:00:00Z" },
  "station": "Backyard",
  "temp_f": 71.2, "humidity_pct": 48, "wind_mph": 4.1, "gust_mph": 9.0, "wind_dir_deg": 210,
  "rain_in": 0.12, "pressure_inhg": 30.02, "uv": 3, "solar_wm2": 420
}

Fields (all optional; °F / mph / in / inHg / % / W/m² applied automatically): temp_f, feels_like_f, dew_point_f, humidity_pct, wind_mph, gust_mph, wind_dir_deg, rain_in, rain_rate_inhr, pressure_inhg, uv, solar_wm2, temp_indoor_f, humidity_indoor_pct. Add station to name it, or send a stations array for several. Full walkthrough: the weather setup guide.

Geothermal field reference

PathTypeUnitNotes
metadata.time *requiredstring—Reading time. ISO-8601 (2026-08-22T17:00:31Z, offset, or naive=UTC). Aliases: query_end, query_start, timestamp.
metadata.unitstring—Your label for the unit (e.g. "1").
metadata.unit_locationstring—Where it is (e.g. "Basement").
metadata.software_versionstring—Your firmware/agent version — shown in your account.
thermostat.ambient_temperature_fnumber°FIndoor/zone temp.
thermostat.relative_humidity_pctnumber%
thermostat.heat_setpoint_f / cool_setpoint_fnumber°F
thermostat.modestring—Free text (e.g. heating/cooling/off).
heat_pump.total_wattsnumberWPowers energy & cost.
heat_pump.water.entering_f / leaving_fnumber°FLoop temps (EWT/LWT).
heat_pump.water.flow_gpmnumberGPM
heat_pump.water.heat_rejection_btuh / heat_extraction_btuhnumberBTU/hDrives the Cooling/Heating mode indicator.
heat_pump.air.return_f / supply_fnumber°F
heat_pump.blower.watts / speednumberW / step
heat_pump.loop_pump.watts / flow_gpmnumberW / GPM
heat_pump.compressor.watts / stagenumberW / #stage 0/1/2.
heat_pump.compressor.suction_temp_f / suction_pressure_psinumber°F / psi
heat_pump.compressor.discharge_pressure_psi / discharge_temp_calculated_fnumberpsi / °F
heat_pump.compressor.superheat_fnumber°F
heat_pump.compressor.saturated_evaporator_temp_f / saturated_condenser_temp_fnumber°F
heat_pump.compressor.reversing_valve_modestring—"Cool" / "Heat".
heat_pump.domestic_hot_water.setpoint_f / temp_fnumber°F
humidity_control.dehumidify_active / relay_activebool—
humidity_control.modestring—
zones[].zonenumber/string—Zone id (1, 2, …). One entry per thermostat on a single machine — see "Multiple thermostat zones" above.
zones[].ambient_temperature_fnumber°FZone temp. Zero/omitted ⇒ that zone is skipped.
zones[].relative_humidity_pctnumber%
zones[].heat_setpoint_f / cool_setpoint_fnumber°FPer-zone setpoints.
zones[].mode / callstring—Per-zone mode / demand.
additional_sensors.<name>.valuenumberanyAny extra sensor; stored as x_<name>.

Numbers may be sent as JSON numbers or numeric strings. Errors: 401 bad/missing token · 422 missing timestamp or invalid JSON · 429 rate limit.

← Back to your account