Loading...
Author Cloudapp
E.G.

Pull Cloud-API Data into Home Assistant: the REST Sensor Explained (One Endpoint, Many Sensors)

June 26, 2026
Table of Contents

Sooner or later almost every Home Assistant setup has a device with no native integration. A wallbox, an inverter, in my case a my-PV hot-water heating element — the vendor app shows every value nicely, but nothing of it appears in HA. What there almost always is, though, is a cloud or local JSON API: an HTTP endpoint that, on a GET request, returns a JSON object with all the readings. That is exactly where the built-in REST sensor comes in.

The trick the official docs cover only briefly: from a single JSON endpoint you can build a whole dozen clean HA sensors — one per field, each with its own name, unit and device_class. In this post I show the pattern on my real my-PV cloud API (which has no native HA integration), but every single line transfers to any REST/JSON API you like.

What the REST sensor does

The platform: rest sensor polls a URL on a fixed interval, gets JSON back, and turns one field of that JSON into a sensor state. You give it four things: the resource (the URL), authentication via headers if needed, a value_template that pulls the field you want out of the JSON, and a scan_interval that sets how often it polls. At its core, that's all it takes.

One thing to understand: each REST sensor makes its own HTTP request. That matters as soon as you want several fields from the same endpoint — more on that below.

The first sensor: one field from the API

Let's start with the simplest case: a single value from the my-PV cloud API. The API answers the /data endpoint with a JSON object that contains, among others, surplus (surplus power). The sensor pulls exactly that field out.

- platform: rest
  name: My-PV Surplus Power
  resource: "https://api.my-pv.com/api/v1/device/<device-id>/data"
  method: GET
  headers:
    Authorization: "!secret my_pv_api_token"
  value_template: "{{ value_json.surplus }}"
  unit_of_measurement: "W"
  scan_interval: 60

Three things happen here. value_json is the parsed JSON response — with value_json.surplus you access the field, just like in any other HA template. You never store the token in clear text; you keep it as !secret my_pv_api_token in your secrets.yaml and HA sets it as the Authorization header. And scan_interval: 60 polls once a minute — deliberately conservative, so you don't run into the cloud's rate limit.

One endpoint, many sensors

Now the actual pattern: the same /data response carries more than just surplus — a whole bundle of fields, including boiler temperature, battery state of charge, house consumption. For each you define another REST sensor with the same resource but a different value_template. That's how one endpoint becomes a whole dashboard.

value_template: "{{ value_json.temp1 | float / 10 }}"   # boiler temp in °C
# ...
value_template: "{{ value_json.m2soc }}"                 # battery state of charge %
# ...
value_template: "{{ value_json.m0sum }}"                 # house consumption W

Note the first entry: temp1 | float / 10. This is the single most common gotcha with vendor APIs — many return temperatures as tenths of a degree in integer form (550 instead of 55.0). With | float / 10 you scale that to the correct value right in the template. You know the counterpart from the Modbus world, where the scale factor does the same job — with register sensors HA scales for you, with REST you do it yourself in the value_template.

Making states readable: 0/1 to On/Off

Some fields aren't measurements but status flags. The my-PV API has a second endpoint, /setup, that returns the configuration — including bstmode, the boost status, as 0 or 1. With a small if in the value_template you turn that into a plain-text sensor that shows "On" or "Off" instead of a bare number.

- platform: rest
  name: My-PV AC THOR Boost Status
  resource: "https://api.my-pv.com/api/v1/device/<device-id>/setup"
  method: GET
  headers:
    Authorization: "!secret my_pv_api_token"
  value_template: "{{ 'On' if value_json.setup.bstmode == 1 else 'Off' }}"
  scan_interval: 30

Two things are new here. First, value_json.setup.bstmode — the response is nested, the value you want sits under a setup object, so you just extend the path with a dot. Second: if you query both /data and /setup, those are two different URLs and therefore two separate REST sensors, each with its own scan_interval.

Polling interval and rate limits

The scan_interval is the most important dial for being polite to the cloud. Each sensor polls independently — five sensors on the same endpoint at scan_interval 30 is ten requests per minute. Few vendors publish their rate limits; when in doubt, start at 60 seconds and only go lower if you genuinely need the value more often. For values that barely change (daily yield, configuration), even 300 seconds is perfectly fine.

If you need several fields from exactly the same endpoint and want to cut request load, look at the rest: platform (rather than platform: rest): there you define the endpoint once and hang several sensor blocks with their own value_template beneath it — one request, many sensors. The single-sensor pattern shown here is the better starting point, because each sensor is completely self-contained.

From reading to writing

The REST sensor is read-only. As soon as you want to actually control something over the same cloud API — in my setup, turning the AC·THOR heating element on and off, because its Modbus write access is blocked at the device — you need the writing counterpart: a shell_command with curl and PUT instead of GET. How I solved that for the my-PV control I cover in a separate post. The REST sensor provides the feedback for it: write with PUT, then verify with the /setup sensor that the value actually landed.

Frequently asked questions

What exactly is value_json?

value_json is the already-parsed JSON response of the endpoint, which HA hands you inside the value_template. You navigate it with dot notation: value_json.surplus for a top-level field, value_json.setup.bstmode for a nested one. If the API returns an array, you index with value_json[0]. If you don't know what the response looks like, hit the endpoint once with curl and inspect the JSON.

My sensor shows "unknown" — what's wrong?

Almost always one of three things: the value_template path doesn't match the real JSON structure (often a forgotten nested object), authentication fails (token wrong, or expected as Bearer instead of a bare value), or the API doesn't return JSON for that call at all. As a test, set value_template to {{ value }} instead of {{ value_json.field }} — you then see the raw response in the state and immediately know whether it's an auth or a path problem.

How do I scale raw values correctly?

In the value_template, with the normal Jinja filters. Divide tenths of a degree by 10 ({{ value_json.temp1 | float / 10 }}), milliwatts by 1000, and so on. The important bit is the | float (or | int): it cleanly casts the value to a number before you do math, otherwise some APIs give you string concatenation instead of a division. Set device_class and unit_of_measurement to match, so the value is sorted correctly in the dashboard and in statistics.

Does this work for a local API without a cloud?

Yes, identically. resource then simply points at the device's local address (e.g. http://<device-ip>/status) instead of a cloud host. For local devices without authentication you drop the headers block; everything else is the same. That's exactly what makes the REST sensor so universal — vendor cloud or a small HTTP server on your LAN, the pattern stays the same.

Related articles