openapi: 3.1.0 info: title: HealthLog API version: 1.5.0 description: >- Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest. ## Date-time contract Every field marked `format: date-time` is an ISO-8601 instant. The schema pattern accepts both `Z` (UTC) and `±HH:MM` (offset) suffixes; clients must accept either. Server responses since v1.4.25 emit timestamps with the requesting user's display-timezone offset when a user context exists (exports, doctor-report PDF). Background-job-authored payloads (backups, admin tools) emit `Z`. Native iOS clients should always parse via a Foundation ISO-8601 formatter with the `withInternetDateTime` + `withTimeZone` options enabled. license: name: AGPL-3.0 url: https://github.com/MBombeck/HealthLog/blob/main/LICENSE contact: name: HealthLog url: https://healthlog.dev servers: - url: https://healthlog.bombeck.io description: Production - url: http://localhost:3000 description: Local dev tags: - name: Auth - name: Measurements - name: Medications - name: Mood - name: Insights - name: Notifications - name: Devices - name: Admin paths: /api/auth/login: post: tags: - Auth summary: Email-or-username login (password) description: "Browser callers receive a session cookie. Native callers (X-Client-Type: native or HealthLog-iOS UA prefix) additionally receive a paired access + refresh token." requestBody: required: true content: application/json: schema: type: object properties: email: type: string minLength: 1 password: type: string minLength: 1 required: - email - password responses: "200": description: Login succeeded. content: application/json: schema: $ref: "#/components/schemas/LoginResponse" "401": &a1 description: Authentication required or invalid credentials. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": &a2 description: Request validation failed. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": &a3 description: Rate limit exceeded. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" /api/auth/passkey/login-verify: post: tags: - Auth summary: Passkey assertion verification requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/PasskeyLoginVerifyRequest" responses: "200": description: Assertion verified — session + optional bearer issued. content: application/json: schema: $ref: "#/components/schemas/PasskeyLoginVerifyResponse" "401": *a1 "422": *a2 "429": *a3 /api/auth/refresh: post: tags: - Auth summary: Rotate refresh token (one-time use) description: Reuse of a consumed refresh token revokes every refresh token still active for the originating device (per-device blast radius from v1.4.23). Legacy tokens issued before v1.4.23 with a null deviceId fall back to revoke-all-for-user. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RefreshTokenRequest" responses: "200": description: Rotation succeeded — new pair issued. content: application/json: schema: $ref: "#/components/schemas/RefreshResponse" "401": *a1 "422": *a2 "429": *a3 /api/measurements: get: tags: - Measurements summary: List measurements description: Filter by type + date range. Response is paged via offset/limit; default limit 100, hard cap 500. parameters: - in: query name: type schema: type: string enum: - WEIGHT - BLOOD_PRESSURE_SYS - BLOOD_PRESSURE_DIA - PULSE - BODY_FAT - SLEEP_DURATION - ACTIVITY_STEPS - BLOOD_GLUCOSE - TOTAL_BODY_WATER - BONE_MASS - OXYGEN_SATURATION - HEART_RATE_VARIABILITY - RESTING_HEART_RATE - ACTIVE_ENERGY_BURNED - FLIGHTS_CLIMBED - WALKING_RUNNING_DISTANCE - VO2_MAX - BODY_TEMPERATURE - FAT_FREE_MASS - FAT_MASS - MUSCLE_MASS - SKIN_TEMPERATURE - PULSE_WAVE_VELOCITY - VASCULAR_AGE - VISCERAL_FAT - AUDIO_EXPOSURE_ENV - AUDIO_EXPOSURE_HEADPHONE - TIME_IN_DAYLIGHT - WALKING_STEADINESS - AUDIO_EXPOSURE_EVENT - in: query name: from schema: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ - in: query name: to schema: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ - in: query name: limit schema: default: 100 type: integer minimum: 1 maximum: 5000 - in: query name: offset schema: default: 0 type: integer minimum: 0 maximum: 9007199254740991 - in: query name: sortBy schema: default: measuredAt type: string enum: - type - value - measuredAt - source - in: query name: sortDir schema: default: desc type: string enum: - asc - desc - in: query name: aggregate schema: type: string enum: - raw - daily - weekly - monthly - in: query name: source schema: type: string enum: - rollup - in: query name: groupBy schema: type: string enum: - day - in: query name: dayKey schema: type: string pattern: ^\d{4}-\d{2}-\d{2}$ responses: "200": description: Measurement page. content: application/json: schema: $ref: "#/components/schemas/ListMeasurementsResponse" "401": *a1 "422": *a2 "429": *a3 post: tags: - Measurements summary: Create one measurement description: Single ingest. Use `/api/measurements/batch` for Apple Health upload streams. requestBody: required: true content: application/json: schema: type: object properties: type: type: string enum: - WEIGHT - BLOOD_PRESSURE_SYS - BLOOD_PRESSURE_DIA - PULSE - BODY_FAT - SLEEP_DURATION - ACTIVITY_STEPS - BLOOD_GLUCOSE - TOTAL_BODY_WATER - BONE_MASS - OXYGEN_SATURATION - HEART_RATE_VARIABILITY - RESTING_HEART_RATE - ACTIVE_ENERGY_BURNED - FLIGHTS_CLIMBED - WALKING_RUNNING_DISTANCE - VO2_MAX - BODY_TEMPERATURE - FAT_FREE_MASS - FAT_MASS - MUSCLE_MASS - SKIN_TEMPERATURE - PULSE_WAVE_VELOCITY - VASCULAR_AGE - VISCERAL_FAT - AUDIO_EXPOSURE_ENV - AUDIO_EXPOSURE_HEADPHONE - TIME_IN_DAYLIGHT - WALKING_STEADINESS - AUDIO_EXPOSURE_EVENT value: type: number measuredAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ notes: type: string maxLength: 25 source: default: MANUAL type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH glucoseContext: type: string enum: - FASTING - POSTPRANDIAL - RANDOM - BEDTIME deviceType: anyOf: - type: string minLength: 1 maxLength: 32 - type: "null" required: - type - value - measuredAt responses: "201": description: Created. content: application/json: schema: $ref: "#/components/schemas/CreateMeasurementResponse" "401": *a1 "422": *a2 "429": *a3 /api/measurements/batch: post: tags: - Measurements summary: Apple Health batch ingest description: Up to 500 HealthKit entries per call. Idempotent via the `Idempotency-Key` header (replay window 24h). Per-entry status lets the iOS client advance its sync cursor accurately. v1.4.25 W8c adds an optional `deviceType` per entry — feed it from `HKDevice.model` so the cross-source canonical picker can break Apple-Watch-vs-iPhone ties; null/absent stays backward-compatible with v1.4.23 clients. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AppleHealthBatchRequest" responses: "200": description: Batch processed. content: application/json: schema: $ref: "#/components/schemas/BatchMeasurementsResponse" "401": *a1 "422": *a2 "429": *a3 /api/measurements/by-external-ids: delete: tags: - Measurements summary: Delete measurements by external ID (iOS deletion-sync) description: Removes the user's measurement rows whose externalId is in the request list. Rows owned by another user are silently skipped (cross-user 404 guard). Up to 500 externalIds per call. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/MeasurementsDeleteByExternalIdsRequest" responses: "200": description: Delete batch processed. content: application/json: schema: $ref: "#/components/schemas/MeasurementsDeleteByExternalIdsResponse" "401": *a1 "422": *a2 "429": *a3 /api/workouts: get: tags: - Measurements summary: List workouts (v1.4.32) description: "Paginated workout list with cross-source canonical-row dedup. The picker reads the per-user source-priority ladder and buckets by `(startedAt 5 min slot, sportType)` — twin workouts (Apple Watch + Withings ScanWatch) collapse to a single canonical row per cluster. Field names mirror the iOS handoff contract: `distanceM`, `activeEnergyKcal`, `avgHr`, `maxHr`." parameters: - in: query name: limit schema: type: integer minimum: 1 maximum: 200 - in: query name: offset schema: type: integer minimum: 0 maximum: 9007199254740991 - in: query name: since schema: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ - in: query name: until schema: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ - in: query name: sportType schema: type: string responses: "200": description: Canonical workout page. content: application/json: schema: $ref: "#/components/schemas/ListWorkoutsResponse" "401": *a1 "422": *a2 "429": *a3 /api/workouts/{id}: get: tags: - Measurements summary: Workout detail (v1.4.32) description: Single-workout envelope. Owns the optional `WorkoutRoute` GeoJSON geometry + `canonicalId` pointer that resolves to the cluster winner so deep-links into non-canonical twin rows can redirect cleanly. Cross-user rows surface as 404 (existence channel sealed). parameters: - in: path name: id schema: type: string required: true responses: "200": description: Workout detail. content: application/json: schema: $ref: "#/components/schemas/GetWorkoutResponse" "401": *a1 "404": description: Workout not found (or owned by another user). content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a2 "429": *a3 /api/workouts/batch: post: tags: - Measurements summary: Typed workout batch ingest (v1.4.25 W16b) description: Server-side ingest endpoint for HKWorkout records (iOS) and Withings activity rows (server-to-server). Up to 100 workouts per call; nested route geometry (GeoJSON LineString) is capped at 20 000 points and stored in a 1:1 `WorkoutRoute` row keyed by `workoutId`. Idempotent via the `Idempotency-Key` header (replay window 24h). Per-entry status (`inserted | duplicate | skipped`) lets the iOS sync cursor checkpoint accurately. The request body ceiling is 5 MB enforced at the HTTP layer — clients above the ceiling receive a 413 with `workout.batch.payload_too_large`. requestBody: required: true content: application/json: schema: type: object properties: workouts: minItems: 1 maxItems: 100 type: array items: type: object properties: sportType: type: string enum: - walking - running - cycling - hiking - swimming - rowing - elliptical - stairClimber - yoga - mindAndBody - strength - hiit - dance - golf - tennis - basketball - soccer - crossTraining - mixedCardio - other startedAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ endedAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ totalEnergyKcal: type: number minimum: 0 maximum: 50000 totalDistanceM: type: number minimum: 0 maximum: 1000000 avgHeartRate: type: integer minimum: 20 maximum: 300 maxHeartRate: type: integer minimum: 20 maximum: 300 minHeartRate: type: integer minimum: 20 maximum: 300 stepCount: type: integer minimum: 0 maximum: 200000 elevationM: type: number minimum: -500 maximum: 10000 pauseDurationSec: type: integer minimum: 0 maximum: 86400 source: default: MANUAL type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH externalId: type: string maxLength: 128 externalSourceVersion: type: string maxLength: 64 metadata: type: object propertyNames: type: string additionalProperties: {} route: type: object properties: geometry: type: object properties: type: type: string const: LineString coordinates: minItems: 2 maxItems: 20000 type: array items: type: array prefixItems: - type: number minimum: -180 maximum: 180 - type: number minimum: -90 maximum: 90 items: type: number required: - type - coordinates sampleTimestamps: type: array items: type: object properties: t: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ speedMs: type: number minimum: 0 maximum: 50 hr: type: integer minimum: 20 maximum: 300 required: - t required: - geometry required: - sportType - startedAt - endedAt required: - workouts responses: "200": description: Batch processed. content: application/json: schema: $ref: "#/components/schemas/BatchWorkoutsResponse" "400": description: Batch validation failed (over-cap, schema reject, or oversized route). content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "413": description: Request body exceeds the 5 MB ceiling. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a2 "429": *a3 /api/devices: post: tags: - Devices summary: Register native device + APNs token description: Idempotent upsert by `token`. APNs token + environment are paired — supplying one without the other returns 422. Cross-user re-registration of either identifier returns 409. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/DeviceRegisterRequest" responses: "201": description: Device registered or refreshed. content: application/json: schema: $ref: "#/components/schemas/DeviceRegisterResponse" "401": *a1 "409": description: Device or APNs token already registered to another user. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a2 "429": *a3 /api/auth/me/coach-prefs: get: tags: - Auth summary: Read per-user Coach prompt-tuning preferences description: Returns the persisted preferences with defaults filled in. Null persisted state resolves to the legacy v1.4.22 defaults. responses: "200": description: Resolved preferences. content: application/json: schema: $ref: "#/components/schemas/GetCoachPrefsResponse" "401": *a1 "422": *a2 "429": *a3 put: tags: - Auth summary: Replace per-user Coach prompt-tuning preferences description: Persists the supplied prefs. Body is validated against `CoachPrefs`; missing keys fall back to the documented defaults. requestBody: required: true content: application/json: schema: type: object properties: tone: default: warm type: string enum: - warm - neutral - concise verbosity: default: default type: string enum: - brief - default - detailed excludeMetrics: default: [] maxItems: 11 type: array items: type: string enum: - bp - weight - pulse - mood - compliance - hrv - sleep - resting_hr - steps - medications - anthropometrics showEvidenceByDefault: default: false type: boolean defaultWindow: default: allTime type: string enum: - last7days - last30days - last90days - allTime responses: "200": description: Saved preferences echoed back. content: application/json: schema: $ref: "#/components/schemas/PutCoachPrefsResponse" "401": *a1 "422": *a2 "429": *a3 /api/auth/me/disable-coach: get: tags: - Auth summary: Read per-user Coach opt-out flag description: Returns the user's per-account Coach opt-out flag. Default `false` (Coach visible). Powers the Settings → Insights "Hide Coach" Switch and the layout FAB short-circuit. responses: "200": description: Resolved flag. content: application/json: schema: $ref: "#/components/schemas/GetDisableCoachResponse" "401": *a1 "422": *a2 "429": *a3 patch: tags: - Auth summary: Toggle per-user Coach opt-out flag description: Flips the per-account Coach opt-out flag. Idempotent — the DB write fires even when the value matches so the audit-log row mirrors the API call. Rate-limit 60/min per user. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/DisableCoachFlag" responses: "200": description: Resolved next-state echoed back for optimistic-update consumers. content: application/json: schema: $ref: "#/components/schemas/PatchDisableCoachResponse" "401": *a1 "422": *a2 "429": *a3 /api/auth/me/source-priority: get: tags: - Auth summary: Read per-user source priority description: Returns the per-metric-class source priority used by the analytics aggregator. Missing keys fall back to `DEFAULT_SOURCE_PRIORITY`. Null persisted state resolves to the documented defaults. responses: "200": description: Resolved priority. content: application/json: schema: $ref: "#/components/schemas/GetSourcePriorityResponse" "401": *a1 "422": *a2 "429": *a3 put: tags: - Auth summary: Replace per-user source priority description: Persists the supplied priority. Body is validated against `SourcePriority`; missing keys read as `DEFAULT_SOURCE_PRIORITY` at the call site. requestBody: required: true content: application/json: schema: type: object properties: steps: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH activeEnergy: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH walkingRunningDistance: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH flightsClimbed: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH sleep: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH weight: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bloodPressure: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH pulse: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyFat: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyTemperature: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH spo2: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH hrv: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH restingHeartRate: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH vo2Max: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH metricPriority: type: object properties: steps: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH activeEnergy: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH walkingRunningDistance: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH flightsClimbed: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH sleep: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH weight: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bloodPressure: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH pulse: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyFat: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyTemperature: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH spo2: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH hrv: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH restingHeartRate: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH vo2Max: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH deviceTypePriority: type: object properties: default: maxItems: 8 type: array items: type: string enum: - watch - band - ring - phone - scale - other - unknown additionalProperties: maxItems: 8 type: array items: type: string enum: - watch - band - ring - phone - scale - other - unknown responses: "200": description: Saved priority (defaulted) echoed back. content: application/json: schema: $ref: "#/components/schemas/PutSourcePriorityResponse" "401": *a1 "422": *a2 "429": *a3 /api/insights/chat/messages/{id}/feedback: post: tags: - Insights summary: Rate a Coach assistant message description: Persists a helpful/unhelpful rating for a single Coach reply. Reuses the v1.4.16 RecommendationFeedback table via the polymorphic `targetType` column. The aggregator buckets ratings by (promptVersion, tone, verbosity). requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/CoachMessageFeedbackRequest" responses: "201": description: Feedback saved. content: application/json: schema: $ref: "#/components/schemas/CoachMessageFeedbackResponse" "401": *a1 "404": description: Message not found or not owned by the caller. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" "409": description: Caller has already rated this message text. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a2 "429": *a3 /api/admin/notifications/diagnostic: get: tags: - Admin summary: Admin notification diagnostic snapshot description: "Admin-only. Returns what the dispatcher would see when targeting the calling admin's account: registered devices (APNs tokens masked to prefix + suffix), per-channel enabled + configPresent flags, and recent push attempts (currently always `[]` pending the v1.4.48+ PushAttempt table). Cookie auth only — never Bearer." responses: "200": description: Diagnostic snapshot. content: application/json: schema: $ref: "#/components/schemas/AdminDiagnosticResponse" "401": *a1 "403": description: Caller is not an admin. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" "422": *a2 "429": *a3 /api/insights/comprehensive: get: tags: - Insights summary: Comprehensive AI insights bundle description: Full Insights surface — daily briefing, recommendations with rationale, optional weekly report + storyboard annotations. Strict-schema validated server-side. responses: "200": description: Insights bundle. content: application/json: schema: $ref: "#/components/schemas/InsightsComprehensiveResponseEnvelope" "401": *a1 "422": *a2 "429": *a3 components: schemas: PasskeyLoginVerifyRequest: type: object properties: challengeId: type: string minLength: 1 description: Server-issued WebAuthn challenge id. credential: type: object properties: id: type: string rawId: type: string type: type: string const: public-key response: type: object propertyNames: type: string additionalProperties: {} authenticatorAttachment: type: string enum: - platform - cross-platform clientExtensionResults: type: object propertyNames: type: string additionalProperties: {} required: - id - rawId - type - response description: SimpleWebAuthn-style assertion response payload. required: - challengeId - credential description: Passkey assertion verification. Same native-client token issuance as the password path. RefreshTokenRequest: type: object properties: refreshToken: type: string minLength: 1 description: Caller-presented refresh token (`hlr_<64hex>`). revoke: description: When true, revoke the supplied token instead of rotating. type: boolean required: - refreshToken description: Exchange a one-time-use refresh token for a fresh access + refresh pair. AppleHealthBatchRequest: type: object properties: entries: minItems: 1 maxItems: 500 type: array items: $ref: "#/components/schemas/AppleHealthBatchEntry" required: - entries description: Apple Health batch ingest. ≤500 entries per call; idempotent via `Idempotency-Key`. AppleHealthBatchEntry: type: object properties: hkIdentifier: type: string minLength: 1 maxLength: 120 description: HealthKit identifier (e.g. `HKQuantityTypeIdentifierBodyMass`). value: type: number unit: type: string minLength: 1 maxLength: 60 startDate: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ endDate: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ sleepStage: description: HKCategoryValueSleepAnalysis codepoint; only for sleep samples. type: integer minimum: 0 maximum: 20 externalId: type: string minLength: 1 maxLength: 120 description: HKSample.uuid string — the dedup key. externalSourceVersion: type: string minLength: 1 maxLength: 120 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 treats it as `unknown` and the picker falls through. anyOf: - type: string enum: - watch - band - ring - phone - scale - other - unknown - type: "null" required: - hkIdentifier - value - unit - startDate - endDate - externalId MeasurementsDeleteByExternalIdsRequest: type: object properties: externalIds: minItems: 0 maxItems: 500 type: array items: type: string minLength: 1 maxLength: 120 required: - externalIds description: iOS deletion-sync. Up to 500 externalIds per call; matching rows owned by other users are silently skipped (cross-user 404 guard). DeviceRegisterRequest: type: object properties: token: type: string minLength: 8 maxLength: 512 pattern: ^[A-Za-z0-9+/=._:-]+$ description: Generic device identifier (legacy; APNs token below). bundleId: type: string minLength: 1 maxLength: 128 locale: type: string minLength: 2 maxLength: 16 appVersion: type: string minLength: 1 maxLength: 32 model: type: string minLength: 1 maxLength: 64 apnsToken: description: Hex-encoded APNs device token. Must be paired with `apnsEnvironment`. type: string minLength: 8 maxLength: 256 pattern: ^[A-Fa-f0-9]+$ apnsEnvironment: description: Gateway the iOS client received `apnsToken` from. Server never auto-detects. type: string enum: - sandbox - production required: - token - bundleId description: Native device registration. Re-registering an APNs token belonging to another user returns 409 (cross-user-hijack guard). DisableCoachFlag: type: object properties: disableCoach: type: boolean required: - disableCoach description: Per-account Coach opt-out toggle (v1.4.47 W3). `true` hides the Coach FAB and short-circuits its API gates. CoachMessageFeedbackRequest: type: object properties: rating: type: string enum: - helpful - unhelpful reason: type: string minLength: 1 maxLength: 200 required: - rating description: Per-message helpful/unhelpful feedback (v1.4.23 H7). Optional `reason` is free-form prose, capped at 200 chars. LoginResponse: type: object properties: data: $ref: "#/components/schemas/AccessRefreshBundle" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false AccessRefreshBundle: type: object properties: user: type: object properties: id: type: string username: type: string required: - id - username additionalProperties: false token: type: string description: Access token (`hlk_<64hex>`). tokenExpiresAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ refreshToken: description: Refresh token (`hlr_<64hex>`); only present for native-policy callers. type: string refreshTokenExpiresAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ required: - token - tokenExpiresAt additionalProperties: false description: Native-client token bundle returned by login + refresh. Web cookie-only callers see only `user`. ErrorEnvelope: type: object properties: data: type: "null" error: type: string meta: type: object properties: requestId: type: string errorCode: type: string additionalProperties: false required: - data - error additionalProperties: false description: "Standard error response: data is null, error is human prose." PasskeyLoginVerifyResponse: type: object properties: data: $ref: "#/components/schemas/AccessRefreshBundle" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false RefreshResponse: type: object properties: data: $ref: "#/components/schemas/AccessRefreshBundle" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false ListMeasurementsResponse: type: object properties: data: type: object properties: measurements: type: array items: $ref: "#/components/schemas/MeasurementResource" total: type: integer minimum: 0 maximum: 9007199254740991 required: - measurements - total additionalProperties: false error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false MeasurementResource: type: object properties: id: type: string type: type: string enum: - WEIGHT - BLOOD_PRESSURE_SYS - BLOOD_PRESSURE_DIA - PULSE - BODY_FAT - SLEEP_DURATION - ACTIVITY_STEPS - BLOOD_GLUCOSE - TOTAL_BODY_WATER - BONE_MASS - OXYGEN_SATURATION - HEART_RATE_VARIABILITY - RESTING_HEART_RATE - ACTIVE_ENERGY_BURNED - FLIGHTS_CLIMBED - WALKING_RUNNING_DISTANCE - VO2_MAX - BODY_TEMPERATURE - FAT_FREE_MASS - FAT_MASS - MUSCLE_MASS - SKIN_TEMPERATURE - PULSE_WAVE_VELOCITY - VASCULAR_AGE - VISCERAL_FAT - AUDIO_EXPOSURE_ENV - AUDIO_EXPOSURE_HEADPHONE - TIME_IN_DAYLIGHT - WALKING_STEADINESS - AUDIO_EXPOSURE_EVENT value: type: number unit: type: string measuredAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ source: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH notes: anyOf: - type: string - type: "null" required: - id - type - value - unit - measuredAt - source additionalProperties: false description: Server-shaped measurement row returned by GET endpoints. CreateMeasurementResponse: type: object properties: data: $ref: "#/components/schemas/MeasurementResource" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false BatchMeasurementsResponse: type: object properties: data: $ref: "#/components/schemas/AppleHealthBatchResponse" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false AppleHealthBatchResponse: type: object properties: inserted: type: integer minimum: 0 maximum: 9007199254740991 duplicate: type: integer minimum: 0 maximum: 9007199254740991 skipped: type: integer minimum: 0 maximum: 9007199254740991 entries: type: array items: $ref: "#/components/schemas/AppleHealthBatchEntryResult" required: - inserted - duplicate - skipped - entries additionalProperties: false AppleHealthBatchEntryResult: type: object properties: index: type: integer minimum: 0 maximum: 9007199254740991 status: type: string enum: - inserted - duplicate - skipped reason: type: string required: - index - status additionalProperties: false MeasurementsDeleteByExternalIdsResponse: type: object properties: data: $ref: "#/components/schemas/MeasurementsDeleteByExternalIdsResult" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false MeasurementsDeleteByExternalIdsResult: type: object properties: deletedCount: type: integer minimum: 0 maximum: 9007199254740991 required: - deletedCount additionalProperties: false ListWorkoutsResponse: type: object properties: data: $ref: "#/components/schemas/WorkoutListResponse" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false WorkoutListResponse: type: object properties: workouts: type: array items: $ref: "#/components/schemas/WorkoutListEntry" meta: type: object properties: total: type: integer minimum: 0 maximum: 9007199254740991 limit: type: integer exclusiveMinimum: 0 maximum: 9007199254740991 offset: type: integer minimum: 0 maximum: 9007199254740991 droppedDuplicates: type: integer minimum: 0 maximum: 9007199254740991 required: - total - limit - offset - droppedDuplicates additionalProperties: false required: - workouts - meta additionalProperties: false WorkoutListEntry: type: object properties: id: type: string sportType: type: string startedAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ endedAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ durationSec: type: integer minimum: 0 maximum: 9007199254740991 distanceM: anyOf: - type: number - type: "null" activeEnergyKcal: anyOf: - type: number - type: "null" avgHr: anyOf: - type: integer minimum: -9007199254740991 maximum: 9007199254740991 - type: "null" maxHr: anyOf: - type: integer minimum: -9007199254740991 maximum: 9007199254740991 - type: "null" source: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH externalId: anyOf: - type: string - type: "null" required: - id - sportType - startedAt - endedAt - durationSec - distanceM - activeEnergyKcal - avgHr - maxHr - source - externalId additionalProperties: false GetWorkoutResponse: type: object properties: data: $ref: "#/components/schemas/WorkoutDetailResponse" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false WorkoutDetailResponse: type: object properties: id: type: string sportType: type: string startedAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ endedAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ durationSec: type: integer minimum: 0 maximum: 9007199254740991 distanceM: anyOf: - type: number - type: "null" activeEnergyKcal: anyOf: - type: number - type: "null" avgHr: anyOf: - type: integer minimum: -9007199254740991 maximum: 9007199254740991 - type: "null" maxHr: anyOf: - type: integer minimum: -9007199254740991 maximum: 9007199254740991 - type: "null" minHr: anyOf: - type: integer minimum: -9007199254740991 maximum: 9007199254740991 - type: "null" stepCount: anyOf: - type: integer minimum: -9007199254740991 maximum: 9007199254740991 - type: "null" elevationM: anyOf: - type: number - type: "null" pauseDurationSec: anyOf: - type: integer minimum: -9007199254740991 maximum: 9007199254740991 - type: "null" source: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH externalId: anyOf: - type: string - type: "null" metadata: anyOf: - {} - type: "null" route: anyOf: - $ref: "#/components/schemas/WorkoutRouteGeometry" - type: "null" canonicalId: type: string required: - id - sportType - startedAt - endedAt - durationSec - distanceM - activeEnergyKcal - avgHr - maxHr - minHr - stepCount - elevationM - pauseDurationSec - source - externalId - metadata - route - canonicalId additionalProperties: false WorkoutRouteGeometry: type: object properties: geometry: {} sampleTimestamps: anyOf: - type: array items: type: string - type: "null" required: - geometry - sampleTimestamps additionalProperties: false BatchWorkoutsResponse: type: object properties: data: $ref: "#/components/schemas/WorkoutBatchResponse" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false WorkoutBatchResponse: type: object properties: processed: type: integer minimum: 0 maximum: 9007199254740991 inserted: type: integer minimum: 0 maximum: 9007199254740991 duplicates: type: integer minimum: 0 maximum: 9007199254740991 skipped: type: array items: type: object properties: index: type: integer minimum: 0 maximum: 9007199254740991 reason: type: string required: - index - reason additionalProperties: false entries: type: array items: $ref: "#/components/schemas/WorkoutBatchEntryResult" required: - processed - inserted - duplicates - skipped - entries additionalProperties: false WorkoutBatchEntryResult: type: object properties: index: type: integer minimum: 0 maximum: 9007199254740991 status: type: string enum: - inserted - duplicate - skipped reason: type: string required: - index - status additionalProperties: false DeviceRegisterResponse: type: object properties: data: type: object properties: id: type: string required: - id additionalProperties: false error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false GetCoachPrefsResponse: type: object properties: data: type: object properties: tone: default: warm type: string enum: - warm - neutral - concise verbosity: default: default type: string enum: - brief - default - detailed excludeMetrics: default: [] maxItems: 11 type: array items: type: string enum: - bp - weight - pulse - mood - compliance - hrv - sleep - resting_hr - steps - medications - anthropometrics showEvidenceByDefault: default: false type: boolean defaultWindow: default: allTime type: string enum: - last7days - last30days - last90days - allTime required: - tone - verbosity - excludeMetrics - showEvidenceByDefault - defaultWindow additionalProperties: false error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false PutCoachPrefsResponse: type: object properties: data: type: object properties: tone: default: warm type: string enum: - warm - neutral - concise verbosity: default: default type: string enum: - brief - default - detailed excludeMetrics: default: [] maxItems: 11 type: array items: type: string enum: - bp - weight - pulse - mood - compliance - hrv - sleep - resting_hr - steps - medications - anthropometrics showEvidenceByDefault: default: false type: boolean defaultWindow: default: allTime type: string enum: - last7days - last30days - last90days - allTime required: - tone - verbosity - excludeMetrics - showEvidenceByDefault - defaultWindow additionalProperties: false error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false GetDisableCoachResponse: type: object properties: data: $ref: "#/components/schemas/DisableCoachFlagOutput" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false PatchDisableCoachResponse: type: object properties: data: $ref: "#/components/schemas/DisableCoachFlagOutput" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false GetSourcePriorityResponse: type: object properties: data: type: object properties: steps: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH activeEnergy: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH walkingRunningDistance: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH flightsClimbed: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH sleep: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH weight: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bloodPressure: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH pulse: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyFat: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyTemperature: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH spo2: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH hrv: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH restingHeartRate: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH vo2Max: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH metricPriority: type: object properties: steps: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH activeEnergy: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH walkingRunningDistance: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH flightsClimbed: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH sleep: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH weight: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bloodPressure: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH pulse: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyFat: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyTemperature: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH spo2: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH hrv: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH restingHeartRate: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH vo2Max: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH additionalProperties: false deviceTypePriority: type: object properties: default: maxItems: 8 type: array items: type: string enum: - watch - band - ring - phone - scale - other - unknown additionalProperties: maxItems: 8 type: array items: type: string enum: - watch - band - ring - phone - scale - other - unknown additionalProperties: false error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false PutSourcePriorityResponse: type: object properties: data: type: object properties: steps: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH activeEnergy: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH walkingRunningDistance: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH flightsClimbed: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH sleep: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH weight: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bloodPressure: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH pulse: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyFat: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyTemperature: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH spo2: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH hrv: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH restingHeartRate: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH vo2Max: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH metricPriority: type: object properties: steps: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH activeEnergy: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH walkingRunningDistance: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH flightsClimbed: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH sleep: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH weight: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bloodPressure: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH pulse: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyFat: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH bodyTemperature: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH spo2: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH hrv: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH restingHeartRate: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH vo2Max: maxItems: 8 type: array items: type: string enum: - MANUAL - WITHINGS - IMPORT - APPLE_HEALTH additionalProperties: false deviceTypePriority: type: object properties: default: maxItems: 8 type: array items: type: string enum: - watch - band - ring - phone - scale - other - unknown additionalProperties: maxItems: 8 type: array items: type: string enum: - watch - band - ring - phone - scale - other - unknown additionalProperties: false error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false CoachMessageFeedbackResponse: type: object properties: data: type: object properties: id: type: string createdAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ required: - id - createdAt additionalProperties: false error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false AdminDiagnosticResponse: type: object properties: data: $ref: "#/components/schemas/AdminDiagnosticData" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false AdminDiagnosticData: type: object properties: devices: type: array items: $ref: "#/components/schemas/AdminDiagnosticDevice" notificationChannels: type: array items: $ref: "#/components/schemas/AdminDiagnosticChannel" recentPushAttempts: type: array items: $ref: "#/components/schemas/AdminDiagnosticPushAttempt" required: - devices - notificationChannels - recentPushAttempts additionalProperties: false description: Admin notification diagnostic snapshot for the calling user — what the dispatcher would see when targeting this account. Surfaces device tokens (masked), channel state, and recent push attempts so an operator can debug an iOS / Web Push / Telegram / ntfy issue without DB shell access. AdminDiagnosticDevice: type: object properties: id: type: string platform: type: string hasApnsToken: type: boolean apnsTokenPrefix: anyOf: - type: string - type: "null" apnsTokenSuffix: anyOf: - type: string - type: "null" apnsEnvironment: anyOf: - type: string - type: "null" lastSeenAt: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ required: - id - platform - hasApnsToken - apnsTokenPrefix - apnsTokenSuffix - apnsEnvironment - lastSeenAt additionalProperties: false description: Per-device APNs registration snapshot. The APNs token is masked to its 8-char hex prefix + 8-char suffix so an operator can correlate with iOS-side logs without disclosing the delivery target. AdminDiagnosticChannel: type: object properties: type: type: string enabled: type: boolean configPresent: type: boolean required: - type - enabled - configPresent additionalProperties: false description: Per-channel state. `configPresent` is true when the channel's encrypted-JSON config blob carries the field the corresponding sender will read (chatId+botToken for TELEGRAM, topic for NTFY); WEB_PUSH/APNS rely on sibling tables so the row's existence is the signal. AdminDiagnosticPushAttempt: type: object properties: eventType: type: string channel: type: string result: type: string reason: anyOf: - type: string - type: "null" at: type: string format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ required: - eventType - channel - result - reason - at additionalProperties: false description: Single push delivery attempt. Reserved for the v1.4.48+ follow-up that adds the dedicated PushAttempt table — currently always returned as an empty array so the response shape stays stable for iOS consumers. InsightsComprehensiveResponseEnvelope: type: object properties: data: $ref: "#/components/schemas/InsightsComprehensiveResponse" error: type: "null" meta: type: object properties: requestId: type: string additionalProperties: false required: - data - error additionalProperties: false InsightsComprehensiveResponse: type: object properties: summary: type: string recommendations: type: array items: type: object propertyNames: type: string additionalProperties: {} citations: type: array items: type: object propertyNames: type: string additionalProperties: {} warnings: type: array items: type: object propertyNames: type: string additionalProperties: {} dailyBriefing: anyOf: - type: object propertyNames: type: string additionalProperties: {} - type: "null" trendAnnotations: anyOf: - type: object propertyNames: type: string additionalProperties: {} - type: "null" storyboardAnnotations: type: array items: type: object propertyNames: type: string additionalProperties: {} metricSource: type: object propertyNames: type: string additionalProperties: {} required: - summary - recommendations - citations - warnings additionalProperties: false description: AI-generated insights bundle. Strict-schema validated server-side; Coach-routed when the insight surface needs day-level grounding. DisableCoachFlagOutput: type: object properties: disableCoach: type: boolean required: - disableCoach additionalProperties: false description: Per-account Coach opt-out toggle (v1.4.47 W3). `true` hides the Coach FAB and short-circuits its API gates. securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: hlk_<64hex> description: Native-client API token. Use the `token` field returned by /api/auth/login on a native client. cookieAuth: type: apiKey in: cookie name: healthlog_session description: Browser session cookie — set by /api/auth/login.