get_history_range
Retrieve raw state-change history for an entity over a specific date/time range. Use it to inspect past days or correlate with external events.
Instructions
Get raw state-change history for an entity over a date/time range.
Like get_history, but takes an explicit window instead of "N hours
from now". Useful for inspecting what happened on a specific day or
correlating with an external event.
Args:
entity_id: The entity to fetch history for.
start_time: ISO-8601 start (e.g. 2026-05-15 or
2026-05-15T08:00:00Z). Treated as UTC if no offset.
end_time: ISO-8601 end. Defaults to now (UTC).
Returns:
Same shape as get_history: entity_id, states, count,
first_changed, last_changed.
Examples: get_history_range("light.kitchen", "2026-05-15") get_history_range("sensor.power", "2026-05-15T00:00:00Z", "2026-05-16T00:00:00Z")
Best Practices:
- Bound the window — wider ranges return more data and more tokens.
- For aggregated long-term data, prefer get_statistics_range.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| entity_id | Yes | ||
| start_time | Yes | ||
| end_time | No |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- app/server.py:1308-1347 (handler)The main tool handler for 'get_history_range'. Decorated with @mcp.tool() and @async_handler, it takes entity_id, start_time, and optional end_time, calls get_entity_history_range from hass.py, and uses _flatten_history to format the response.
@mcp.tool() @async_handler("get_history_range") async def get_history_range( entity_id: str, start_time: str, end_time: Optional[str] = None, ) -> Dict[str, Any]: """ Get raw state-change history for an entity over a date/time range. Like `get_history`, but takes an explicit window instead of "N hours from now". Useful for inspecting what happened on a specific day or correlating with an external event. Args: entity_id: The entity to fetch history for. start_time: ISO-8601 start (e.g. `2026-05-15` or `2026-05-15T08:00:00Z`). Treated as UTC if no offset. end_time: ISO-8601 end. Defaults to now (UTC). Returns: Same shape as `get_history`: `entity_id`, `states`, `count`, `first_changed`, `last_changed`. Examples: get_history_range("light.kitchen", "2026-05-15") get_history_range("sensor.power", "2026-05-15T00:00:00Z", "2026-05-16T00:00:00Z") Best Practices: - Bound the window — wider ranges return more data and more tokens. - For aggregated long-term data, prefer `get_statistics_range`. """ logger.info( f"Getting history range for {entity_id}: {start_time} -> {end_time or 'now'}" ) try: history_data = await get_entity_history_range(entity_id, start_time, end_time) except ValueError as e: return {"entity_id": entity_id, "error": str(e), "states": [], "count": 0} return _flatten_history(history_data, entity_id) - app/server.py:1276-1305 (helper)Shared helper function _flatten_history used by both get_history and get_history_range to normalize HA's list-of-lists response into a consistent dict shape with entity_id, states, count, first_changed, last_changed.
def _flatten_history(history_data: Any, entity_id: str) -> Dict[str, Any]: """Shared formatter for get_history / get_history_range.""" if isinstance(history_data, dict) and "error" in history_data: return { "entity_id": entity_id, "error": history_data["error"], "states": [], "count": 0, } states: List[Dict[str, Any]] = [] if history_data and isinstance(history_data, list): for state_list in history_data: states.extend(state_list) if not states: return { "entity_id": entity_id, "states": [], "count": 0, "first_changed": None, "last_changed": None, "note": "No state changes found in the specified timeframe.", } states.sort(key=lambda x: x.get("last_changed", "")) return { "entity_id": entity_id, "states": states, "count": len(states), "first_changed": states[0].get("last_changed"), "last_changed": states[-1].get("last_changed"), } - app/hass.py:675-711 (helper)The underlying helper function get_entity_history_range in app/hass.py that calls HA's REST API /api/history/period/<start> with end_time, filter_entity_id, and minimal_response parameters. Also validates that start_time < end_time.
@handle_api_errors async def get_entity_history_range( entity_id: str, start_time: Union[str, datetime], end_time: Optional[Union[str, datetime]] = None, ) -> List[Dict[str, Any]]: """Get state-change history for an entity within a date/time range. Uses HA's REST history endpoint (raw state changes, not aggregated). Useful when you want exact state transitions over a specific window — e.g. "what did the front door do last Tuesday?" — without being boxed in to N hours back from "now". Args: entity_id: The entity to fetch history for. start_time: ISO-8601 string or datetime. Treated as UTC if naive. end_time: ISO-8601 string or datetime. Defaults to now (UTC). Returns: A list of state-change buckets exactly as HA returns them (`/api/history/period/...` shape). """ start_dt = _parse_iso_dt(start_time) end_dt = _parse_iso_dt(end_time) if end_time is not None else datetime.now(timezone.utc) if start_dt >= end_dt: raise ValueError("start_time must be before end_time") client = await get_client() url = f"{HA_URL}/api/history/period/{start_dt.strftime('%Y-%m-%dT%H:%M:%SZ')}" params = { "filter_entity_id": entity_id, "minimal_response": "true", "end_time": end_dt.strftime("%Y-%m-%dT%H:%M:%SZ"), } response = await client.get(url, headers=get_ha_headers(), params=params) response.raise_for_status() return response.json() - app/hass.py:656-672 (helper)Utility function _parse_iso_dt used by get_entity_history_range to parse ISO-8601 datetime strings or datetime objects into tz-aware UTC datetimes.
def _parse_iso_dt(value: Union[str, datetime]) -> datetime: """Coerce a user-supplied datetime to a tz-aware UTC datetime. Accepts a `datetime` (assumed UTC if naive) or an ISO-8601 string (`2026-01-15`, `2026-01-15T12:00:00`, `2026-01-15T12:00:00Z`, or with explicit offset). Raises ValueError on anything else. """ if isinstance(value, datetime): return value if value.tzinfo else value.replace(tzinfo=timezone.utc) if not isinstance(value, str): raise ValueError(f"datetime must be str or datetime, got {type(value).__name__}") s = value.strip() # `fromisoformat` in 3.11+ accepts `Z`, but be explicit for clarity. if s.endswith("Z"): s = s[:-1] + "+00:00" dt = datetime.fromisoformat(s) return dt if dt.tzinfo else dt.replace(tzinfo=timezone.utc) - app/server.py:17-23 (registration)Import of get_entity_history_range from app.hass module at the top of app/server.py, which is the function called by the tool handler.
from app.hass import ( get_hass_version, get_entity_state, call_service, get_entities, get_automations, restart_home_assistant, cleanup_client, filter_fields, summarize_domain, get_system_overview, get_hass_error_log, get_entity_history, get_entity_history_range, get_entity_statistics, get_entity_statistics_range, )