Tado4s is a Scala 3 library that provides a functional, type-safe client for the Tado API. It offers comprehensive access to smart thermostat data, zone controls, and home automation features.
Built on http4s and Cats Effect, Tado4s provides a pure functional interface to the Tado REST API with OAuth2 authentication, automatic token refresh, persistent token storage, and typed request/response models.
- Type-Safe API - Strongly typed request and response models for all endpoints
- Functional Design - Built on Cats Effect with pure functional semantics
- Comprehensive Coverage - Supports homes, zones, devices, weather, day reports, and more
- Retry Logic - Built-in exponential backoff retry policy for API calls
- Streaming Ready - Returns
F[_]effects compatible with fs2 streams
| Category | Endpoints |
|---|---|
| Account | User account information |
| Homes | Home details, state, and presence control |
| Zones | Zone configuration, state, and capabilities |
| Zone Control | Manual temperature control, overlays, schedules |
| Timetables | Active timetable, all timetables, blocks |
| Away Configuration | Away mode settings per zone |
| Devices | Registered device information |
| Mobile Devices | Mobile device management and geo-tracking |
| Installations | Installation details |
| Users | Configured user information |
| Weather | Current weather at home location |
| Day Reports | Historical zone data for a specific day |
| Heating Circuits | Heating circuit information |
| Air Comfort | Air comfort and freshness data |
Tado4s uses OAuth2 device code flow. First, obtain a refresh token using the provided Python script:
cd scripts
python tado_device_auth.pyThis will guide you through the authentication process and provide a refresh token. The script outputs environment variables that you can use:
HOMEDATA_TADO_TOKEN="your-refresh-token"
HOMEDATA_TADO_TOKEN_ISSUE_TIME="2024-01-01T00:00:00+00:00"Create a client and fetch account information:
import cats.effect.*
import com.colofabrix.scala.tado4s.*
import com.colofabrix.scala.tado4s.store.TadoRefreshToken
import java.time.OffsetDateTime
object MyApp extends IOApp.Simple {
def run: IO[Unit] =
for
client <- Tado4sClient[IO](None)
initialToken = TadoRefreshToken("your-refresh-token", OffsetDateTime.now())
_ <- client.authenticate(initialToken)
account <- client.getAccountInfo()
homeId = account.homes.head.id
_ <- IO.println(s"Home ID: $homeId")
yield ()
}val zones =
for
client <- Tado4sClient[IO](None)
_ <- client.authenticate(initialToken)
account <- client.getAccountInfo()
homeId = account.homes.head.id
zones <- client.getHomeZones(homeId)
yield zonesval zoneState =
for
client <- Tado4sClient[IO](None)
_ <- client.authenticate(initialToken)
state <- client.getZoneState(homeId = 12345, zoneId = 1)
yield stateimport java.time.LocalDate
val dayReport =
for
client <- Tado4sClient[IO](None)
_ <- client.authenticate(initialToken)
report <- client.getZoneDayReport(
homeId = 12345,
zoneId = 1,
date = LocalDate.now().minusDays(1),
)
yield reportval weather =
for
client <- Tado4sClient[IO](None)
_ <- client.authenticate(initialToken)
weather <- client.getHomeWeather(homeId = 12345)
yield weatherThe main entry point for interacting with the Tado API.
| Method | Description |
|---|---|
authenticate(refreshToken) |
Authenticate with refresh token |
logout() |
Clear authentication and delete tokens |
getAccountInfo() |
Get user account information |
getHomeDetails(homeId) |
Get home configuration |
getHomeZones(homeId) |
Get zones for a home |
getHomeState(homeId) |
Get current home state |
getHomeDevices(homeId) |
Get registered devices |
getHomeInstallations(homeId) |
Get installation details |
getHomeUsers(homeId) |
Get configured users |
getZoneState(homeId, zoneId) |
Get zone current state |
getHomeWeather(homeId) |
Get weather at home location |
getZoneDayReport(homeId, zoneId, date) |
Get historical day report |
getZoneCapabilities(request) |
Get zone temperature capabilities |
getEarlyStart(request) |
Get early start settings |
setEarlyStart(request) |
Set early start enabled/disabled |
getActiveTimetable(request) |
Get active schedule timetable |
setActiveTimetable(request) |
Set active schedule timetable |
getTimetables(request) |
Get all schedule timetables |
getTimetableBlocks(request) |
Get schedule blocks for a timetable |
getAwayConfiguration(request) |
Get away mode configuration |
setAwayConfiguration(request) |
Set away mode configuration |
setZoneOverlay(request) |
Set manual temperature control |
deleteZoneOverlay(request) |
Clear manual control, resume schedule |
setHomePresence(request) |
Set home to HOME or AWAY |
getMobileDevices(request) |
Get mobile devices |
getMobileDeviceSettings(request) |
Get mobile device settings |
setMobileDeviceSettings(request) |
Set mobile device geo-tracking |
deleteMobileDevice(request) |
Remove a mobile device |
getHeatingCircuits(request) |
Get heating circuit information |
getAirComfort(request) |
Get air comfort and freshness data |
| Type | Description |
|---|---|
AccountResponse |
User account with home list |
HomeResponse |
Home configuration and settings |
HomeZoneResponse |
Zone configuration |
HomeStateResponse |
Home presence state |
HomeDeviceResponse |
Device information |
HomeInstallationResponse |
Installation details |
HomeUserResponse |
User information |
ZoneStateResponse |
Zone temperature and settings |
WeatherResponse |
Current weather data |
DayReportResponse |
Historical zone data |
Configure the client via application.conf:
tado4s {
api-auth = "https://login.tado.com"
api-base = "https://my.tado.com/api/v2"
http-timeout = 30 seconds
max-retries = 5
max-retry-time = 1 minute
ignore-ssl = false
client-id = ${?TADO4S_CLIENT_ID}
token-path = ~/.tado4s/token.conf
}Or programmatically:
import scala.concurrent.duration.*
import java.nio.file.Paths
val config =
TadoConfig(
apiBase = Uri.unsafeFromString("https://my.tado.com/api/v2"),
apiAuth = Uri.unsafeFromString("https://login.tado.com"),
clientId = "your-client-id",
tokenPath = Paths.get(System.getProperty("user.home"), ".tado4s", "token.conf"),
httpTimeout = 30.seconds,
maxRetries = 5,
maxRetryTime = 1.minute,
ignoreSsl = false,
)
val client = Tado4sClient[IO](Some(config))Tado4s automatically persists refresh tokens to disk at the configured token-path
(default: ~/.tado4s/token.conf). When authenticating:
- The client checks for an existing stored token
- Compares the stored token's issue time with the provided token
- Uses the newer token for authentication
- Saves the new refresh token after successful authentication
This ensures tokens survive application restarts and prevents token conflicts.
Tado4s uses typed errors for API failures:
import com.colofabrix.scala.tado4s.*
client
.getZoneState(homeId, zoneId)
.handleErrorWith {
case Tado4sError(message, Some(tadoError)) =>
IO.println(s"API error: ${tadoError.message}")
case Tado4sError(message, None) =>
IO.println(s"Client error: $message")
}Tado uses OAuth2 with device code flow:
- Run the authentication script:
python scripts/tado_device_auth.py - Follow the prompts to authorize the application
- Store the refresh token securely
- Create a
TadoRefreshTokenwith the token and issue time - Pass the refresh token to
client.authenticate()
The client automatically:
- Handles access token refresh when needed
- Persists new refresh tokens to disk
- Uses a thread-safe state machine to prevent concurrent token refreshes (single-flight pattern)
- Applies exponential backoff retry policy for transient failures
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Tado4s is released under the MIT license. See LICENSE for details.
- Tado API Reference
- Tado OpenAPI Spec
- http4s - Typeful, functional HTTP for Scala
- Cats Effect - The pure asynchronous runtime for Scala