# Push readings as JSON

URL: https://pro-weather.com/docs/getting-started/json-ingest

> A documented JSON envelope for feeding a Pro Weather site from a script, a home-built sensor or anything with no weather-station firmware of its own.

The [personal upload URL](https://pro-weather.com/docs/getting-started/connect-hardware) speaks the
Ecowitt and Wunderground protocols, which covers every commercial station.
If you are sending readings from something else - a script, a Raspberry Pi, a
home-built sensor, an industrial logger - you can POST JSON to the same URL
instead.

Send it to your ingest URL with a JSON content type:

```bash
curl -X POST https://i.pro-weather.com/i/YOUR-INGEST-TOKEN \
  -H 'Content-Type: application/json' \
  -d '{
    "units": { "temperature": "C", "windspeed": "kph", "rainfall": "mm", "pressure": "hPa" },
    "lastupdated": "2026-08-07T14:30:00Z",
    "temperature": { "outdoor": 18.4, "indoor": 21.0 },
    "humidity":    { "outdoor": 67 },
    "wind":        { "speed": 11.2, "direction": 213, "gust10m": 24.0 },
    "rain":        { "counter": 3.4, "rate": 0.0 },
    "pressure":    { "sealevel": 1013.2, "absolute": 998.4 },
    "solar":       { "irradiation": 286, "uvi": 2 }
  }'
```

You get back `success` when the reading was accepted.

## You declare the units

This is the part that matters. Every other upload protocol fixes its units
into the field names, so `tempf` is always Fahrenheit whether you like it or
not. Here you say what you are sending, and we convert.

| Group | Accepted values |
| --- | --- |
| `temperature` | `C`, `F` |
| `windspeed` | `mph`, `ms`, `kph`, `knots` |
| `rainfall` | `mm`, `in` |
| `pressure` | `hPa`, `mb`, `kPa`, `inHg` |
| `lightning` | `km`, `mi` |

**A reading with no declared unit is dropped, not guessed.** If you send a
temperature of `20` and no `units.temperature`, we do not assume: read as
Fahrenheit that is a 31-degree error, and a station that is quietly 31 degrees
wrong is worse than one showing nothing. The rest of the payload still lands.
The same applies to a unit we do not recognise.

## Fields

Everything is optional except that you must send at least one real reading.
Anything you leave out is treated as "no sensor", not as zero.

| Field | Contents |
| --- | --- |
| `lastupdated` | ISO 8601, unix seconds, or `"now"`. An unusable value falls back to arrival time. |
| `temperature` | `outdoor`, `indoor`, `dewpoint` |
| `humidity` | `outdoor`, `indoor` (percent) |
| `wind` | `speed`, `gust10m`, `direction` (degrees) |
| `rain` | `counter` (today's total), `year`, `rate` (per hour) |
| `pressure` | `sealevel`, `absolute` |
| `solar` | `irradiation` (W/m²), `uvi` |
| `co2` | `co2`, `co2_24h`, `pm2p5`, `pm2p5avg24h`, `pm10`, `pm10avg24h`, `temperature`, `humidity` |
| `lightning` | `distance`, `strikes`, `time` |

Multi-channel sensors are arrays, each entry carrying its own `index`
(1-16):

| Field | Entry shape |
| --- | --- |
| `extratemp` | `{ index, temperature, humidity }` |
| `usertemp` | `{ index, temperature }` |
| `soiltemp` | `{ index, temperature }` |
| `soilmoisture` | `{ index, value }` (percent) |
| `leafwetness` | `{ index, value }` |
| `airquality` | `{ index, pm2p5, pm2p5avg24h, pm10, pm10avg24h }` |

## Notes

- **Dew point, feels-like, wind chill, heat index and cloud base are derived
  for you** from temperature, humidity and wind. Send them only if you have
  measured them; anything you send wins over the calculated value.
- Readings outside a plausible range for the sensor are dropped individually,
  so one stuck channel cannot poison a whole upload.
- Post as often as you like. Readings are archived at roughly five-minute
  intervals, matching the rest of the platform, so a faster cadence gives you
  a livelier page without inflating your history.
- The envelope deliberately matches the JSON station format documented for
  [CumulusMX](https://cumuluswiki.org/a/Main_Page), so if you already emit that
  payload you can point it here without changing anything.
