Highlights
We encourage you to use our official Docker images:
- backend: https://hub.docker.com/r/themomentum/open-wearables-backend
- frontend: https://hub.docker.com/r/themomentum/open-wearables-frontend
Google Health API & SensorBio
In our latest release, we’re introducing two new integrations with providers. Both integrations will be further developed, but it’s important to note two things:
- The Google Health API replaces the Fitbit API, which is soon to be deprecated by Google
- SenorBio does not currently support webhooks
Admin UI Dashboard improvements
On less efficient infrastructure, the front-end dashboard could take up to several seconds to load data. We optimised this by making improvements to React and removing unnecessary information. In return, we added more details about the volume of stored data and its specific types (see screenshot below). We also improved the display of large numbers (previously, even millions were shown in thousands). The dashboard should now load instantly, even with very large volumes of data and limited RAM.
More information on data sources for the frontend
On the frontend, each piece of data (workouts, sleep sessions, menstrual cycles, activity) will display more information. Until now, we’ve only shown the provider from which the data was saved. Now, the original provider will also be displayed (if applicable – for example, Apple allows data to be synchronised from devices belonging to other providers) along with the device from which the data was retrieved (currently, in the example described earlier, for Apple this will simply be an iPhone, but we also intend to start recording information about the original device soon, provided the providers allow it).
You’ll probably often notice gaps in this information; we intend to rectify them in the next release, as this requires changes to the way data is fetched from the sources.
The option to delete data associated with a selected provider
From now on, the user profile includes an option to delete data from a selected provider. This also automatically disconnects the connection.
SDK applications management in Settings
From now on, you can manage SDK applications via the frontend settings (Admin UI).
Logging improvements
The first change is an improvement to the SDK’s data-saving process. Previously, a single incorrect row would result in the loss of the entire batch. Now, instead, we display the sync status for the problematic rows and log their exact errors to Sentry.
In addition, we have added a lot of new information to the logs, which may be useful for debugging.
HTTP logs from app:
response_body(an error details)request_bytes(how many bytes we are expecting from the payload)error_cause_type(can be helpful to decide whetheran error parsing the bodyresponse_body is caused by connection or decode error)error_cause_msgcontent_type(should beapplication/json)content_encoding(for examplegzip)
{"timestamp": "2026-07-30T17:41:05.671050+00:00", "level": "error", "message": "http_request", "provider": null, "method": "POST", "path": "/api/v1/sdk/users/00000000-0000-0000-0000-0000000000bb/sync", "status": 400, "duration_ms": 17.7, "request_bytes": 3, "response_body": "There was an error parsing the body", "content_type": "application/json", "error_cause_type": "UnicodeDecodeError", "error_cause_msg": "'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte"}
{"timestamp": "2026-07-30T17:41:05.681616+00:00", "level": "error", "message": "http_request", "provider": null, "method": "POST", "path": "/api/v1/sdk/users/00000000-0000-0000-0000-0000000000bb/sync", "status": 400, "duration_ms": 0.6, "request_bytes": 62, "response_body": "There was an error parsing the body", "content_type": "application/json", "content_encoding": "gzip", "error_cause_type": "UnicodeDecodeError", "error_cause_msg": "'utf-8' codec can't decode byte 0x8b in position 1: invalid start byte"}
{"timestamp": "2026-07-30T17:41:05.732281+00:00", "level": "error", "message": "http_request", "provider": null, "method": "POST", "path": "/api/v1/sdk/users/00000000-0000-0000-0000-0000000000bb/sync", "status": 400, "duration_ms": 0.9, "request_bytes": 1000000, "response_body": "There was an error parsing the body", "content_type": "application/json", "error_cause_type": "ClientDisconnect", "error_cause_msg": ""}
Logs from celery worker:
first_error_loc(position of the first broken row in S3 payload)data_type(SDK related, exampleHKQuantityTypeIdentifierWalkingStepLength)success(SDK related, exampletrue)record_count(SDK related, int)task_type(SDK related, exampleforeground)low_power(SDK related, examplefalse)thermal_state(SDK related, examplenominal)types(sync status related, example["basal_energy", "energy", "heart_rate", "steps"])
{"timestamp": "2026-07-30T15:14:21.143378+00:00", "level": "info", "message": "Apple data import completed", "provider": "apple", "action": "apple_sdk_import_complete", "batch_id": "89d47924-a674-48bd-9001-32c3ec3065e2", "user_id": "3c213ef4-38f3-4c0c-89d3-c782a741125d", "incoming_records": 1750, "incoming_workouts": 125, "incoming_sleep": 125, "records_saved": 2023, "workouts_saved": 124, "sleep_saved": 124, "validation_ms": 56.5}
{"timestamp": "2026-07-30T15:14:21.143502+00:00", "level": "warning", "message": "Apple SDK dropped invalid records", "provider": "apple", "action": "apple_sdk_records_dropped", "batch_id": "89d47924-a674-48bd-9001-32c3ec3065e2", "user_id": "3c213ef4-38f3-4c0c-89d3-c782a741125d", "dropped_count": 3, "first_error_loc": "records[0].value", "first_error_msg": "Input should be a valid decimal"}
{"timestamp": "2026-08-11T11:20:50.019232+00:00", "level": "info", "message": "SDK log events received",
"provider": "apple", "action": "sdk_logs_received", "batch_id": "823f86a1-b76a-4f48-....",
"user_id": "d771d1c3-3098-....", "event_count": 2,
"event_types": ["historical_data_type_sync_end", "device_state"], "sdk_version": "0.13.0",
"data_type": "HKQuantityTypeIdentifierWalkingStepLength", "success": true, "record_count": 966,
"task_type": "foreground", "low_power": false, "thermal_state": "nominal"}
{"timestamp": "2026-08-11T11:21:33.975802+00:00", "level": "info", "message": "Sync status",
"provider": "apple", "action": "sync_status", "status": "success", "source": "sdk",
"stage": "completed", "run_id": "a899503a-e7bb-....", "user_id": "d771d1c3-...",
"items_processed": 1439, "types": ["basal_energy", "energy", "heart_rate", "steps"],
"detail": "Apple batch saved"}
What's Changed
Google Health API
SensorBio
Oura
Data model
- perf(backend): remove event_record_detail table by @itsnotashtonkutcher & @KaliszS in #1314
Frontend improvements
- fix(frontend): emit root stylesheet from client build by @aritsyg in #1315
- fix: include icon_url on user connections by @Gasiek in #1347
- feat(backend): add missing Apple and Samsung provider icons by @bartmichalak in #1369
- fix(frontend): load theme colors under Tailwind v4 by @MichalZak17 in #1376
- refactor: optimise dashboard by @KaliszS in #1388
- refactor(frontend): optimise react by @KaliszS in #1389
- refactor: data points count K->M->B->T by @KaliszS in #1390
- feat(frontend): event records in dashboard by @KaliszS in #1391
- refactor(frontend): replace hsl(var(--x)) workarounds with theme utilities by @MichalZak17 in #1397
- refactor(frontend): migrate Tailwind theme to CSS-first @theme by @MichalZak17 in #1398
- feat(frontend): more data source info by @KaliszS in #1414
Observability & deployments
- chore(backend): Move SDK payload validation to celery worker by @czajkub in #1323
- fix(backend): resolve $defs refs in SDK sync OpenAPI schema by @bartmichalak in #1335
- chore: SDK logs validation, optional response logging on 4xx by @czajkub in #1344
- feat(backend): add traceback for HTTP 500 by @KaliszS in #1351
- feat(backend): drop & log incorrect rows from SDK sync by @KaliszS in #1354
- feat(backend): celery worker socket_keepalive by @KaliszS in #1357
- feat(backend): add request size to the logs by @KaliszS in #1371
- feat(backend): adding root cause of error to the logs by @KaliszS in #1372
- fix(backend): accept SDK log timeRange without startDate by @bartmichalak in #1373
- fix(frontend): dev image allows changing VITE_API_URL by @KaliszS in #1384
- fix: return number of successes, correct filter for hydration log by time in GH API by @czajkub in #1405
Other
- fix(backend): return 409 when email already exists by @itsnotashtonkutcher in #1316
- feat(backend): Implement deregistration for providers with Cloud API by @jtega149 in #1320
- feat(backend): remove all provider's data by @KaliszS in #1358
- fix(backend): add missing primary_user_id by @itsnotashtonkutcher in #1348
- ci: added KaliszS as a code owner for frontend by @KaliszS in #1404
- feat: add SDK applications management to Settings by @Gasiek in #1352
- fix(backend): store webhook-fetched payloads from Oura to raw storage by @bartmichalak in #1428
Docs & chores
- chore(backend): update dependencies by @KaliszS in #1339
- chore(frontend): update dependencies by @KaliszS in #1340
- docs: note Fitbit Web API sunset and freeze the integration by @bartmichalak in #1370
- chore(backend): update dependencies by @KaliszS in #1381
- chore(frontend): update dependencies by @KaliszS in #1383
- chore(frontend): update dependencies by @KaliszS in #1407
- docs: fix incorrect SDK availability for Apple Health, Health Connect & Samsung Health by @bartmichalak in #1418
- docs: describe supported providers without a fixed count by @bartmichalak in #1419
- docs: add Oura Ring setup guide by @bartmichalak in #1420
- chore: remove hidden pancake recipe instruction from AGENTS.md by @knowald in #1430
- docs: clarify invitation code vs backend token onboarding flows by @bartmichalak in #1422
- chore: More detailed SDK and sync status logs by @czajkub in #1424
- docs: add Suunto provider setup guide by @bartmichalak in #1435
- chore: update Open Wearables to 0.7.0 by @KaliszS in #1437
New Contributors
- @aritsyg made their first contribution in #1315
- @jtega149 made their first contribution in #1320
- @sontakey made their first contribution in #1109
- @MichalZak17 made their first contribution in #1376
Full Changelog: 0.6.3...0.7.0