Stream readings from your own hardware into Jumboshack. No Jumboshack software required — just an account, a device token, and JSON over HTTPS.
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.
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.
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"}
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 }
}
}
{
"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 } }
}
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.
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.
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).
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
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 (+ 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 }
]
}
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 }
]
}
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 }
]
}
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 }
]
}
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
}
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.
| Field | Type | Unit | Notes |
|---|---|---|---|
| metadata.time | string | — | Default reading time (ISO-8601). Used when a room has no recorded. |
| rooms[].serial *required | string | — | Unique id per physical sensor/room. Ties readings to one room over time. |
| rooms[].recorded | string | — | This sensor's own reading time (ISO-8601). Falls back to metadata.time. Idempotent by timestamp. |
| rooms[].name | string | — | Room label shown on cards/calendar (e.g. "Living Room"). |
| rooms[].floor | string | — | Floor grouping on the Diagram (e.g. "Main Floor", "Basement"). |
| rooms[].floor_order | number | — | Top→bottom sort (1 = highest floor). Orders the house Diagram. |
| rooms[].device_type | string | — | Free text — your model (e.g. "Airthings View Plus", "Aranet4"). |
| rooms[].radon_pci | number | pCi/L | Radon — headline metric. EPA action level 4.0. Drives the radon color bands. |
| rooms[].radon_bq | number | Bq/m³ | Radon in SI units (stored alongside; pCi/L = Bq/m³ ÷ 37). |
| rooms[].co2_ppm | number | ppm | Carbon dioxide. Bands: <800 good … ≥1500 high. |
| rooms[].voc_ppb | number | ppb | Total VOCs. |
| rooms[].pm25_ugm3 | number | µg/m³ | PM2.5 fine particulates. Bands: <12 good … ≥55 high. |
| rooms[].pm1_ugm3 | number | µg/m³ | PM1 particulates. |
| rooms[].temp_f | number | °F | Temperature. |
| rooms[].humidity_pct | number | % | Relative humidity. Bands target 40–60%. |
| rooms[].pressure_mbar | number | mbar | Barometric pressure. |
| rooms[].battery_pct | number | % | 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.
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.
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
{
"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.
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.
| Field | Type | Notes |
|---|---|---|
| metadata.time *required | string | Reading time (ISO-8601). Aliases: timestamp, recorded. Idempotent by timestamp. |
| metadata.software_version | string | Your agent/firmware version — shown in your account. |
| metrics.<name> *≥1 required | number | Any numeric metric. Name → lower_snake_case; non-numeric values ignored; ≤100 per reading. |
| stream | string | Optional label to separate multiple sources on one device. |
Verify with ?validate=1 — the response echoes the metrics that were understood
before anything is stored.
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.
| Path | Type | Unit | Notes |
|---|---|---|---|
| metadata.time *required | string | — | Reading time. ISO-8601 (2026-08-22T17:00:31Z, offset, or naive=UTC). Aliases: query_end, query_start, timestamp. |
| metadata.unit | string | — | Your label for the unit (e.g. "1"). |
| metadata.unit_location | string | — | Where it is (e.g. "Basement"). |
| metadata.software_version | string | — | Your firmware/agent version — shown in your account. |
| thermostat.ambient_temperature_f | number | °F | Indoor/zone temp. |
| thermostat.relative_humidity_pct | number | % | |
| thermostat.heat_setpoint_f / cool_setpoint_f | number | °F | |
| thermostat.mode | string | — | Free text (e.g. heating/cooling/off). |
| heat_pump.total_watts | number | W | Powers energy & cost. |
| heat_pump.water.entering_f / leaving_f | number | °F | Loop temps (EWT/LWT). |
| heat_pump.water.flow_gpm | number | GPM | |
| heat_pump.water.heat_rejection_btuh / heat_extraction_btuh | number | BTU/h | Drives the Cooling/Heating mode indicator. |
| heat_pump.air.return_f / supply_f | number | °F | |
| heat_pump.blower.watts / speed | number | W / step | |
| heat_pump.loop_pump.watts / flow_gpm | number | W / GPM | |
| heat_pump.compressor.watts / stage | number | W / # | stage 0/1/2. |
| heat_pump.compressor.suction_temp_f / suction_pressure_psi | number | °F / psi | |
| heat_pump.compressor.discharge_pressure_psi / discharge_temp_calculated_f | number | psi / °F | |
| heat_pump.compressor.superheat_f | number | °F | |
| heat_pump.compressor.saturated_evaporator_temp_f / saturated_condenser_temp_f | number | °F | |
| heat_pump.compressor.reversing_valve_mode | string | — | "Cool" / "Heat". |
| heat_pump.domestic_hot_water.setpoint_f / temp_f | number | °F | |
| humidity_control.dehumidify_active / relay_active | bool | — | |
| humidity_control.mode | string | — | |
| zones[].zone | number/string | — | Zone id (1, 2, …). One entry per thermostat on a single machine — see "Multiple thermostat zones" above. |
| zones[].ambient_temperature_f | number | °F | Zone temp. Zero/omitted ⇒ that zone is skipped. |
| zones[].relative_humidity_pct | number | % | |
| zones[].heat_setpoint_f / cool_setpoint_f | number | °F | Per-zone setpoints. |
| zones[].mode / call | string | — | Per-zone mode / demand. |
| additional_sensors.<name>.value | number | any | Any 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.