Command_line sensor with CURL

I want to create a command_line sensor that uses a CURL call. If I go to the HA Terminal, I can sent the command and get a JSON returned,

$ curl -X GET https://api.carbonintensity.org.uk/regional/postcode/LE16 -H 'Accept: application/js'
    #{"data":[{"regionid":9,"dnoregion":"WPD East Midlands","shortname":"East Midlands","postcode":"LE16","data":[{"from":"2025-06-16T07:00Z","to":"2025-06-16T07:30Z","intensity":{"forecast":206,"index":"high"},"generationmix":[{"fuel":"biomass","perc":27.9},{"fuel":"coal","perc":0},{"fuel":"imports","perc":3.5},{"fuel":"gas","perc":43.8},{"fuel":"nuclear","perc":0.6},{"fuel":"other","perc":0},{"fuel":"hydro","perc":0},{"fuel":"solar","perc":13.4},{"fuel":"wind","perc":10.7}]}]}]}[core-ssh ~]

But I just can’t figure how to translate this into YAML

- sensor:
    command: "curl -X GET https://api.carbonintensity.org.uk/regional/postcode/LE16 -H 'Accept: application/json'"
    name: Carbon Intensity UK
    unique_id: carbonintensityuk
    #unit_of_measurement:  string (Optional) Defines the unit of measurement of the sensor, if any.
    value_template: '{{ value_json.data.date.intensity.forecast | float | round(0) }}''
    #string (Optional) Defines a template to extract a value from the payload.
    #availability template (Optional, default: true) Defines a template to get the available state of the entity. If the template either fails to render or returns True, "1", "true", "yes", "on", "enable", or a non-zero number, the entity will be available. If the template returns any other value, the entity will be unavailable. If not configured, the entity will always be available. Note that string comparisons are not case sensitive; "TrUe" and "yEs" are allowed.
    #device_class device_class (Optional) Sets the class of the device, changing the device state and icon that is displayed on the UI (see below). It does not set the unit_of_measurement.
    #state_class string (Optional) The state_class of the sensor. This will display the value based on the Number Format defined in the user profile.
    scan_interval integer: 300

I’d be happy to thrash around for a bit, but I can’t work out how to debug this.

Do you, or do you actually want a sensor for the carbon intensity?

If the latter, I’d suggest using a more “integrated” Rest sensor instead [docs for the Rest integration which I use out of personal preference as opposed to the platform, but you can use either].

rest:
  - resource: https://api.carbonintensity.org.uk/regional/postcode/LE16
    scan_interval: 600
    sensor:
      - name: "East Midlands carbon intensity"
        unique_id: d5fe443b-d1ad-43f1-86cf-2de4bafae025
        unit_of_measurement: 'g/kWh'
        availability: "{{ value_json is defined }}"
        value_template: "{{ value_json['data'][0]['data'][0]['intensity']['forecast'] }}"

Your problem, however, appears to have been your value_template which was close, but sadly that’s not good enough in computing.

Data structure is:

{'data':
  [ {'regionid': 9,
     'dnoregion': 'WPD East Midlands',
     'shortname': 'East Midlands',
     'postcode': 'LE16',
     'data': [ {'from': '2025-06-16T07:00Z',
                'to': '2025-06-16T07:30Z',
                'intensity': {'forecast': 206, 'index': 'high'},
                'generationmix': [ {'fuel': 'biomass', 'perc': 27.9},
                                   {'fuel': 'coal', 'perc': 0},
                                   {'fuel': 'imports', 'perc': 3.5},
                                   {'fuel': 'gas', 'perc': 43.8},
                                   {'fuel': 'nuclear', 'perc': 0.6},
                                   {'fuel': 'other', 'perc': 0},
                                   {'fuel': 'hydro', 'perc': 0},
                                   {'fuel': 'solar', 'perc': 13.4},
                                   {'fuel': 'wind', 'perc': 10.7} ]
                  } ]
      } ]
}

The 206 figure that you want is:

  • under the data key,
  • in the first ([0]) list element,
  • under the data key,
  • in the first ([0]) list element,
  • under the intensity key,
  • under the forecast key.

Here’s my template, using bracket notation as opposed to dot notation (always safer):

{{ value_json['data'][0]['data'][0]['intensity']['forecast'] }}

Yours was:

{{ value_json.data.date.intensity.forecast }}

You had omitted the list indices (the [0], or .0 in dot notation), and mis-spelled the second data as date. You also don’t need the float or round filters here.

You can experiment in Developer Tools / Template. Here, I set the variable value_json to the response then test my template:

{% set value_json = {"data":[{"regionid":9,"dnoregion":"WPD East Midlands","shortname":"East Midlands","postcode":"LE16","data":[{"from":"2025-06-16T07:00Z","to":"2025-06-16T07:30Z","intensity":{"forecast":206,"index":"high"},"generationmix":[{"fuel":"biomass","perc":27.9},{"fuel":"coal","perc":0},{"fuel":"imports","perc":3.5},{"fuel":"gas","perc":43.8},{"fuel":"nuclear","perc":0.6},{"fuel":"other","perc":0},{"fuel":"hydro","perc":0},{"fuel":"solar","perc":13.4},{"fuel":"wind","perc":10.7}]}]}]} %}
{{ value_json['data'][0]['data'][0]['intensity']['forecast'] }}

A good trick, if you’re unsure where the problem lies in a config that’s pulling in external data, is to set the value_template to "{{ value[:250] }}" with no unit of measurement or device class.

That way, the entity state should be set to the first 250 characters of the response, which will tell you if the communication step is working, indicating that your problem lies downstream from there.

1 Like

Hello @Troon that’s a really, really helpful reply. Thank you for taking the time and trouble to explain it to me. Cheers!

1 Like

Following success here, thanks @Troon I have created some more sensors relating to the generationmix from the json.
However it occurred to me that a more efficient way to do this would be add attributes to the intensity sensor e.g. biomass, coal, solar, wind, etc.
Something like:

- resource_template: 'https://api.carbonintensity.org.uk/regional/postcode/{{states("input_text.postcode_outward")}}'
  scan_interval: 600
  headers:
    Accept: "application/json"
    Content-Type: "application/json"
  sensor:
    - name: "Carbon Intensity PostCode"
      unique_id: carbonintensitypostcode
      unit_of_measurement: 'g/kWh'
      icon: mdi:molecule-co2
      availability: "{{ value_json is defined }}"
      value_template: "{{ value_json['data'][0]['data'][0]['intensity']['forecast'] }}"
      json_attributes:
        biomass:>-
            {{ value_json['data'][0]['data'][0]['generationmix'][0]['perc'] }}
        coal:>-
            {{ value_json['data'][0]['data'][0]['generationmix'][1]['perc'] }}
        imports:>-
            {{ value_json['data'][0]['data'][0]['generationmix'][2]['perc'] }}
        gas:>-
            {{ value_json['data'][0]['data'][0]['generationmix'][3]['perc'] }}
        nuclear:>-
            {{ value_json['data'][0]['data'][0]['generationmix'][4]['perc'] }}
        other:>-
            {{ value_json['data'][0]['data'][0]['generationmix'][5]['perc'] }}
        hydro:>-
            {{ value_json['data'][0]['data'][0]['generationmix'][6]['perc'] }}
        solar:>-
            {{ value_json['data'][0]['data'][0]['generationmix'][7]['perc'] }}
         wind:>-
            {{ value_json['data'][0]['data'][0]['generationmix'][8]['perc'] }}

This doesn’t work and I can’t find any working examples to play with.

The json_attributes feature expects the data laid out more like:

{'biomass': 27.9, 'coal': 0, 'wind': 10.7}

With the list of dicts data structure that we have, the closest you could get in one step is:

json_attributes_path: "$.data[0].data[0]"
json_attributes:
  - generationmix

…which would give you an attribute called generationmix with the list of fuel type / percentage dictionaries in it. You could then do e.g.

{{ (state_attr('sensor.carbon_intensity_postcode','generationmix')|selectattr('fuel','==','nuclear')|first)['perc'] }}

in a template sensor which should give you the 0.6 figure from the data set above. However…

If you’re using the REST integration, what you’re doing is efficient as it’s creating all the sensors from a single call to the resource.

Here’s my version, creating sensors with easier-to-access states (that also have units and can have icons assigned), and with the fuel type looked up by name rather than assuming the order of the list:

- resource_template: 'https://api.carbonintensity.org.uk/regional/postcode/{{states("input_text.postcode_outward")}}'
  scan_interval: 600
  headers:
    Accept: "application/json"
    Content-Type: "application/json"
  sensor:
    - name: "Carbon Intensity PostCode"
      unique_id: b06ccd96-d344-47cf-8fbe-5e3032ae159f
      unit_of_measurement: 'g/kWh'
      icon: mdi:molecule-co2
      availability: "{{ value_json is defined }}"
      value_template: "{{ value_json['data'][0]['data'][0]['intensity']['forecast'] }}"

    - name: "Gen mix biomass"
      unique_id: 163ee2d4-6d63-48b4-99e7-270da73711dd
      unit_of_measurement: '%'
      availability: "{{ value_json is defined }}"
      value_template: "{{ (value_json['data'][0]['data'][0]['generationmix']|selectattr('fuel','==','biomass')|first)['perc'] }}"

    - name: "Gen mix coal"
      unique_id: 5a779a79-25fa-41e7-a3a0-11ac79e71849
      unit_of_measurement: '%'
      availability: "{{ value_json is defined }}"
      value_template: "{{ (value_json['data'][0]['data'][0]['generationmix']|selectattr('fuel','==','coal')|first)['perc'] }}"

    - name: "Gen mix imports"
      unique_id: 37d7c341-c112-4736-b1f8-b1a9a9d7dc2a
      unit_of_measurement: '%'
      availability: "{{ value_json is defined }}"
      value_template: "{{ (value_json['data'][0]['data'][0]['generationmix']|selectattr('fuel','==','imports')|first)['perc'] }}"

    - name: "Gen mix gas"
      unique_id: 5a8751c6-a9d7-4dd1-aa29-57d46032a62e
      unit_of_measurement: '%'
      availability: "{{ value_json is defined }}"
      value_template: "{{ (value_json['data'][0]['data'][0]['generationmix']|selectattr('fuel','==','gas')|first)['perc'] }}"

    - name: "Gen mix nuclear"
      unique_id: 5074f3c4-f607-4c37-94ca-934642ae465e
      unit_of_measurement: '%'
      availability: "{{ value_json is defined }}"
      value_template: "{{ (value_json['data'][0]['data'][0]['generationmix']|selectattr('fuel','==','nuclear')|first)['perc'] }}"

    - name: "Gen mix other"
      unique_id: 54a1bad1-2506-4c3a-9d8d-cc7e17c18a2f
      unit_of_measurement: '%'
      availability: "{{ value_json is defined }}"
      value_template: "{{ (value_json['data'][0]['data'][0]['generationmix']|selectattr('fuel','==','other')|first)['perc'] }}"

    - name: "Gen mix hydro"
      unique_id: 995b170f-9043-4dc6-8549-87533b2f1a4a
      unit_of_measurement: '%'
      availability: "{{ value_json is defined }}"
      value_template: "{{ (value_json['data'][0]['data'][0]['generationmix']|selectattr('fuel','==','hydro')|first)['perc'] }}"

    - name: "Gen mix solar"
      unique_id: 9a35a1c4-c1ac-42cf-9c0c-99968d0cdd4f
      unit_of_measurement: '%'
      availability: "{{ value_json is defined }}"
      value_template: "{{ (value_json['data'][0]['data'][0]['generationmix']|selectattr('fuel','==','solar')|first)['perc'] }}"

    - name: "Gen mix wind"
      unique_id: c56587e9-7d60-4497-ba37-1e5d8e3fa3da
      unit_of_measurement: '%'
      availability: "{{ value_json is defined }}"
      value_template: "{{ (value_json['data'][0]['data'][0]['generationmix']|selectattr('fuel','==','wind')|first)['perc'] }}"
1 Like

That makes a lot of sense and doesn’t involve too much extra learning, apart from finding an mdi-icon for coal. Thanks again!