REST API
The HTTP endpoints under /api/ to read states, call actions, fire events, and render templates, with the permissions each one needs.
Home Assistant serves a REST API under /api/, on the same port as the frontend: 8123 by default, or 80 when Home Assistant runs with the Supervisor, like on Home Assistant OS. The SETUP_PORT environment variable overrides that default. It accepts and returns JSON. The api integration provides it, and it is loaded whenever the frontend is.
The REST API is good for simple, one-off requests from scripts and other systems. For anything that needs to stay connected or follow changes, use the WebSocket API. The frontend uses that one, and it has many more commands.
Every request needs an access token in the Authorization header. For a script, create a long-lived access token on your profile, under Security. For an app, see the authentication API.
curl \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
http://homeassistant.local:8123/api/The same request in Python, with requests:
import requests
response = requests.get(
"http://homeassistant.local:8123/api/states/sun.sun",
headers={"Authorization": "Bearer TOKEN"},
timeout=10,
)
print(response.json())Successful requests return status 200, or 201 when something was created. The usual errors are 400 (bad request), 401 (unauthorized), 404 (not found), and 405 (method not allowed). An action that fails with an error returns 500, and while Home Assistant is shutting down, every endpoint returns 503.
Some endpoints need an administrator. They are marked Admin below. Others return only what the user may read, following their permissions.
Returns {"message": "API running."} if the API is up. Mind the trailing slash: the path is /api/, not /api.
Returns the core configuration: location, units, time zone, version, loaded components, and more.
{
"location_name": "Home",
"latitude": 52.37,
"longitude": 4.89,
"elevation": 0,
"unit_system": {
"length": "km",
"mass": "g",
"temperature": "°C",
"volume": "L"
},
"time_zone": "Europe/Amsterdam",
"version": "2026.10.0",
"components": ["api", "frontend", "http", "sun"],
"state": "RUNNING"
}The example is shortened. The response holds more keys.
Returns whether core is running, and whether a database migration is in progress. Meant as a fast health check, for example for the Supervisor.
{
"state": "RUNNING",
"recorder_state": {
"migration_in_progress": false,
"migration_is_live": false
}
}Returns the list of loaded components.
Admin. Checks configuration.yaml. Returns result (valid or invalid), and errors and warnings as text, or null.
Returns all states the user may read, as a list of state objects.
Returns the state of one entity, or 404.
{
"entity_id": "sun.sun",
"state": "below_horizon",
"attributes": {
"friendly_name": "Sun",
"elevation": -17.67
},
"last_changed": "2026-10-10T21:43:29.204838+00:00",
"last_reported": "2026-10-10T21:50:30.529465+00:00",
"last_updated": "2026-10-10T21:50:30.529465+00:00",
"context": {
"id": "01J9Z7K1M3S8TQ0Y6E4H2B5N7C",
"parent_id": null,
"user_id": null
}
}Admin. Creates or replaces a state. The body needs a state, and can have attributes:
{
"state": "25",
"attributes": {
"unit_of_measurement": "°C"
}
}Returns 201 if the state is new and 200 if it existed, with the new state in the body and a Location header pointing to it. Add "force_update": true to fire a state change even if nothing changed.
This only changes what Home Assistant shows. It does not talk to the device, and the next update of the entity overwrites it. To control a device, call an action.
Admin. Removes a state. Returns 404 if it did not exist.
Returns the registered actions, grouped by domain. The path still uses the old name, services.
Calls an action and waits until it is done. The body is the action data, and can include the target:
curl \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"entity_id": "light.kitchen"}' \
http://homeassistant.local:8123/api/services/light/turn_onReturns the list of states that changed because of this call. To get response data, add ?return_response to the URL. The response then looks like this:
{
"changed_states": [],
"service_response": {
"weather.forecast_home": {
"forecast": []
}
}
}Use ?return_response for actions that support it, and only for those. Leaving it out for an action that only returns data, or adding it for an action without response data, gets you a 400.
Returns the event types that have listeners, with the number of listeners each.
Admin. Fires an event. The optional body is the event data:
{
"button_id": "front_door"
}Returns {"message": "Event front_door_pressed fired."}, with your event type in the message. Events are stored in the database, so be mindful of what you fire.
Admin. A stream of all events as server-sent events. Pass ?restrict=state_changed,call_service to limit it to some event types. The WebSocket API's subscribe_events is the better choice for new code.
Admin. Renders a template and returns the result as plain text.
{
"template": "It is {{ states('sensor.temperature') }} degrees."
}These come from the history and logbook integrations, which are part of the default configuration.
Returns the state history of entities. The timestamp, like 2026-10-01T00:00:00+02:00, is the start of the period and is optional: it defaults to one day ago. Query parameters:
filter_entity_id: required. One or more entity IDs, separated by commas.end_time: the end of the period, URL encoded. Defaults to one day after the start.minimal_response: only returnstateandlast_changedfor the states between the first and the last. Much faster.no_attributes: skip the attributes. Also faster.significant_changes_only=0: return every change. By default, you only get significant changes.skip_initial_state: leave out the state at the start of the period.
The response is a list with one list of states per entity.
Returns logbook entries, starting from the optional timestamp. Without a timestamp, it starts at the beginning of the current day, in local time. Query parameters:
entity: one or more entity IDs, separated by commas.end_time: the end of the period, URL encoded.period: the number of days to return when there is noend_time. Defaults to 1.context_id: only the entries of one context. Cannot be combined withentity.
Several integrations add their own endpoints:
GET /api/error_log(Admin): the log of the current session, as plain text.GET /api/camera_proxy/<entity_id>: the current image of a camera.GET /api/calendars: the calendar entities.GET /api/calendars/<entity_id>?start=<timestamp>&end=<timestamp>returns the events of one calendar in that period.POST /api/intent/handle: handles an intent.POST /api/conversation/process: sends text to a conversation agent. See the conversation API.
An integration can add endpoints with a HomeAssistantView from homeassistant.helpers.http, registered with hass.http.register_view(MyView()). Set url and name, and implement get, post, or another method. Views require authentication unless you set requires_auth = False. See permissions for an example.
Think twice before you do. Most features belong in the WebSocket API, which the frontend can use directly. HTTP views fit when something outside Home Assistant has to call in, like a webhook, or when you serve files.