The action registry
How the action registry works underneath, what an action handler gets, and how to call actions from code.
This page explains how the action registry works underneath: what a handler gets, how the data is validated, and how actions are called. To register actions in your integration, describe them in services.yaml, and return response data, see actions in integrations.
An action is something Home Assistant can do on request, like light.turn_on or weather.get_forecasts. Automations, scripts, dashboards, voice assistants, and the APIs all call actions. Every action lives in the action registry on hass.services.
Actions used to be called services. The code still says service almost everywhere: hass.services, ServiceCall, services.yaml, and the call_service event. In text, write action. In code, use the names the code uses.
hass.services.async_register(domain, service, handler) adds an action to the registry. It takes an optional schema, and supports_response for actions that return data. Integrations register their actions in async_setup, see register actions in async_setup.
Home Assistant uses probatio for schemas. Import it as probatio, without an alias. When you register a schema, it runs before your handler, so invalid data never reaches you. Without a schema, call.data is whatever the caller sent.
The handler gets a ServiceCall with:
call.data: the validated data, read-only.call.context: theContextof the call, with the user who started it. Pass it on to anything you do on their behalf. See permissions.call.domainandcall.service: which action was called.call.return_response: whether the caller asked for response data.call.hass: the Home Assistant instance.
The handler can be a coroutine function, a function decorated with @callback, or a plain function. A plain function runs in the executor, like an event listener.
When something goes wrong, the handler raises an exception. It never returns an error code, also not in response data. Which exception to raise is on raise errors.
Actions that act on entities do not register a plain handler. async_register_platform_entity_service from homeassistant.helpers.service resolves the target the user picked (entities, devices, areas, floors, or labels), checks permissions, and calls a method on each targeted entity. See entity actions.
await hass.services.async_call(
"light",
"turn_on",
{"brightness": 255},
target={"entity_id": "light.kitchen"},
blocking=True,
context=call.context,
)blocking=Truewaits until the action is done, and raises its exceptions. Withblocking=False, the call is started in the background, and errors only end up in the log.return_response=Truereturns the response data. It only works together withblocking=True, and only for actions that support responses.context: pass the context of whatever caused this call, so it is attributed correctly.
Calling an action from your own integration is unusual. Talk to your own code directly. Calling another integration's actions is mostly something automations and scripts do.
When an action is called, the registry looks up the handler, validates the data against the schema if there is one, fires a call_service event on the event bus, and runs the handler. hass.services.async_services() returns everything that is registered, and hass.services.has_service(domain, service) checks for one. Registering and removing actions fire the service_registered and service_removed events.