aiotractive
Unofficial Asynchronous Python client for the Tractive REST API.
This project and its author are not affiliated with Tractive GmbH
This project is a result of reverse engineering of the Tractive web app.
Inspired by home_assistant_tractive.
Initially some code was borrowed from home_assistant_tractive, but in the end all of it was replaced with my own implementations.
The package is in active development. Not all features available in the Tractive web app are implemented.
Important notes:
- In order to use Tractive devices and their service you need to have an active subscription.
- Tractive may change their API at any point of time and this project will be broken. Please, report any issues.
Installation
pip install aiotractive
Usage
import asyncio
from aiotractive import Tractive
async def main():
async with Tractive("email", "password") as client:
# interact with the client here
pass
if __name__ == "__main__":
asyncio.run(main())
Tractive
Tractive is the entrypoint class, it acts as an async context manager and provides access to API endpoints.
Trackers
tracker = client.tracker("TRACKER_ID")
# Retrieve details
# Includes device capabilities, battery status (not level), charging state and so on
await tracker.details()
await tracker.hw_info() # Includes battery level, firmware version, model and so on
# Retrieve current location
await tracker.pos_report() # Includes coordinates, latitude, speed and so on
# Control the buzzer
await tracker.set_buzzer_active(True) # or False
# Control the LED
await tracker.set_led_active(True) # or False
# Control the live tracking
await tracker.set_live_tracking_active(True) # or False
Trackable objects (usually pets)
objects = await client.trackable_objects()
obj = objects[0]
# Or get a specific trackable object by ID
obj = client.trackable_object("TRACKABLE_ID")
# Retrieve details
await obj.details() # Includes pet's name, pet's tracker id and so on
# Retrieve health overview (activity, sleep, rest, and health metrics)
await obj.health_overview()
Status and push updates
The client can keep a typed, merged status of every tracker and pet, filled from REST and kept current by the event channel.
async with Tractive("email", "password", fetch_delay=2.0) as client:
trackables = await client.async_fetch_trackables() # pets with an owned tracker
for trackable in trackables:
print(trackable.name, trackable.pet_id, trackable.tracker_id)
# Refresh via REST, returns client.status
status = await client.async_fetch_status()
tracker_status = status.trackers[trackables[0].tracker_id]
print(tracker_status.battery_level, tracker_status.latitude)
# Create health sensors/UI only for pets with health data
for trackable in trackables:
if trackable.health_available is True:
...
def on_update(error: Exception | None) -> None:
if error is None:
print("status changed or channel (re)connected", client.status)
else:
print("channel error", error)
client.subscribe_updates(on_update)
listener = asyncio.create_task(client.listen())
...
listener.cancel()
async_fetch_trackables()returns a list ofTrackable(pet_id,tracker_id,pet_details,tracker_details,name,weightin grams) and caches it. Pets without a tracker and shared trackers (no details) are skipped; a tracker without an_idraisesTractiveError.async_fetch_status()refreshes every trackable via REST (fetching trackables first if needed) and returnsclient.status. Values are merged, so switch states received from events are kept. It also setsTrackable.health_available, so a renewed subscription is picked up on the next refresh.Trackable.health_availableisNoneuntil checked (noasync_fetch_status()call has run and no health event arrived),Truewhen the last health overview request returned a response (even an empty one) or ahealth_overviewevent arrived for the pet, andFalsewhen the API answered 400 or 403 (tracker without health features, or inactive subscription). The flag only changes onasync_fetch_status()or when a health event arrives; a lapsed subscription is noticed on the next refresh.async_refresh_pet_details()re-requests the details of every cached trackable (sequentially withfetch_delaybetween pets), updatesTrackable.pet_detailsandstatus.pets[pet_id].weight, and returnsclient.status. Fetches trackables first if none are cached. RaisesTractiveErrororUnauthorizedErrorlike other fetchers.client.statusis aTractiveStatuswithtrackers: dict[str, TrackerStatus]andpets: dict[str, PetStatus], keyed by tracker ID and pet ID.TrackerStatus:battery_level,tracker_state,battery_charging,power_saving,power_saving_zone,latitude,longitude,accuracy,sensor_used,buzzer,led,live_tracking.PetStatus:daily_goal,minutes_active,minutes_day_sleep,minutes_night_sleep,minutes_rest,weight(in grams).- Unknown values are
None. Switch states (buzzer,led,live_tracking) are only available from events.
subscribe_updates(listener)registers a callback. It is called withNonewhen the status changed or the channel (re)connected, and with an exception on a channel error: aTractiveErroris transient and the channel reconnects automatically after 10 seconds, anUnauthorizedErroris terminal and stops listening.listen()consumes events and updatesclient.statusuntil cancelled. The Tractive API does not push pet profile changes (a weight edited in the app produces no event), so whilelisten()runs, pet details are refreshed viaasync_refresh_pet_details()every 30 minutes (PET_DETAILS_REFRESH_INTERVAL, 1800 seconds), counted from the last fetch (async_fetch_trackables()/async_refresh_pet_details()) or from the start oflisten(), whichever is later. The update listener is called withNoneafter a successful refresh. ATractiveErrorduring refresh is logged as a warning, previous values are kept, the cached trackables are dropped so the next refresh fetches them again, and the attempt counts toward the interval;UnauthorizedErrorstops listening. Run it as a task and cancel it to stop.fetch_delay(default2.0seconds) is the pause between the REST requests of consecutive trackables, to avoid HTTP 429 (too many requests) responses.- Events without a known message type or
tracker_idare ignored and logged at debug level (Ignoring event ...). Supported events and the bodies of REST responses are logged at debug level by theaiotractive.tractiveandaiotractive.apiloggers. - Trackers returned by
client.tracker()are bound toclient.status: when aset_buzzer_active,set_led_activeorset_live_tracking_activecommand is accepted, the switch state is updated optimistically before the confirming event arrives.
Exceptions
The library raises the following exceptions:
TractiveError- Base exception classUnauthorizedError- When authentication fails or token is invalidForbiddenError- When access to a resource is denied (403). Authentication and the event channel report 403 asUnauthorizedErrorBadRequestError- When the server rejects the request (400)NotFoundError- When the requested resource is not found (404)DisconnectedError- When the event channel disconnects
Type Hints
This library is fully typed and includes a py.typed marker for PEP 561 compliance. Type checkers like mypy will recognize the inline type hints.
Creating a development environment
Requires uv.
python3 -m venv .venv
source .venv/bin/activate
uv sync --all-groups
uv run prek install
Contribution
You know;)
Metadata
Release files for aiotractive 2.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| aiotractive-2.0.0.tar.gz | 34.6 kB | Without provenance | Details for aiotractive-2.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| aiotractive-2.0.0-py3-none-any.whl | Python 3 | none | any | Details for aiotractive-2.0.0-py3-none-any.whl |
Total release size: 52.8 kB
Release files / aiotractive-2.0.0.tar.gz
| Download URL | aiotractive-2.0.0.tar.gz |
|---|---|
| Size | 34.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cbd1cb67434e0c225b7b4a7e3eada58f04c6024715b102ad64cbdf8ac847a4f5
|
|
BLAKE2b-256 checksum How to use checksums |
10b4dd58b2c8d267d7703aa748cb9f4bb839dc4f236c2a4fb927c4870489fa4f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / aiotractive-2.0.0-py3-none-any.whl
| Download URL | aiotractive-2.0.0-py3-none-any.whl |
|---|---|
| Size | 18.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8a4230ba8cd91ae5e143018ad4bc4967c87bb713296052136bccc3337abcd7b1
|
|
BLAKE2b-256 checksum How to use checksums |
d8c8a4701f6f0350a367b14cb6af0d6268614edc1d3513669f2317fb05863e4e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Security for aiotractive 2.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.