feat(api): accept an optional source on the measurements batch endpoint

Add an optional per-entry `source` to POST /api/measurements/batch,
restricted to the {APPLE_HEALTH, MANUAL} subset of MeasurementSource and
defaulting to APPLE_HEALTH when absent so existing web and iOS callers
are unchanged. The standalone iOS client can now tag adopt-on-pair
backfill rows that the user entered by hand as MANUAL instead of having
them mis-attributed to HealthKit.

source is threaded into the (userId, type, source, externalId) dedup
key, the existence probe, and the stats:* overwrite scope, so a MANUAL
row and an APPLE_HEALTH row sharing an externalId are distinct rows and
the stats:* re-post overwrite only touches the matching source.
WITHINGS and IMPORT stay server-owned and are rejected here with 422.

Regenerate the OpenAPI spec and add integration coverage for the
absent-source default, explicit MANUAL, cross-source distinctness, and
the rejected-source 422 path.
This commit is contained in:
Marc-André Bombeck committed 2026-06-01 22:54:20 +02:00
1 parent ca4425b2e0
commit 5d2ae7732e
4 files changed
+193 -6

No files matched your search

+9
View File
@@ -1992,6 +1992,15 @@ components:
type: string
minLength: 1
maxLength: 120
source:
description: Source attribution for the row. Defaults to `APPLE_HEALTH` when omitted. Send `MANUAL` to tag rows the user
entered by hand on-device (e.g. the standalone adopt-on-pair backfill). Part of the `(userId, type, source,
externalId)` dedup key, so the same externalId under a different source is a distinct row. `WITHINGS` /
`IMPORT` are not accepted here.
type: string
enum:
- APPLE_HEALTH
- MANUAL
deviceType:
description: Device class mapped from `HKDevice.model`. Used by the analytics aggregator to break ties when the same
source contributed multiple devices for the same day. Omit (or send null) on legacy clients — the server