# How the Home Assistant Service Registry Handles Registration and Calls

> Learn how the Home Assistant service registry registers and executes services using in-memory storage, schema validation, and asynchronous job execution. Understand its core mechanics.

- Repository: [Home Assistant/core](https://github.com/home-assistant/core)
- Tags: internals
- Published: 2026-02-28

---

**The Home Assistant service registry stores callable services in an in-memory dictionary and dispatches calls through schema validation, `ServiceCall` object creation, and asynchronous job execution via `HassJob`.**

The service registry serves as the central hub that orchestrates all inter-component communication within the Home Assistant ecosystem. Located primarily in [`homeassistant/core.py`](https://github.com/home-assistant/core/blob/main/homeassistant/core.py) and supported by helper utilities in [`homeassistant/helpers/service.py`](https://github.com/home-assistant/core/blob/main/homeassistant/helpers/service.py), this registry manages how integrations expose functionality and how automations, scripts, and the frontend invoke those capabilities. Understanding how the service registry handles service registration and calls is essential for developing robust custom integrations that interact cleanly with Home Assistant's event-driven architecture.

## Core Data Structures in homeassistant/core.py

Three classes defined in [`homeassistant/core.py`](https://github.com/home-assistant/core/blob/main/homeassistant/core.py) form the foundation of the service infrastructure.

### The Service Class

The `Service` class (around line 2410) represents a registered callable. It encapsulates:

- **`job`**: A `HassJob` wrapping the actual function to execute
- **`schema`**: An optional `voluptuous` schema for data validation
- **`supports_response`**: Boolean indicating if the service can return data
- **`description_placeholders`**: Metadata for documentation generation

### The ServiceCall Class

When a service is invoked, the registry creates a `ServiceCall` instance (defined around line 2445). This object carries:

- **`domain`** and **`service`**: Identifiers for the target service
- **`data`**: The validated parameters passed by the caller
- **`context`**: A `Context` object tracking the call's origin and permissions
- **`return_response`**: Boolean flag indicating whether the caller expects a return value

### The ServiceRegistry Class

The `ServiceRegistry` class (line 2477) maintains the actual storage mechanism via `self._services`, a dictionary mapping domains to service names to `Service` objects. This in-memory structure provides O(1) lookup times when dispatching calls.

## Registering Services Through the Registry

Integration code rarely interacts with the core registry directly. Instead, developers use helper functions that construct appropriate `HassJob` wrappers and metadata.

### From Integration Code to Registry Storage

Most entity-based integrations use `async_register_entity_service` from [`homeassistant/helpers/service.py`](https://github.com/home-assistant/core/blob/main/homeassistant/helpers/service.py) (around line 1919). This helper accepts:

```python
from homeassistant.helpers.service import async_register_entity_service

async_register_entity_service(
    hass,
    domain="light",
    name="turn_on",
    entities=entities,
    func="async_turn_on",
    schema=vol.Schema({vol.Optional("transition"): vol.Coerce(int)}),
)

```

The helper builds a service function that iterates over target entities and forwards the registration to `hass.services.async_register`.

### The Registration Flow in ServiceRegistry.async_register

The core registration logic resides in `ServiceRegistry.async_register` (lines 2670–2690). The method:

- Normalizes domain and service names to lowercase
- Instantiates a `Service` object with the provided `HassJob`, schema, and metadata
- Stores the instance in `self._services[domain][service]`

```python
self._services.setdefault(domain.lower(), {})[service.lower()] = Service(
    func, schema, domain, service, context, supports_response, job_type,
    description_placeholders,
)

```

## Executing Service Calls

When automations or scripts trigger services, the registry coordinates validation, context propagation, and asynchronous execution.

### The async_call Execution Pipeline

The `ServiceRegistry.async_call` method (lines 2712–2750) handles the complete dispatch flow:

1. **Service Lookup**: Retrieves the `Service` instance from `self._services`
2. **Schema Validation**: If a schema exists, validates and coerces `service_data` against it
3. **Context Building**: Creates a `ServiceCall` object encapsulating the request
4. **Job Scheduling**: Executes `hass.async_run_hass_job(service.job, call)` to run the handler asynchronously

```python
await hass.services.async_call(
    domain="light",
    service="turn_on",
    service_data={"entity_id": "light.kitchen", "brightness": 255},
    blocking=True,
    context=Context(user_id="admin_123"),
    return_response=False,
)

```

Setting `blocking=True` pauses the caller until the service completes, while `return_response=True` captures any data returned by the service handler.

### Schema Validation and Response Handling

If the `Service` instance includes a schema, the registry validates incoming data before creating the `ServiceCall` object. Validation failures raise exceptions immediately, preventing invalid state changes. When `return_response` is enabled, the registry captures the result of `hass.async_run_hass_job` and returns it to the caller; otherwise, the method returns `None` after confirming execution.

## Admin-Only Services and Security Controls

For sensitive operations, `async_register_admin_service` (lines 4950–4975 in [`homeassistant/helpers/service.py`](https://github.com/home-assistant/core/blob/main/homeassistant/helpers/service.py)) wraps handlers with `_async_admin_handler`. This wrapper inspects the `ServiceCall.context` to verify the calling user possesses administrator privileges before executing the underlying function. If the check fails, the call aborts with a permission error.

```python
hass.services.async_register(
    domain,
    service,
    partial(_async_admin_handler, hass, HassJob(service_func)),
    schema,
    supports_response,
)

```

## Caching Service Descriptions

The registry maintains a documentation cache to avoid repeatedly parsing YAML files. The `async_get_all_descriptions` helper (around line 3530 in [`homeassistant/helpers/service.py`](https://github.com/home-assistant/core/blob/main/homeassistant/helpers/service.py)) lazily loads [`services.yaml`](https://github.com/home-assistant/core/blob/main/services.yaml) files from each integration, storing structured metadata in `SERVICE_DESCRIPTION_CACHE`. This cache enables the frontend to display user-friendly service descriptions and parameter schemas without filesystem overhead on every call.

## Complete Example: Registering and Calling a Custom Service

The following implementation demonstrates the full lifecycle from registration to invocation:

```python

# custom_components/my_integration/__init__.py

from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.service import async_register_admin_service
import voluptuous as vol

DOMAIN = "my_integration"

async def async_setup(hass: HomeAssistant, config: dict) -> bool:
    async def handle_set_value(call: ServiceCall):
        """Handle the service call."""
        value = call.data.get("value", 0)
        hass.states.async_set(f"{DOMAIN}.example_sensor", value)

    # Register with admin restrictions and schema validation

    async_register_admin_service(
        hass,
        DOMAIN,
        "set_value",
        handle_set_value,
        schema=vol.Schema({vol.Optional("value"): vol.Coerce(int)}),
    )
    return True

```

Invoke the service programmatically:

```python
await hass.services.async_call(
    "my_integration",
    "set_value",
    {"value": 42},
    blocking=True,
    return_response=False,
)

```

Or via YAML automation:

```yaml
service: my_integration.set_value
data:
  value: 42

```

## Summary

- The **service registry** stores all callable services in `ServiceRegistry._services`, a nested dictionary mapping domains to service names.
- **Registration** flows through helper functions like `async_register_entity_service` and `async_register_admin_service`, ultimately calling `ServiceRegistry.async_register` in [`homeassistant/core.py`](https://github.com/home-assistant/core/blob/main/homeassistant/core.py).
- **Service calls** trigger the `ServiceRegistry.async_call` pipeline, which validates data against schemas, creates `ServiceCall` objects, and executes handlers via `HassJob`.
- **Security controls** wrap sensitive services with admin verification checks before execution.
- **Description caching** optimizes frontend performance by storing parsed [`services.yaml`](https://github.com/home-assistant/core/blob/main/services.yaml) metadata in memory.

## Frequently Asked Questions

### What is the difference between async_register_entity_service and async_register_admin_service?

**`async_register_entity_service`** targets specific entity instances, automatically iterating over targeted entities and calling the specified method on each one. **`async_register_admin_service`** wraps a handler with permission checks, ensuring only users with administrator privileges can execute sensitive operations like configuration changes or system restarts.

### How does the service registry validate incoming call data?

The registry validates data using **Voluptuous schemas** attached during registration. When `ServiceRegistry.async_call` processes a request, it checks if the `Service` instance has a schema attribute. If present, the registry validates and coerces the `service_data` dictionary against this schema before creating the `ServiceCall` object, raising exceptions for invalid input.

### Can a service return data to the caller in Home Assistant?

Yes. Services can return data when registered with `supports_response=True` and called with `return_response=True`. The registry captures the result from `hass.async_run_hass_job` and returns it to the caller. This pattern enables services to provide confirmation data, entity states, or calculation results back to automations or the frontend.

### Where are service descriptions stored and cached?

Service descriptions reside in each integration's [`services.yaml`](https://github.com/home-assistant/core/blob/main/services.yaml) file. The registry caches these descriptions in memory via `SERVICE_DESCRIPTION_CACHE`, populated by `async_get_all_descriptions` in [`homeassistant/helpers/service.py`](https://github.com/home-assistant/core/blob/main/homeassistant/helpers/service.py). This caching layer prevents repeated filesystem access when the frontend requests service documentation for the developer tools or automation editors.