Community tool that syncs RENPHO smart tape measure (and optionally smart scale) measurements into
SparkyFitness through its existing POST /api/health-data endpoint.
It is a standalone tool and needs no changes to SparkyFitness.
Unofficial API. RENPHO has no public API. This tool talks to the same cloud endpoints as the RENPHO app, using the protocol documented by the MIT-licensed
renpho-apiPython client (ported to TypeScript). It may break if RENPHO changes its API, and use may be restricted by RENPHO's Terms of Service. Use it only with your own account and at your own risk. Not affiliated with RENPHO or SparkyFitness.
-
In SparkyFitness, create an API key with write access (Settings -> API keys).
-
Set environment variables (see
.env.example):Variable Meaning RENPHO_EMAIL/RENPHO_PASSWORDRENPHO account (an email/password login; SSO-only accounts need a password set) RENPHO_AREA_CODEOptional, default US. Two-letter area stored on the Renpho Health account.renpho.comandrenpho.euare shop sites; both regions' Health accounts usehttps://cloud.renpho.com. Set the country code chosen in the app, for exampleDE,GB,FR, orNLRENPHO_API_URLOptional, default https://cloud.renpho.com. https origin only. Set this only if your account is on a different Renpho Health cloudSPARKY_URLBase URL of your SparkyFitness server, e.g. https://fit.example.comSPARKY_API_KEYThe API key from step 1 STATE_PATHOptional, default state.json: remembers that the initial full sync finishedLENGTH_UNITOptional, cm(default) orin: unit for the custom tape sites (chest, arms, ...). Set toinif your SparkyFitness measurement unit is inchesSYNC_DEVICESOptional, default scale,tape: which devices to sync.scale,tapeor bothSCALE_TABLESOptional: only sync these scale tables (comma-separated, e.g. measurements_info_8). Empty means every scale on the account. Find names with--list-devicesSYNC_RETRIESOptional, default 3: total tries per sync for temporary network or server errors (waits 30s, then 2 min)HEALTHCHECK_URLOptional: healthchecks.io-style URL, called after each successful sync and at <url>/failafter a failed oneNOTIFY_URLOptional: ntfy-style URL, POSTed once when syncs start failing and once when they recover SYNC_DAYSOptional, default 3: after the first run, how many recent days are re-sent on every syncFULL_SYNC/SINCE_DATEOptional: FULL_SYNC=truere-sends the whole history;SINCE_DATE=YYYY-MM-DDlimits the full sync to that date onwardSYNC_INTERVAL_MINUTESOptional: if > 0 the process keeps running and syncs on this interval -
npm install && npm run sync -- --dry-run, thennpm run sync. Run it from cron/systemd for regular syncs.
Test mode: --dry-run (alias --test) or TEST_MODE=true logs in to RENPHO, reads your data and prints every entry that would be sent (date, type, value, unit), without contacting SparkyFitness or saving state, so SPARKY_URL / SPARKY_API_KEY aren't needed. It shows what the next real run would send (the full history until the first real sync completes, then the last SYNC_DAYS days). In Docker, set TEST_MODE=true in .env and watch docker compose logs.
Debugging: --debug or DEBUG=true also logs what RENPHO's account reports: scale table names and counts, and how many records each endpoint returned. It logs counts only, never your measurement values. With debug on it also calls RENPHO's getTokenTime endpoint once per run and logs the response (tokens masked), to help work out how long a session lasts.
Choosing devices: docker compose run --rm sparkyfitness-renpho --list-devices (or npm run sync -- --list-devices) shows the scales and tape measure on your account without syncing anything (SparkyFitness settings aren't needed). Scales are identified by RENPHO's table name, not a friendly name.
Flags: --list-devices, --debug, --full, --dry-run/--test, --tape-only, --scale-only, --since=YYYY-MM-DD.
cp .env.example .env # fill in the values
docker compose up -d
docker compose logs -fdocker-compose.yml pulls the prebuilt multi-arch image (amd64/arm64) from
ghcr.io/jerrys-modz/renpho-bridge:latest, published by the Docker workflow on every push to main and on
v* tags. To build from source instead, replace the image: line with build: . and use up -d --build.
The container syncs every SYNC_INTERVAL_MINUTES (default 60) and keeps its state in the renpho-state
volume. Set SYNC_INTERVAL_MINUTES=0 to sync once and exit (for an external scheduler), e.g.
docker compose run --rm sparkyfitness-renpho --dry-run. If SparkyFitness runs in another compose project,
point SPARKY_URL at a host the container can reach (e.g. http://host.docker.internal:3010).
Like the SparkyFitness mobile app: the first run does a one-time full history sync, then every later run
re-sends only the last SYNC_DAYS days (default 3). The window is re-sent each time on purpose: SparkyFitness
upserts these values by day, so edits and late-arriving records are picked up and nothing is duplicated.
state.json only remembers that the initial sync finished; if SparkyFitness rejects anything during it, the
next run retries the full sync. Use --full / FULL_SYNC=true to redo the full history. Test mode never
saves state, so it keeps showing the full history until a real run completes.
- Retries: network errors, timeouts, and 5xx/408/429 responses are retried (
SYNC_RETRIES). Wrong credentials, a rejected API call or entries SparkyFitness refuses are not, since repeating a failed RENPHO login can lock the account. - Docker health: the image has a
HEALTHCHECKthat turns unhealthy if no sync has succeeded within two sync intervals plus 15 minutes (docker psshows it; autoheal tools and Portainer can act on it).status.jsonin the data volume holds the last success, last error and failure count. - Alerts: set
HEALTHCHECK_URLfor a dead-man's-switch service such as healthchecks.io, and/orNOTIFY_URL(for examplehttps://ntfy.sh/your-topic) for a push message when syncs start failing and when they recover. You get one alert per outage, not one per attempt. Alert calls never affect the sync itself.
Every RENPHO login creates a new session, which can sign the phone app out. To avoid doing that on every
sync, the tool saves its session token (session.json next to the state file, i.e. in the Docker volume,
readable only by the container user) and reuses it. It only logs in again when the saved token stops working,
so you may still be signed out of the app occasionally, for example right after the app logs in and replaces
the tool's session. Ways to reduce it further:
- Sync less often:
SYNC_INTERVAL_MINUTES=720(twice a day) or1440(daily) is plenty for a scale and tape. - Use a second RENPHO account for the bridge, if RENPHO lets you share the scale/tape with it (family or shared-device features), so the tool and your phone never share a session. This is untested.
| RENPHO | SparkyFitness |
|---|---|
| Tape: neck, waist, hip | check-in neck, waist, hips (cm) |
| Tape: shoulder, chest, abdomen, arm (and left/right), thigh, calf | custom measurements (cm), categories auto-created |
| Scale: weight, body fat %, body water %, BMR | check-in weight, body_fat, body_water_percentage, bmr |
| Scale: muscle mass (kg), bone mass (kg) | check-in muscle_mass_kg, bone_mass_kg |
| Scale: BMI, skeletal muscle %, visceral fat, subcutaneous fat %, protein %, metabolic age, fat-free weight, heart rate | custom measurements (units as RENPHO reports them: %, kg, level, years, bpm) |
| Tape: waist-to-hip ratio | custom measurement (unitless) |
| Tape: the five user-defined "custom" slots | not synced (RENPHO doesn't expose their names) |
Units: whatever unit the RENPHO app displays, each record carries its own unit code and the tool converts to
cm first. neck/waist/hips are always sent in cm, because SparkyFitness stores them in cm and shows them in the
user's chosen unit. The custom sites are stored with the unit they are sent in, so set LENGTH_UNIT=in if you
use inches (changing it later creates a separate category, since the category is keyed on name and unit). Days use the timezone stored on each RENPHO record.
Both the tape measure and the scale have been checked against real RENPHO accounts (including a 222-record scale history). RENPHO's API is unofficial, so field names can differ between scale models; if a value is missing or looks wrong, open an issue with the --debug output.
npm test, npm run typecheck. Licensed MIT; protocol details derived from renpho-api (MIT, danvaneijck).