Skip to content

Exporters

BLE Scale Sync exports body composition data to 7 targets. The setup wizard walks you through exporter selection, configuration, and connectivity testing.

Exporters are configured in global_exporters (shared by all users). For multi-user setups with separate accounts, see Per-User Exporters. All enabled exporters run in parallel — the process reports an error only if every exporter fails.

TargetDescription
Garmin ConnectAutomatic body composition upload — no phone app needed
MQTTHome Assistant auto-discovery with 10 sensors, LWT
InfluxDBTime-series database (v2 write API)
WebhookAny HTTP endpoint — n8n, Make, Zapier, custom APIs
NtfyPush notifications to phone/desktop
File (CSV/JSONL)Append readings to a local file
StravaUpdate weight in your Strava athlete profile

Garmin Connect

Automatic body composition upload to Garmin Connect — no phone app needed. Uses a Python subprocess with cached authentication tokens.

FieldRequiredDefaultDescription
emailYesGarmin account email
passwordYesGarmin account password
token_dirNo~/.garmin_tokensDirectory for cached auth tokens
yaml
global_exporters:
  - type: garmin
    email: '${GARMIN_EMAIL}'
    password: '${GARMIN_PASSWORD}'

Authentication

The setup wizard handles Garmin authentication automatically. You only need to authenticate once — tokens are cached and reused. To re-authenticate manually:

Native:

bash
npm run setup-garmin

Docker (single user with env vars):

bash
docker run --rm -it \
  -v ./config.yaml:/app/config.yaml \
  -v garmin-tokens:/home/node/.garmin_tokens \
  -e GARMIN_EMAIL \
  -e GARMIN_PASSWORD \
  ghcr.io/kristianp26/ble-scale-sync:latest setup-garmin

Docker (specific user from config.yaml):

bash
docker run --rm -it \
  -v ./config.yaml:/app/config.yaml \
  -v garmin-tokens-alice:/home/node/.garmin_tokens_alice \
  -e GARMIN_EMAIL -e GARMIN_PASSWORD \
  ghcr.io/kristianp26/ble-scale-sync:latest setup-garmin --user Alice

Docker (all users from config.yaml):

bash
docker run --rm -it \
  -v ./config.yaml:/app/config.yaml \
  -v garmin-tokens-alice:/home/node/.garmin_tokens_alice \
  -v garmin-tokens-bob:/home/node/.garmin_tokens_bob \
  -e GARMIN_EMAIL -e GARMIN_PASSWORD \
  ghcr.io/kristianp26/ble-scale-sync:latest setup-garmin --all-users

IP blocking

Garmin may block requests from cloud/VPN IPs. If authentication fails, try from a different network, then copy the token directory to your target machine.

MQTT

Publishes body composition as JSON to an MQTT broker. Home Assistant auto-discovery is enabled by default — all 10 metrics appear as sensors grouped under a single device, with availability tracking (LWT) and display precision per metric.

FieldRequiredDefaultDescription
broker_urlYesmqtt://host:1883 or mqtts:// for TLS
topicNoscale/body-compositionPublish topic
qosNo1QoS level (0, 1, or 2)
retainNotrueRetain last message
usernameNoBroker auth username
passwordNoBroker auth password
client_idNoble-scale-syncMQTT client identifier
ha_discoveryNotrueHome Assistant auto-discovery
ha_device_nameNoBLE ScaleDevice name in Home Assistant
yaml
global_exporters:
  - type: mqtt
    broker_url: 'mqtts://broker.example.com:8883'
    username: myuser
    password: '${MQTT_PASSWORD}'

Webhook

Sends body composition as JSON to any HTTP endpoint. Works with n8n, Make, Zapier, or custom APIs.

FieldRequiredDefaultDescription
urlYesTarget URL
methodNoPOSTHTTP method
headersNoCustom headers (YAML object)
timeoutNo10000Request timeout in ms
yaml
global_exporters:
  - type: webhook
    url: 'https://example.com/hook'
    headers:
      X-Api-Key: '${WEBHOOK_API_KEY}'

InfluxDB

Writes metrics to InfluxDB v2 using line protocol. Float fields use 2 decimal places, integer fields use i suffix.

FieldRequiredDefaultDescription
urlYesInfluxDB server URL
tokenYesAPI token with write access
orgYesOrganization name
bucketYesDestination bucket
measurementNobody_compositionMeasurement name
yaml
global_exporters:
  - type: influxdb
    url: 'http://localhost:8086'
    token: '${INFLUXDB_TOKEN}'
    org: my-org
    bucket: my-bucket

Ntfy

Push notifications to phone/desktop via ntfy. Works with ntfy.sh or self-hosted instances.

FieldRequiredDefaultDescription
urlNohttps://ntfy.shNtfy server URL
topicYesTopic name
titleNoScale MeasurementNotification title
priorityNo3Priority (1–5)
tokenNoBearer token auth
usernameNoBasic auth username
passwordNoBasic auth password
yaml
global_exporters:
  - type: ntfy
    topic: my-scale
    priority: 4

File (CSV/JSONL)

Append each reading to a local CSV or JSONL file. Useful for simple logging without external services.

FieldRequiredDefaultDescription
file_pathYesPath to the output file
formatNocsvcsv or jsonl
yaml
global_exporters:
  - type: file
    file_path: './measurements.csv'
    format: csv

CSV files get an automatic header row on first write. JSONL files append one JSON object per line.

Docker

Mount a volume so the file persists across container restarts:

yaml
volumes:
  - scale-data:/app/data
# config.yaml: file_path: './data/measurements.csv'

Strava

Update your weight in the Strava athlete profile. Requires a Strava API application.

FieldRequiredDefaultDescription
client_idYesStrava API application client ID
client_secretYesStrava API application client secret
token_dirNo./strava-tokensDirectory for cached OAuth tokens
yaml
users:
  - name: Alice
    exporters:
      - type: strava
        client_id: '${STRAVA_CLIENT_ID}'
        client_secret: '${STRAVA_CLIENT_SECRET}'

Creating a Strava API Application

  1. Go to strava.com/settings/api
  2. Upload an Application Icon (required before you can save the form)
  3. Fill in the application details:
    • Application Name: anything you like (e.g. BLE Scale Sync)
    • Category: choose any
    • Website: can be anything (e.g. https://github.com/KristianP26/ble-scale-sync)
    • Authorization Callback Domain: set to localhost (the OAuth flow redirects here, but the page does not need to load)
  4. Save and copy the Client ID and Client Secret

Callback Domain

The Authorization Callback Domain must be set to localhost. During the OAuth flow, Strava redirects to http://localhost?code=XXXX. The page will not load (nothing is listening), but you only need to copy the code parameter from the URL bar.

Authentication

After adding the Strava exporter to your config, run the setup script to authorize:

Native:

bash
npm run setup-strava

Docker:

bash
docker run --rm -it \
  -v ./config.yaml:/app/config.yaml \
  -v strava-tokens:/app/strava-tokens \
  ghcr.io/kristianp26/ble-scale-sync:latest setup-strava

The script prints a browser URL for Strava authorization. After authorizing, copy the code parameter from the redirect URL and paste it back. Tokens are cached and automatically refreshed.

Secrets

Use ${ENV_VAR} references in YAML for passwords and tokens. The variable must be defined in the environment or in a .env file:

yaml
global_exporters:
  - type: garmin
    email: '${GARMIN_EMAIL}'
    password: '${GARMIN_PASSWORD}'

See Configuration — Environment Variables for details.

Healthchecks

At startup, exporters are tested for connectivity. Failures are logged as warnings but don't block the scan.

ExporterMethod
MQTTConnect + disconnect
WebhookHEAD request
InfluxDB/health endpoint
Ntfy/v1/health endpoint
GarminNone (Python subprocess)
FileDirectory writable check
StravaNone (avoid API rate limits)

Released under the GPL-3.0 License.