Skip to content
Get started

Native API Component

The ESPHome native API is used to communicate with clients directly, with a highly-optimized network protocol. Currently, only the ESPHome tool, Home Assistant and ioBroker use this native API.

After adding an api: line to your ESPHome configuration you can go to the Home Assistant web interface and navigate to the “Integrations” screen in the “Configuration” panel. Then wait for the ESPHome device to show up under the discovered section (can take up to 5 minutes) or add the device manually by choosing “ESPHome” from the integration overview and entering “<NODE_NAME>.local” or the IP address of the unit in the “Host” field.

The ESPHome native API is based on a custom TCP protocol using protocol buffers. You can find the protocol data structure definitions here: api.proto A Python library that implements this protocol is aioesphomeapi.

NOTE

Actions were previously called Services. ESPHome changed the name in line with Home Assistant but will continue to support YAML with services and homeassistant.service for the foreseeable future. Documentation will only refer to Actions.

# Example configuration entry
api:
# Example with more options
api:
port: 6053
batch_delay: 50ms # Reduce latency for real-time applications
listen_backlog: 2 # Allow 2 pending connections in queue
max_connections: 6 # Allow up to 6 simultaneous connections
max_send_queue: 10 # Maximum queued messages per connection before disconnect
encryption:
key: "YOUR_ENCRYPTION_KEY_HERE"
reboot_timeout: 30min
  • port (Optional, int): The port to run the API server on. Defaults to 6053.

  • listen_backlog (Optional, int): The maximum number of pending connections in the listen queue. Must be between 1 and 10. Defaults to 1 for ESP8266/RP2040, 4 for ESP32 and other platforms. Lower values use less memory but may reject connections during bursts.

  • max_connections (Optional, int): The maximum number of simultaneous API connections allowed. Must be between 1 and 20. Defaults to:

    • 4 for ESP8266/RP2040,
    • 5 for ESP32/BK72xx/RTL87xx/LN882x,
    • 8 for the host platform.

    NOTE

    max_connections is baked into the compiled firmware as a compile-time constant — the connection slots are statically allocated, so every slot costs a small amount of static RAM whether it is used or not, and the firmware must be re-flashed to change the value. In addition, each active API connection consumes approximately 500–1000 bytes of RAM while connected. ESP8266 and RP2040 devices have limited RAM available (ESP8266 typically has around 40KB of free RAM after boot, but this can drop to under 20KB once sensors and other components are configured; RP2040 uses LWIP raw sockets with similar constraints), so be careful not to set this value too high or it may cause out-of-memory crashes. The defaults are set to balance memory usage with allowing multiple simultaneous connections.

  • max_send_queue (Optional, int): The maximum number of messages that can be queued for sending per connection before the connection is dropped. Must be between 1 and 64. Defaults to:

    • 5 for ESP8266/RP2040,
    • 8 for ESP32/BK72xx/LN882x/nRF52/RTL87xx,
    • 16 for host platform.

    This prevents memory exhaustion when a client is slow or network-stalled. Each queued message uses approximately 8-12 bytes of overhead plus the message size.

    NOTE

    When the send queue is full for a connection, the device will log an error and disconnect that client to prevent out-of-memory crashes. Slow clients, poor WiFi connections causing retries, or network congestion may trigger this. Increase this value if legitimate clients are being disconnected, but be mindful of memory constraints on embedded devices.

  • encryption (Optional): If present, encryption will be enabled for the API. Using encryption helps to secure the communication between the device running ESPHome and the connected client(s).

    • key (Optional, string): A 32-byte base64-encoded string to be used as the encryption key. If not provided, the key may be set at runtime, but encryption will not be used until it is set.

      If you need a key, you can use the key below; it is randomly generated by your browser each time this page loads:

NOTE

Support for configuring the encryption key on-the-fly will be implemented in a future release of Home Assistant.

TIP

For comprehensive security guidance including API encryption best practices, see the Security Best Practices guide.

  • actions (Optional, list): A list of user-defined actions. See User-defined Actions.
  • batch_delay (Optional, Time): The delay time for batching multiple state update messages together to reduce network overhead. Lower values send updates sooner but use more network packets, while higher values batch more efficiently but add latency. Must be between 0ms and 65535ms (65.535 seconds). Defaults to 100ms.

NOTE

Setting batch_delay: 0ms enables immediate sending mode for state updates. This is useful for applications that require real-time responsiveness, such as IR remote binary sensors where rapid ON→OFF transitions must be preserved. However, this will increase network traffic and may impact WiFi performance with many rapidly-changing sensors. Only use this setting when necessary.

  • custom_services (Optional, boolean): Enable compilation of custom API services for external components that use the C++ CustomAPIDevice class. Only needed when external components register their own services via the native API. Defaults to false.

  • homeassistant_services (Optional, boolean): Enable compilation of Home Assistant service call support for external components that use the C++ CustomAPIDevice::call_homeassistant_service() or CustomAPIDevice::fire_homeassistant_event() methods. This is automatically enabled when using homeassistant.service or homeassistant.event actions, or the homeassistant platform for number or switch components. Only needs to be manually set when external components call Home Assistant services without using the built-in actions. Defaults to false.

  • homeassistant_states (Optional, boolean): Enable compilation of Home Assistant state subscription support for external components that use the C++ CustomAPIDevice::subscribe_homeassistant_state() method. This is automatically enabled when using any homeassistant platform components (sensor, binary_sensor, text_sensor, switch, or number). Only needs to be manually set when external components subscribe to Home Assistant states without using the built-in components. Defaults to false.

  • reboot_timeout (Optional, Time): The amount of time to wait before rebooting when no client connects to the API. This is needed because sometimes the low level ESP functions report that the ESP is connected to the network, when in fact it is not - only a full reboot fixes it. Only a client that completes authentication counts; a connection that never does, such as a port scan or a client with a stale encryption key, does not hold off the reboot. Can be disabled by setting this to 0s. Defaults to 15min.

  • wizard (Optional): A setup wizard that Home Assistant shows when the device is added. See Wizard.

  • id (Optional, ID): Manually specify the ID used for code generation.

  • on_client_connected (Optional, Action): An automation to perform when a client connects to the API. See on_client_connected Trigger.

  • on_client_disconnected (Optional, Action): An automation to perform when a client disconnects from the API. See on_client_disconnected Trigger.

  • outgoing_connection (Optional): Have the device open the API connection itself when no dial-back client is connected. Requires encryption. See Outgoing Connections.

    • host (Optional, IP address): The address to dial. When not set, the device remembers the address of the last client that declared itself a dial-back target (such as Home Assistant) and dials that.
    • port (Optional, integer): The TCP port the dial-back target listens on. Defaults to 6054.
    • delay (Optional, Time): How long to give a dial-back client to connect in on its own, both after boot and after one disconnects, before the device dials out. Devices with deep sleep skip the wait and dial as soon as the network is up on every wake, since the awake window is short. Defaults to 60s.

IMPORTANT

Outgoing connections require API encryption; this is a hard requirement by design and plaintext support is not planned. Because the device opens the connection, the traffic can leave the local network entirely, so the shared encryption key is the only trustworthy way the device and the client can verify each other. The option refuses to configure without encryption, and only key-verified clients can become dial-back targets.

Normally the client, such as Home Assistant, opens the TCP connection to the device. With outgoing_connection configured, the device notices when no dial-back client is connected and, after delay, opens the connection itself; the protocol then runs over that connection unchanged and both sides still verify each other with the encryption key. This lets a device stay connected when the client can no longer reach it, for example after network or firewall changes, as long as the device can still reach the client.

A client declares itself a dial-back target when it connects; the device remembers the address of the last such client across reboots, or always dials host when one is set. Home Assistant 2026.10.0 or later accepts these connections automatically for every device with an encryption key; there is nothing to configure on its side.

NOTE

The device must be added to the client once while it is reachable: dial-ins are only accepted for devices the client already knows, since an unknown caller cannot be verified. After that first setup the device can stay permanently unreachable from the client.

# Example configuration entry
api:
encryption:
key: !secret api_encryption_key
outgoing_connection:

Before using any of the actions below, you’ll need to tell Home Assistant to allow your device to perform actions.

NOTE

Starting with ESPHome 2025.10.0, you can configure actions to receive and process responses from Home Assistant using capture_response, on_success, and on_error. See Action Response Handling for details.

Open the ESPHome integration page on your Home Assistant instance:

Open your Home Assistant instance and show the ESPHome integration.

Then:

  1. Find your device in the device list

  2. Click the “configure” button next to it

  3. Check the “Allow the device to perform Home Assistant actions” box

  4. Then click “submit”

NOTE

Be sure to follow the instructions above to tell Home Assistant to allow your device to perform actions.

When using the native API with Home Assistant, you can create events in the Home Assistant event bus straight from ESPHome Automations.

# In some trigger
on_...:
# Simple
- homeassistant.event:
event: esphome.button_pressed
data:
message: Button was pressed
  • event (Required, string): The event to create - must begin with esphome.

  • data (Optional, mapping): Optional static data to pass along with the event.

  • data_template (Optional, mapping): Optional template data to pass along with the event. This is evaluated on the Home Assistant side with Home Assistant’s templating engine.

  • variables (Optional, mapping): Optional variables that can be used in the data_template. A value tagged with !lambda is a lambda evaluated on the device before sending the request; any other value is sent as a static string.

NOTE

Be sure to follow the instructions above to tell Home Assistant to allow your device to perform actions.

When using the native API with Home Assistant, you can perform Home Assistant actions straight from ESPHome Automations.

# In some trigger
on_...:
# Simple
- homeassistant.action:
action: notify.html5
data:
message: Button was pressed
# With templates and variables
- homeassistant.action:
action: notify.html5
data:
title: New Humidity
data_template:
message: The humidity is {{ my_variable }}%.
variables:
my_variable: !lambda |-
return id(my_sensor).state;
  • action (Required, string, templatable): The Home Assistant Action to perform.

  • data (Optional, mapping): Optional static data to perform the action with.

  • data_template (Optional, mapping): Optional template data to perform the action with. This is evaluated on the Home Assistant side with Home Assistant’s templating engine.

  • variables (Optional, mapping): Optional variables that can be used in the data_template. A value tagged with !lambda is a lambda evaluated on the device before sending the request; any other value is sent as a static string.

  • capture_response (Optional, boolean): Enable capturing the response from the Home Assistant action call. When enabled, on_success must be configured. Defaults to false.

  • response_template (Optional, string, templatable): Optional Jinja template to process the action response data. This template is evaluated on the Home Assistant side with Home Assistant’s templating engine. Requires capture_response: true.

  • on_success (Optional, Automation): Optional automation to execute when the Home Assistant action call succeeds. When capture_response: true, the response data is available as a response variable of type JsonObjectConst. See Action Response Handling.

  • on_error (Optional, Automation): Optional automation to execute when the Home Assistant action call fails. See Action Response Handling.

NOTE

Home Assistant starts listening for device actions shortly after the API connection is established, so actions triggered right at connection time (for example from on_client_connected or on_time_sync) can fire before Home Assistant is ready for them. Such actions are not delivered and ESPHome logs a warning (versions before 2026.7.0 dropped them silently). The same applies to homeassistant.event. If you need to perform an action as soon as a connection is made, add a short delay before it so Home Assistant has time to start listening.

Data structures are not possible, but you can create a script in Home Assistant and call with all the parameters in plain format.

# Home Assistant Configuration
script:
...
set_light_rgb:
alias: 'ESPHome RGB light set'
sequence:
- action: light.turn_on
data_template:
entity_id: '{{ light_name }}'
rgb_color:
- '{{ red }}'
- '{{ green }}'
- '{{ blue }}'

Then, in ESPHome:

# In some trigger
on_...:
- homeassistant.action:
action: script.set_light_rgb
data:
light_name: 'my_light'
red: '255'
green: '199'
blue: '71'

NOTE

Action response handling is available in ESPHome 2025.10.0 and later.

You can configure actions to receive and process responses from Home Assistant. This enables bidirectional communication where ESPHome can not only call Home Assistant actions but also handle their responses.

Use on_success and on_error to respond to action completion:

on_...:
- homeassistant.action:
action: light.toggle
data:
entity_id: light.demo_light
on_success:
- logger.log: "Toggled demo light"
on_error:
- logger.log: "Failed to toggle demo light"

To capture and process response data from actions, set capture_response: true. When enabled, on_success must be configured and the response data is available as a JsonObjectConst variable named response.

# Example: Get weather forecast and parse JSON response
on_...:
- homeassistant.action:
action: weather.get_forecasts
data:
entity_id: weather.forecast_home
type: hourly
capture_response: true
on_success:
- lambda: |-
JsonObjectConst next_hour = response["response"]["weather.forecast_home"]["forecast"][0];
float next_temperature = next_hour["temperature"].as<float>();
ESP_LOGI("weather", "Temperature next hour: %.1f", next_temperature);

Use response_template to extract and format data from complex responses using Home Assistant’s Jinja templating engine. This requires capture_response: true.

# Example: Extract temperature using a template
on_...:
- homeassistant.action:
action: weather.get_forecasts
data:
entity_id: weather.forecast_home
type: hourly
capture_response: true
response_template: "{{ response['weather.forecast_home']['forecast'][0]['temperature'] }}"
on_success:
- lambda: |-
float temperature = response["response"].as<float>();
ESP_LOGI("weather", "Temperature next hour: %.1f", temperature);

When response_template is used, the processed result is available in response["response"].

NOTE

Be sure to follow the instructions above to tell Home Assistant to allow your device to make action calls.

When using the native API with Home Assistant, you can push tag_scanned to Home Assistant straight from ESPHome Automations.

# In some trigger
on_...:
# Simple
- homeassistant.tag_scanned: some-tag
  • tag (Required, string, templatable): The id of the scanned tag

This trigger is activated each time a client connects to the API. Two variables of type std::string are available for use by actions called from within this trigger:

  • client_address : the IP address of the client that connected
  • client_info : the name of the client that connected
api:
# ...
on_client_connected:
- logger.log:
format: "Client %s connected to API with IP %s"
args: ["client_info.c_str()", "client_address.c_str()"]

This trigger is activated each time the API disconnects from the API. Two variables of type std::string are available for use by actions called from within this trigger:

  • client_address : the IP address of the client that disconnected
  • client_info : the name of the client that disconnected
api:
# ...
on_client_disconnected:
- logger.log: "API client disconnected!"

This Condition checks if at least one client is connected to the ESPHome native API.

  • state_subscription_only (Optional, boolean, templatable): If enabled, only counts clients that have subscribed to entity state updates. This filters out logger-only connections (such as esphome logs command), which can cause false positives when waiting for Home Assistant. Defaults to false.

Check if any client is connected:

on_...:
if:
condition:
api.connected:
then:
- logger.log: Client is connected to API!

The lambda equivalent for this is id(api_id).is_connected().

Check if a client subscribed to entity states is connected (typically Home Assistant):

on_boot:
- wait_until:
condition:
api.connected:
state_subscription_only: true
- logger.log: Home Assistant is connected!
- homeassistant.event:
event: esphome.device_booted

The lambda equivalent for this is id(api_id).is_connected(true).

Use Cases:

  • Use state_subscription_only: false (default) to detect any API connection
  • Use state_subscription_only: true when you need to ensure Home Assistant (or other connections that subscribe to states) is connected before sending events or calling services, preventing errors from logger-only connections

This Condition checks if a Home Assistant entity has been picked for a standalone wizard input. Home Assistant sends the picked entity each time it connects, so after a restart this condition is false until Home Assistant has connected. It only works with standalone inputs (declared with id). A linked input is read through its homeassistant entity.

on_...:
if:
condition:
api.wizard.input_is_set: weather_input
then:
- logger.log: A weather entity has been picked

The lambda equivalent for this is id(weather_input).has_entity_id().

The wizard option describes a setup wizard that Home Assistant shows when the device is added. Instead of a device page with many entities and no guidance, the user is taken through a series of pages that point out the entities that matter for setup and ask for the Home Assistant entities the device needs, such as an outdoor temperature sensor or a weather forecast.

NOTE

The wizard needs Home Assistant 2026.11 or newer. Older versions ignore it and add the device as usual.

api:
wizard:
pages:
- title: Features
entities:
- id: feature_switch
description: Turn on the feature
- title: Sources
inputs:
- entity: outdoor_temperature
description: Outdoor temperature
target:
entity:
- domain: sensor
device_class: temperature

Each page lists entities, inputs, or both:

  • An entity is an entity of the device itself, for example a switch that turns a feature on or off, which the page points out to the user.
  • An input asks the user to pick a Home Assistant entity. Home Assistant keeps the entity ID the user picks and sends it to the device every time it connects. The device does not store it, so after a restart the input is empty until Home Assistant connects. An input is either:
    • linked (entity): it gives the entity ID of a homeassistant platform entity on the device. That entity must not set entity_id, as Home Assistant sets it through the wizard. Supported platforms are sensor, binary sensor, text sensor, number, switch, text, select and button.
    • standalone (id): it only stores an entity ID, for use in lambdas. id(my_input).entity_id() returns the picked entity ID, and id(my_input).has_entity_id() returns whether one has been picked. See also the api.wizard.input_is_set Condition.

Titles and descriptions are sent to Home Assistant as they are, so they can also be Home Assistant translation placeholders such as "[%key:component::example::feature::enable%]".

The wizard is sent to Home Assistant in a single message, and validation fails if it does not fit. In that case, shorten the texts or use fewer pages, entities or filters.

  • pages (Required, list): The pages of the wizard, in the order they are shown. At least one page is required. Each page has these options, and needs at least one of entities and inputs:

    • title (Optional, string): The title of the page. At most 127 characters.

    • description (Optional, string): A text shown at the top of the page. At most 255 characters.

    • entities (Optional, list): Entities of the device to show on the page. Each entry has these options:

      • id (Required, ID): The ID of the entity. The entity must be exposed to Home Assistant, so it cannot be internal, and it must have a name that is not empty.
      • description (Optional, string): A text shown with the entity. At most 255 characters.
    • inputs (Optional, list): Home Assistant entities for the user to pick. Each entry needs exactly one of entity and id, and each input can appear in the wizard only once:

      • entity (Optional, ID): The ID of a homeassistant platform entity on the device to link the input to.

      • id (Optional, ID): The ID of a new standalone input.

      • description (Optional, string): A text shown with the input. At most 255 characters.

      • target (Optional): Limits which Home Assistant entities the user can pick.

        • entity (Required, list): A list of filters. An entity can be picked if it matches any one of them. Each filter needs at least one of these options, and an entity must match all of the options a filter sets:

          • integration (Optional, string): The integration that provides the entity, for example met.
          • domain (Optional, string or list): One or more entity domains, for example sensor.
          • device_class (Optional, string or list): One or more device classes, for example temperature.
          • supported_features (Optional, string or list): One or more supported features, for example weather.WeatherEntityFeature.FORECAST_DAILY.

A linked input can only be set to an entity that its homeassistant platform can work with. Without a target, the user can pick from these domains:

  • switch: automation, fan, humidifier, input_boolean, light, remote, siren and switch
  • number: number
  • text: text and input_text
  • select: select and input_select
  • button: button and input_button

For switch, number, text, select and button inputs with a target, every filter must set domain, and only the domains in the list above are allowed. Sensor, binary sensor and text sensor inputs accept any entity unless a target is set.

This wizard has two pages. The first lets the user turn on a feature of the device. The second asks for an outdoor temperature sensor, which a homeassistant sensor then follows, and for a weather entity, whose daily forecast the device fetches every hour once one has been picked.

api:
wizard:
pages:
- title: Features
description: Choose which features to turn on
entities:
- id: frost_protection
description: Turn the heating on when it freezes outside
- title: Sources
inputs:
- entity: outdoor_temperature
description: Outdoor temperature sensor
target:
entity:
- domain: sensor
device_class: temperature
- id: weather_input
description: Weather forecast
target:
entity:
- domain: weather
supported_features: weather.WeatherEntityFeature.FORECAST_DAILY
switch:
- platform: template
id: frost_protection
name: Frost Protection
optimistic: true
sensor:
- platform: homeassistant
id: outdoor_temperature
interval:
- interval: 1h
then:
- if:
condition:
api.wizard.input_is_set: weather_input
then:
- homeassistant.action:
action: weather.get_forecasts
data:
type: daily
entity_id: !lambda 'return id(weather_input).entity_id().c_str();'
capture_response: true
on_success:
- lambda: |-
JsonObjectConst today =
response["response"][id(weather_input).entity_id().c_str()]["forecast"][0];
ESP_LOGI("weather", "High today: %.1f", today["temperature"].as<float>());

The homeassistant.action needs Home Assistant to allow the device to perform actions, see Actions.

It is also possible to get data from Home Assistant to ESPHome with user-defined actions. When you declare actions in your ESPHome YAML file, they will automatically show up in Home Assistant and you can call them directly.

NOTE

User-defined actions can also send responses back to the calling client using the api.respond action. See Action Responses for details.

# Example configuration entry
api:
actions:
- action: start_laundry
then:
- switch.turn_on: relay
- delay: 3h
- switch.turn_off: relay

For example with the configuration seen above, after uploading you will see an action called esphome.livingroom_start_laundry (livingroom is the node name) which you can then call.

Additionally, you can also transmit data from Home Assistant to ESPHome with this method:

# Example configuration entry
api:
actions:
- action: start_effect
variables:
my_brightness: int
my_effect: string
then:
- light.turn_on:
id: my_light
brightness: !lambda 'return my_brightness;'
effect: !lambda 'return my_effect;'

Using the variables key you can tell ESPHome which variables to expect from Home Assistant. For example the action seen above would be executed with something like this:

# Example Home Assistant Action
action: esphome.livingroom_start_effect
data_template:
my_brightness: "{{ states.brightness.state }}"
my_effect: "Rainbow"

Then each variable you define in the variables section is accessible in the automation triggered by the user-defined action through the name you gave it in the variables section (note: this is a local variable, so do not wrap it in id(...) to access it).

There are currently 4 types of variables:

  • bool: A boolean (ON/OFF). C++ type: bool
  • int: An integer. C++ type: int /int32_t
  • float: A floating point number. C++ type: float
  • string: A string. C++ type: std::string

Each of these also exist in array form:

  • bool[]: An array of boolean values. C++ type: std::vector<bool>
  • … - Same for other types.

Actions and their variables can carry metadata. Clients such as Home Assistant use it to describe the action and its fields in the UI:

# Example configuration entry
api:
actions:
- action: play_buzzer
description: Play a melody on the buzzer
variables:
song_str:
type: string
description: Melody in RTTTL format
example: "two_short:d=4,o=5,b=100:16e6,16e6"
then:
- rtttl.play:
rtttl: !lambda "return song_str;"
  • description (Optional, string): Description of the action.
  • variables (Optional, mapping): Each variable may be given as name: type or as a mapping:
    • type (Required, string): The variable type, one of the types listed above.
    • description (Optional, string): Description of the variable.
    • example (Optional, string): Example value for the variable. Always a string, so quote values for numeric variables, e.g. example: "420".

The name: type shorthand and the mapping form can be mixed within the same variables: block.

User-defined actions can send responses back to the calling client (such as Home Assistant). This enables bidirectional communication where actions can report success/error status or return structured JSON data.

The response behavior is controlled by the supports_response option, which can be set explicitly or auto-detected based on your action configuration:

  • none (default): No response is sent. The action is “fire and forget”.
  • status: Reports success/error status without data. Auto-detected when api.respond is used without data:.
  • optional: Returns JSON data when the client requests it. Auto-detected when api.respond is used with data:.
  • only: Always returns JSON data. Must be set explicitly. Use this for query-type actions.
  • supports_response (Optional, string): The response mode for this action. One of none, status, optional, or only. If not specified, the mode is auto-detected based on api.respond usage in the action.

When supports_response is not none, the following variables are available in the action:

  • call_id (uint32_t): A unique identifier for this action call. Used internally by api.respond.
  • return_response (bool): Only available in optional mode. Indicates whether the client requested a response. You don’t typically need to check this - api.respond handles it automatically.

This action sends a response back to the client that called the user-defined action. It can report success/error status and optionally include JSON data.

  • success (Optional, boolean, templatable): Whether the action succeeded. Defaults to true.
  • error_message (Optional, string, templatable): An error message to include when success is false. Defaults to an empty string.
  • data (Optional, lambda): A lambda that populates a JSON object with response data. The lambda receives a root variable of type JsonObject that you can populate with key-value pairs.

Report success or error without returning data:

api:
actions:
- action: validate_input
variables:
value: int
then:
- if:
condition:
lambda: 'return value < 0;'
then:
- api.respond:
success: false
error_message: "Value must be positive"
else:
- api.respond:
success: true

Return structured JSON data to the caller:

api:
actions:
- action: get_sensor_data
variables:
sensor_name: string
then:
- api.respond:
data: !lambda |-
root["sensor"] = sensor_name;
root["value"] = id(my_sensor).state;
root["unit"] = "°C";
root["timestamp"] = id(homeassistant_time).now().timestamp;

This action will be auto-detected as optional mode because it uses api.respond with data:.

For actions that always return data (like queries), explicitly set supports_response: only:

api:
actions:
- action: get_device_info
supports_response: only
then:
- api.respond:
data: !lambda |-
root["hostname"] = App.get_name();
root["version"] = ESPHOME_VERSION;
root["uptime"] = millis() / 1000;

You can create complex nested JSON structures:

api:
actions:
- action: get_full_status
supports_response: only
then:
- api.respond:
data: !lambda |-
root["device"]["name"] = "living_room";
root["device"]["version"] = 1;
root["sensors"]["temperature"] = id(temp_sensor).state;
root["sensors"]["humidity"] = id(humidity_sensor).state;

Actions with response support appear in Home Assistant with their response mode indicated. You can call them and receive the response data:

# Home Assistant automation example
action: esphome.device_get_sensor_data
data:
sensor_name: "living_room"
response_variable: sensor_response

The response will be available in the sensor_response variable with the structure you defined in the data: lambda.

The ESPHome native API has many advantages over using MQTT for communication with Home Automation software (currently only Home Assistant and ioBroker). But MQTT is a great protocol and will never be removed. Features of native API (vs. MQTT):

  • Much more efficient: ESPHome encodes all messages in a highly optimized format with protocol buffers - for example binary sensor state messages are about 1/10 of the size.

  • One-click configuration: ESPHome just needs one click to set up in Home Assistant - no more messing around with retained MQTT discovery messages and alike.

  • One less single point of failure: In the ESPHome native API each ESP is its own server. With MQTT, when the broker shuts off nothing can communicate anymore.

  • Stability: Since ESPHome has far more control over the protocol than with MQTT, it’s really easy for us to roll out stability improvements.

  • Low Latency: The native API is optimized for very low latency, usually this is only a couple of milliseconds and far less than can be noticed by the eye.