docs(measurements): label EXTERNAL and update the guide and contract

The measurements list already badges any row whose source is not MANUAL and
already builds its source filter from the shared enum, so both pick the new
value up on their own. What it needs is a name: `EXTERNAL` reads as a leaked
enum constant, and it is the only new value a user is expected to operate,
since it is an option in the filter and a badge on every bridged row.

Both label catalogues gain an entry. They are keyed by the enum, so a new
source without a label is a type error and the coverage guard fails the
suite — which is exactly what happened here, and is the reason this carries
a settings label too even though EXTERNAL sits on no priority ladder and so
never renders there.

The settings card's scope note and the Home Assistant guide both claimed
readings arrive "as if you had entered them by hand", which is no longer
true. Both now say the rows are marked as coming from an external device and
are still yours to correct, and the guide spells out that the payload must
not name a source at all.

The changelog entries go under Unreleased as Changed: v1.38.1 shipped the
token, so these describe a change to something people already have. They say
what that means in practice — rows written before this keep the old label
and cannot be sorted out afterwards, a batch re-push can duplicate because
the dedup key now carries the source, and an automation sending an explicit
`source` field has to drop it.
This commit is contained in:
Antiheld86 authored and Marc-André Bombeck committed 2026-09-25 06:55:17 +02:00
1 parent 112a029443
commit 9fc1413181
14 files changed
+238 -41

No files matched your search

+23 -8
View File
@@ -47,21 +47,36 @@ It also cannot be pointed at somebody else's record. If an account has shared
their record with you, this credential still will not write to it; use the web
app for that.
## Readings arrive as manually entered
## Readings are marked as coming from an external device
Rows written with this token carry `source: MANUAL`, the same label a reading
you type in by hand gets. Two consequences worth knowing:
Rows written with this token carry `source: EXTERNAL`. They show a badge in the
measurements list and can be picked out with the source filter there, so months
later you can still tell which readings came off the scale and which you typed
in yourself. Three consequences worth knowing:
- You can correct them in the UI. Readings owned by a connected provider are
read-only; these are not.
- They do not claim to be from Apple Health. An entry that explicitly names
`APPLE_HEALTH` is refused with a 422 rather than quietly relabelled — that
source is half the deduplication key the iOS app writes into, and a bridge
borrowing it corrupts the phone's sync rather than merely mislabelling a row.
read-only, because the number is the provider's; these are your own hardware's
and stay editable.
- **The payload must not name a source at all.** The server decides it from the
token. A body carrying `"source": "MANUAL"`, `"source": "APPLE_HEALTH"` or
anything else is refused with a 422 rather than quietly relabelled — you get
told on the first call instead of discovering later that your rows say
something you did not ask for. The example below sends no `source`, which is
all you need.
- They do not claim to be from Apple Health. That source is half the
deduplication key the iOS app writes into, and a bridge borrowing it corrupts
the phone's sync rather than merely mislabelling a row. `EXTERNAL` gives these
writes their own namespace instead, so a bridge's ids can never collide with
the phone's.
For the same reason, a push through this token does not move the native sync
checkpoint. Your phone's own sync window is unaffected by your scale.
If you were already pushing readings with an older build, those rows stay
`MANUAL` — there is no way to tell them apart retroactively, which is the whole
reason for this label. If a re-push of the same entries produces duplicates,
delete the older pair.
## Home Assistant example
Add a `rest_command` to `configuration.yaml`. Keep the token in