diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py
index fe27ec8bcecd2e525400b71bf84279814513eafe..f97dfe730d049e36f40825644f7539c1a591a01f 100644
--- a/homeassistant/components/azure_devops/__init__.py
+++ b/homeassistant/components/azure_devops/__init__.py
@@ -1,43 +1,84 @@
 """Support for Azure DevOps."""
 from __future__ import annotations
 
+from dataclasses import dataclass
+from datetime import timedelta
 import logging
+from typing import Final
 
+from aioazuredevops.builds import DevOpsBuild
 from aioazuredevops.client import DevOpsClient
+from aioazuredevops.core import DevOpsProject
 import aiohttp
 
 from homeassistant.config_entries import ConfigEntry
 from homeassistant.core import HomeAssistant
-from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
-from homeassistant.helpers.entity import DeviceInfo, Entity
+from homeassistant.exceptions import ConfigEntryAuthFailed
+from homeassistant.helpers.entity import DeviceInfo, EntityDescription
+from homeassistant.helpers.update_coordinator import (
+    CoordinatorEntity,
+    DataUpdateCoordinator,
+    UpdateFailed,
+)
 
-from .const import CONF_ORG, CONF_PAT, CONF_PROJECT, DATA_AZURE_DEVOPS_CLIENT, DOMAIN
+from .const import CONF_ORG, CONF_PAT, CONF_PROJECT, DOMAIN
 
 _LOGGER = logging.getLogger(__name__)
 
 PLATFORMS = ["sensor"]
 
+BUILDS_QUERY: Final = "?queryOrder=queueTimeDescending&maxBuildsPerDefinition=1"
+
+
+@dataclass
+class AzureDevOpsEntityDescription(EntityDescription):
+    """Class describing Azure DevOps entities."""
+
+    organization: str = ""
+    project: DevOpsProject = None
+
 
 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     """Set up Azure DevOps from a config entry."""
     client = DevOpsClient()
 
-    try:
-        if entry.data[CONF_PAT] is not None:
-            await client.authorize(entry.data[CONF_PAT], entry.data[CONF_ORG])
-            if not client.authorized:
-                raise ConfigEntryAuthFailed(
-                    "Could not authorize with Azure DevOps. You may need to update your token"
-                )
-        await client.get_project(entry.data[CONF_ORG], entry.data[CONF_PROJECT])
-    except aiohttp.ClientError as exception:
-        _LOGGER.warning(exception)
-        raise ConfigEntryNotReady from exception
-
-    instance_key = f"{DOMAIN}_{entry.data[CONF_ORG]}_{entry.data[CONF_PROJECT]}"
-    hass.data.setdefault(instance_key, {})[DATA_AZURE_DEVOPS_CLIENT] = client
-
-    # Setup components
+    if entry.data.get(CONF_PAT) is not None:
+        await client.authorize(entry.data[CONF_PAT], entry.data[CONF_ORG])
+        if not client.authorized:
+            raise ConfigEntryAuthFailed(
+                "Could not authorize with Azure DevOps. You will need to update your token"
+            )
+
+    project = await client.get_project(
+        entry.data[CONF_ORG],
+        entry.data[CONF_PROJECT],
+    )
+
+    async def async_update_data() -> list[DevOpsBuild]:
+        """Fetch data from Azure DevOps."""
+
+        try:
+            return await client.get_builds(
+                entry.data[CONF_ORG],
+                entry.data[CONF_PROJECT],
+                BUILDS_QUERY,
+            )
+        except (aiohttp.ClientError, aiohttp.ClientError) as exception:
+            raise UpdateFailed from exception
+
+    coordinator = DataUpdateCoordinator(
+        hass,
+        _LOGGER,
+        name=f"{DOMAIN}_coordinator",
+        update_method=async_update_data,
+        update_interval=timedelta(seconds=300),
+    )
+
+    await coordinator.async_config_entry_first_refresh()
+
+    hass.data.setdefault(DOMAIN, {})
+    hass.data[DOMAIN][entry.entry_id] = coordinator, project
+
     hass.config_entries.async_setup_platforms(entry, PLATFORMS)
 
     return True
@@ -45,36 +86,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
 
 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
     """Unload Azure DevOps config entry."""
-    del hass.data[f"{DOMAIN}_{entry.data[CONF_ORG]}_{entry.data[CONF_PROJECT]}"]
-
-    return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
+    unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
+    if unload_ok:
+        del hass.data[DOMAIN][entry.entry_id]
+    return unload_ok
 
 
-class AzureDevOpsEntity(Entity):
+class AzureDevOpsEntity(CoordinatorEntity):
     """Defines a base Azure DevOps entity."""
 
-    def __init__(self, organization: str, project: str, name: str, icon: str) -> None:
+    coordinator: DataUpdateCoordinator[list[DevOpsBuild]]
+    entity_description: AzureDevOpsEntityDescription
+
+    def __init__(
+        self,
+        coordinator: DataUpdateCoordinator[list[DevOpsBuild]],
+        entity_description: AzureDevOpsEntityDescription,
+    ) -> None:
         """Initialize the Azure DevOps entity."""
-        self._attr_name = name
-        self._attr_icon = icon
-        self.organization = organization
-        self.project = project
-
-    async def async_update(self) -> None:
-        """Update Azure DevOps entity."""
-        if await self._azure_devops_update():
-            self._attr_available = True
-        else:
-            if self._attr_available:
-                _LOGGER.debug(
-                    "An error occurred while updating Azure DevOps sensor",
-                    exc_info=True,
-                )
-            self._attr_available = False
-
-    async def _azure_devops_update(self) -> bool:
-        """Update Azure DevOps entity."""
-        raise NotImplementedError()
+        super().__init__(coordinator)
+        self.entity_description = entity_description
+        self._attr_unique_id: str = "_".join(
+            [entity_description.organization, entity_description.key]
+        )
+        self._organization: str = entity_description.organization
+        self._project_name: str = entity_description.project.name
 
 
 class AzureDevOpsDeviceEntity(AzureDevOpsEntity):
@@ -85,7 +121,7 @@ class AzureDevOpsDeviceEntity(AzureDevOpsEntity):
         """Return device information about this Azure DevOps instance."""
         return DeviceInfo(
             entry_type="service",
-            identifiers={(DOMAIN, self.organization, self.project)},  # type: ignore
-            manufacturer=self.organization,
-            name=self.project,
+            identifiers={(DOMAIN, self._organization, self._project_name)},  # type: ignore
+            manufacturer=self._organization,
+            name=self._project_name,
         )
diff --git a/homeassistant/components/azure_devops/const.py b/homeassistant/components/azure_devops/const.py
index 40610ba7baa57bc90e6e27928fb21cd1c01c7a90..adaf5ebe767555a15914180185cb07b3d6280ba8 100644
--- a/homeassistant/components/azure_devops/const.py
+++ b/homeassistant/components/azure_devops/const.py
@@ -1,11 +1,6 @@
 """Constants for the Azure DevOps integration."""
 DOMAIN = "azure_devops"
 
-DATA_AZURE_DEVOPS_CLIENT = "azure_devops_client"
-DATA_ORG = "organization"
-DATA_PROJECT = "project"
-DATA_PAT = "personal_access_token"
-
 CONF_ORG = "organization"
 CONF_PROJECT = "project"
 CONF_PAT = "personal_access_token"
diff --git a/homeassistant/components/azure_devops/sensor.py b/homeassistant/components/azure_devops/sensor.py
index 67d472abc1e3ef3fc6835c795bb0cf774487d731..dc81cf531719ca2494ee718c4199d623f593651c 100644
--- a/homeassistant/components/azure_devops/sensor.py
+++ b/homeassistant/components/azure_devops/sensor.py
@@ -1,126 +1,94 @@
 """Support for Azure DevOps sensors."""
 from __future__ import annotations
 
-from datetime import timedelta
-import logging
+from dataclasses import dataclass
+from typing import Any, Callable
 
 from aioazuredevops.builds import DevOpsBuild
-from aioazuredevops.client import DevOpsClient
-import aiohttp
-
-from homeassistant.components.azure_devops import AzureDevOpsDeviceEntity
-from homeassistant.components.azure_devops.const import (
-    CONF_ORG,
-    CONF_PROJECT,
-    DATA_AZURE_DEVOPS_CLIENT,
-    DATA_ORG,
-    DATA_PROJECT,
-    DOMAIN,
+
+from homeassistant.components.azure_devops import (
+    AzureDevOpsDeviceEntity,
+    AzureDevOpsEntityDescription,
 )
-from homeassistant.components.sensor import SensorEntity
+from homeassistant.components.azure_devops.const import CONF_ORG, DOMAIN
+from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
 from homeassistant.config_entries import ConfigEntry
 from homeassistant.core import HomeAssistant
-from homeassistant.exceptions import PlatformNotReady
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+from homeassistant.helpers.typing import StateType
+
+
+@dataclass
+class AzureDevOpsSensorEntityDescriptionMixin:
+    """Mixin class for required Azure DevOps sensor description keys."""
 
-_LOGGER = logging.getLogger(__name__)
+    build_key: int
 
-SCAN_INTERVAL = timedelta(seconds=300)
-PARALLEL_UPDATES = 4
 
-BUILDS_QUERY = "?queryOrder=queueTimeDescending&maxBuildsPerDefinition=1"
+@dataclass
+class AzureDevOpsSensorEntityDescription(
+    AzureDevOpsEntityDescription,
+    SensorEntityDescription,
+    AzureDevOpsSensorEntityDescriptionMixin,
+):
+    """Class describing Azure DevOps sensor entities."""
+
+    attrs: Callable[[DevOpsBuild], Any] = round
+    value: Callable[[DevOpsBuild], StateType] = round
 
 
 async def async_setup_entry(
-    hass: HomeAssistant, entry: ConfigEntry, async_add_entities
+    hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
 ) -> None:
     """Set up Azure DevOps sensor based on a config entry."""
-    instance_key = f"{DOMAIN}_{entry.data[CONF_ORG]}_{entry.data[CONF_PROJECT]}"
-    client = hass.data[instance_key][DATA_AZURE_DEVOPS_CLIENT]
-    organization = entry.data[DATA_ORG]
-    project = entry.data[DATA_PROJECT]
-    sensors = []
-
-    try:
-        builds: list[DevOpsBuild] = await client.get_builds(
-            organization, project, BUILDS_QUERY
-        )
-    except aiohttp.ClientError as exception:
-        _LOGGER.warning(exception)
-        raise PlatformNotReady from exception
-
-    for build in builds:
-        sensors.append(
-            AzureDevOpsLatestBuildSensor(client, organization, project, build)
+    coordinator, project = hass.data[DOMAIN][entry.entry_id]
+
+    sensors = [
+        AzureDevOpsSensor(
+            coordinator,
+            AzureDevOpsSensorEntityDescription(
+                key=f"{build.project.id}_{build.definition.id}_latest_build",
+                name=f"{build.project.name} {build.definition.name} Latest Build",
+                icon="mdi:pipe",
+                attrs=lambda build: {
+                    "definition_id": build.definition.id,
+                    "definition_name": build.definition.name,
+                    "id": build.id,
+                    "reason": build.reason,
+                    "result": build.result,
+                    "source_branch": build.source_branch,
+                    "source_version": build.source_version,
+                    "status": build.status,
+                    "url": build.links.web,
+                    "queue_time": build.queue_time,
+                    "start_time": build.start_time,
+                    "finish_time": build.finish_time,
+                },
+                build_key=key,
+                organization=entry.data[CONF_ORG],
+                project=project,
+                value=lambda build: build.build_number,
+            ),
         )
+        for key, build in enumerate(coordinator.data)
+    ]
 
     async_add_entities(sensors, True)
 
 
 class AzureDevOpsSensor(AzureDevOpsDeviceEntity, SensorEntity):
-    """Defines a Azure DevOps sensor."""
-
-    def __init__(
-        self,
-        client: DevOpsClient,
-        organization: str,
-        project: str,
-        key: str,
-        name: str,
-        icon: str,
-        measurement: str = "",
-        unit_of_measurement: str = "",
-    ) -> None:
-        """Initialize Azure DevOps sensor."""
-        self._attr_native_unit_of_measurement = unit_of_measurement
-        self.client = client
-        self.organization = organization
-        self.project = project
-        self._attr_unique_id = "_".join([organization, key])
-
-        super().__init__(organization, project, name, icon)
-
-
-class AzureDevOpsLatestBuildSensor(AzureDevOpsSensor):
-    """Defines a Azure DevOps card count sensor."""
-
-    def __init__(
-        self, client: DevOpsClient, organization: str, project: str, build: DevOpsBuild
-    ) -> None:
-        """Initialize Azure DevOps sensor."""
-        self.build: DevOpsBuild = build
-        super().__init__(
-            client,
-            organization,
-            project,
-            f"{build.project.id}_{build.definition.id}_latest_build",
-            f"{build.project.name} {build.definition.name} Latest Build",
-            "mdi:pipe",
-        )
+    """Define a Azure DevOps sensor."""
+
+    entity_description: AzureDevOpsSensorEntityDescription
+
+    @property
+    def native_value(self) -> StateType:
+        """Return the state."""
+        build: DevOpsBuild = self.coordinator.data[self.entity_description.build_key]
+        return self.entity_description.value(build)
 
-    async def _azure_devops_update(self) -> bool:
-        """Update Azure DevOps entity."""
-        try:
-            build: DevOpsBuild = await self.client.get_build(
-                self.organization, self.project, self.build.id
-            )
-        except aiohttp.ClientError as exception:
-            _LOGGER.warning(exception)
-            self._attr_available = False
-            return False
-        self._attr_native_value = build.build_number
-        self._attr_extra_state_attributes = {
-            "definition_id": build.definition.id,
-            "definition_name": build.definition.name,
-            "id": build.id,
-            "reason": build.reason,
-            "result": build.result,
-            "source_branch": build.source_branch,
-            "source_version": build.source_version,
-            "status": build.status,
-            "url": build.links.web,
-            "queue_time": build.queue_time,
-            "start_time": build.start_time,
-            "finish_time": build.finish_time,
-        }
-        self._attr_available = True
-        return True
+    @property
+    def extra_state_attributes(self) -> dict[str, Any]:
+        """Return the state attributes of the entity."""
+        build: DevOpsBuild = self.coordinator.data[self.entity_description.build_key]
+        return self.entity_description.attrs(build)