Metadata-Version: 2.5
Name: eufylife-api
Version: 0.2.0
Summary: Unofficial Python client for the EufyLife smart scale cloud API (weight, body fat, muscle mass, and more)
Project-URL: Homepage, https://github.com/ohsugeek/eufylife-api
Project-URL: Issues, https://github.com/ohsugeek/eufylife-api/issues
Author-email: Kenta Ohsugi <kenta.ohsugi@gmail.com>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Home Automation
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
Description-Content-Type: text/markdown

# eufylife-api

Unofficial Python client for the EufyLife smart scale cloud API. No Home
Assistant, no cloud functions, no dependencies beyond the standard library —
just weight, body fat %, muscle mass, and the rest of what your scale
measures, as plain Python objects.

```python
from eufylife_api import EufyLifeClient

client = EufyLifeClient("me@example.com", "hunter2")
for m in client.get_measurements():
    print(m.timestamp, m.weight_kg, m.muscle_mass_kg, m.body_fat_percent)
```

## Why this exists

Eufy/Anker do not publish a developer API for their smart scales. The
EufyLife app talks to `api.eufylife.com`, and a couple of projects have
reverse-engineered that protocol — but tied to a specific framework:
[`eufylife-api-hacs`](https://github.com/m4ary/eufylife-api-hacs) is a Home
Assistant integration, and `eufylife-mcp` is an MCP server. Neither is usable
as a plain library if you just want the data in a Python script, a Jupyter
notebook, or your own automation.

This package fills that gap: the same protocol, with no framework
dependency.

**Credit**: the request shapes, headers, and field names used here were
derived by reading the source of `m4ary/eufylife-api-hacs` (MIT licensed).
This is an independent re-implementation with no shared code and no
Home Assistant dependency, but the protocol knowledge it's built on comes
from that project.

## Install

Not yet published to PyPI. For now, install from a local checkout:

```bash
pip install -e .
```

## Usage

```python
from eufylife_api import EufyLifeClient

# Credentials are your EufyLife app login (email + password).
client = EufyLifeClient("me@example.com", "hunter2")

# Optional: persist the access token across runs instead of logging in every time.
client = EufyLifeClient("me@example.com", "hunter2", token_cache_path="~/.config/eufy/token.json")

measurements = client.get_measurements()          # all history
measurements = client.get_measurements(after=1735689600)  # only after this unix timestamp

for m in measurements:
    print(m.timestamp, m.weight_kg, m.body_fat_percent, m.muscle_mass_kg)
    if m.missing_fields:
        print("  this reading didn't include:", m.missing_fields)
```

Need a field this library doesn't model, or want the exact API response for
logging/debugging? Use `get_raw_device_data()` instead of `get_measurements()`.

## What you get

This is the differentiator: **every field the API sends**, not just the
handful that `eufylife-api-hacs` happens to surface as Home Assistant
sensors. Reading only that project's source would tell you the API has 10
metrics; the raw response actually has over 20.

**Confirmed** (verified against 68 real readings from a Smart Scale P3 —
consistently populated, and matched an independently hand-recorded value
where one existed): `weight_kg`, `body_fat_percent`, `body_fat_mass_kg`,
`muscle_mass_kg`, `skeletal_muscle_mass_kg`, `bone_mass_kg`, `water_percent`,
`subcutaneous_fat_rate_percent`, `visceral_fat_level`, `bmi`,
`bmr_kcal_per_day`, `body_age_years`, `protein_ratio_percent`,
`heart_rate_bpm`.

**Unconfirmed** (present in every response's shape, but `0` in all 68 real
samples used to build this — likely populated by other scale models, exposed
as-is with no scale/unit applied): `fat_free_weight_raw`, `muscle_raw`,
`water_weight_raw`, `visceral_fat_index_raw`, `bone_raw`,
`standard_weight_raw`, `height_raw`, `head_size_raw`, `impedance_raw`. If one
of these is ever non-zero for you, please open an issue with the raw value
and what the app displayed — that's how the "confirmed" list grows.

**Categorical** (small integers, not physical quantities — `0` may be a real
category, not an absence): `body_type`, `mode`, `fat_mode`.

Plus `timestamp`, `customer_id`, `device_id`, `product_code`, and the
original `raw` record for anything not modeled above.

Not every scale model reports every field. Check `measurement.missing_fields`
rather than assuming everything is populated (it excludes the categorical
fields, since 0 isn't necessarily an absence for those).

### ⚠ `body_fat_mass_kg` might just be `weight_kg × body_fat_percent`

The API does have a body-fat-*mass* field — earlier versions of this README
said otherwise, because that field isn't in `eufylife-api-hacs`'s mapping
and was missed on first read of the raw response. It's there.

But across 67 comparable readings, `body_fat_mass_kg` equalled
`round(weight_kg * body_fat_percent / 100, 1)` in 32 cases and was exactly
`0.1kg` below that in the other 35 — no other deviation ever appeared. That
bimodal, zero-variance pattern looks like a server-side computation off
slightly different-precision inputs, not an independently corroborating
bioimpedance measurement. So this field — and quite possibly the EufyLife
app's own "body fat mass" display — may already *be* the weight×percentage
calculation. Treat it as convenient, not as independent verification of
`body_fat_percent`.

## Reliability notes

This is an undocumented API. It can change without notice, and this library
has no way to detect that in advance. A few things it does to make failures
visible instead of silent:

- `get_raw_device_data()` gives you the exact server response, unmodified.
- Parsing failures and API error responses raise (`EufyLifeAPIError`,
  `EufyLifeAuthError`) rather than returning empty/default data.
- Missing fields on a reading are exposed via `Measurement.missing_fields`
  instead of being silently treated as zero.

What it does *not* do (yet): flag a value as statistically implausible (e.g.
a 5kg overnight weight swing), or diff successive readings for you. That's
left to the caller, since "what counts as implausible" is domain-specific.

## Background

Built while automating daily health tracking — Fitbit/Google Health sync
weight and body fat % from Eufy scales, but not muscle mass or several other
fields the scale itself reports, because those metrics don't exist in
Fitbit's or Apple Health's schema at all. This library exists to reach past
that relay and read the scale's own data directly.

## License

MIT — see [LICENSE](LICENSE).
