mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Register RECOVERY_SCORE / STRESS_SCORE / STRAIN_SCORE as derived metrics that read the nightly-persisted COMPUTED Measurement rows — a passthrough, never a recompute. The dispatcher routes the three ids to a thin reader that returns the latest score, a trailing-window trend, and a direction-aware band (stress and strain invert, so a high stress reads red). This lets every surface — the dashboard, the Coach snapshot, the doctor report — consume the scores through the one Derived<T> route + hook rather than each growing its own read path. Regenerate the OpenAPI enum, which picks up the three ids from the registry.
8149 lines
283 KiB
YAML
8149 lines
283 KiB
YAML
openapi: 3.1.0
|
||
info:
|
||
title: HealthLog API
|
||
version: 1.9.2
|
||
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: Analytics
|
||
- name: Insights
|
||
- name: Dashboard
|
||
- name: Notifications
|
||
- name: Devices
|
||
- name: Export
|
||
- name: Sync
|
||
- name: Admin
|
||
paths:
|
||
/api/export/health-record:
|
||
post:
|
||
tags:
|
||
- Export
|
||
summary: Generate a health-record export (PDF / FHIR / package)
|
||
description: "v1.7.0 flagship export. Returns the doctor-handover artefact in the requested `format`: `pdf` →
|
||
application/pdf, `fhir` → application/fhir+json (HL7 FHIR R4 document Bundle), `package` → application/zip (PDF
|
||
+ FHIR + README). Auth via cookie or Bearer; shared `export:<userId>` rate bucket (10/h). Strict validation:
|
||
unknown keys 422."
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
format:
|
||
type: string
|
||
enum:
|
||
- pdf
|
||
- fhir
|
||
- package
|
||
range:
|
||
type: object
|
||
properties:
|
||
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)))$
|
||
days:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 365
|
||
additionalProperties: false
|
||
sections:
|
||
type: object
|
||
properties:
|
||
vitals:
|
||
type: object
|
||
properties:
|
||
weight:
|
||
type: boolean
|
||
bp:
|
||
type: boolean
|
||
pulse:
|
||
type: boolean
|
||
oxygenSaturation:
|
||
type: boolean
|
||
bodyFat:
|
||
type: boolean
|
||
bodyComposition:
|
||
type: boolean
|
||
cardioFitness:
|
||
type: object
|
||
properties:
|
||
restingHeartRate:
|
||
type: boolean
|
||
hrv:
|
||
type: boolean
|
||
vo2max:
|
||
type: boolean
|
||
activity:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
type: boolean
|
||
distance:
|
||
type: boolean
|
||
energy:
|
||
type: boolean
|
||
sleep:
|
||
type: boolean
|
||
glucose:
|
||
type: boolean
|
||
medications:
|
||
type: object
|
||
properties:
|
||
list:
|
||
type: boolean
|
||
compliance:
|
||
type: boolean
|
||
glp1:
|
||
type: boolean
|
||
sideEffects:
|
||
type: boolean
|
||
mood:
|
||
type: boolean
|
||
bmi:
|
||
type: boolean
|
||
locale:
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
- fr
|
||
- es
|
||
- it
|
||
- pl
|
||
practiceName:
|
||
type: string
|
||
maxLength: 120
|
||
includeCharts:
|
||
type: boolean
|
||
includeAiSummary:
|
||
type: boolean
|
||
germanAtc:
|
||
type: boolean
|
||
required:
|
||
- format
|
||
additionalProperties: false
|
||
responses:
|
||
"200":
|
||
description: "Export generated. Content-Type varies by `format`: application/pdf, application/fhir+json, or
|
||
application/zip."
|
||
content:
|
||
application/pdf:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
application/fhir+json:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
application/zip:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
"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/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
|
||
"422": *a2
|
||
"429": *a3
|
||
/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.
|
||
|
||
|
||
On a 401, `meta.errorCode` is a stable machine code so the client can branch terminal re-auth from a transient
|
||
blip without parsing the prose `error`: `auth.refresh.reuse` (a consumed token was replayed — device family
|
||
revoked, re-pair required), `auth.refresh.revoked` (family revoked out-of-band — re-pair required),
|
||
`auth.refresh.invalid` (not found / expired — drop the token and re-authenticate).
|
||
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/sync/state:
|
||
get:
|
||
tags:
|
||
- Sync
|
||
summary: Sync handshake + window metadata
|
||
description: Cheap 'should I sync?' summary. Returns the previous `lastSyncedAt` checkpoint and advances it server-side
|
||
on each call. The `sync` block carries the incremental-delta window + tombstone retention so the client reads
|
||
them rather than hardcoding.
|
||
responses:
|
||
"200":
|
||
description: Sync state summary.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/SyncStateEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/sync/changes:
|
||
get:
|
||
tags:
|
||
- Sync
|
||
summary: Measurements delta feed
|
||
description: "Incremental catch-up after the first-pair backfill (never a replacement for it). Pages over an opaque
|
||
keyset cursor; each page carries `tombstones` (soft-deleted rows, keyed on `externalId`) and `upserts` (live
|
||
rows). Apply tombstones before upserts within a page. `cursorExpired: true` forces a clean re-init."
|
||
parameters:
|
||
- in: query
|
||
name: cursor
|
||
schema:
|
||
description: Opaque multi-domain keyset cursor from the previous page. Treat as fully opaque — echo, never parse. Omit
|
||
for the initial sync.
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 2048
|
||
description: Opaque multi-domain keyset cursor from the previous page. Treat as fully opaque — echo, never parse. Omit
|
||
for the initial sync.
|
||
- in: query
|
||
name: limit
|
||
schema:
|
||
description: Page size, default 200, hard cap 500.
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 500
|
||
description: Page size, default 200, hard cap 500.
|
||
responses:
|
||
"200":
|
||
description: Delta page.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/SyncChangesEnvelope"
|
||
"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
|
||
- RESPIRATORY_RATE
|
||
- BODY_MASS_INDEX
|
||
- LEAN_BODY_MASS
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
- IRREGULAR_RHYTHM_NOTIFICATION
|
||
- HIGH_HEART_RATE_EVENT
|
||
- LOW_HEART_RATE_EVENT
|
||
- WALKING_STEADINESS_EVENT
|
||
- BREATHING_DISTURBANCE_EVENT
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
- 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
|
||
- RESPIRATORY_RATE
|
||
- BODY_MASS_INDEX
|
||
- LEAN_BODY_MASS
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
- IRREGULAR_RHYTHM_NOTIFICATION
|
||
- HIGH_HEART_RATE_EVENT
|
||
- LOW_HEART_RATE_EVENT
|
||
- WALKING_STEADINESS_EVENT
|
||
- BREATHING_DISTURBANCE_EVENT
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
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: 200
|
||
source:
|
||
default: MANUAL
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
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
|
||
- COMPUTED
|
||
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
|
||
samples:
|
||
minItems: 1
|
||
maxItems: 30000
|
||
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)))$
|
||
hr:
|
||
type: integer
|
||
minimum: 20
|
||
maximum: 300
|
||
speedMs:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 50
|
||
power:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 3000
|
||
cadence:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 400
|
||
required:
|
||
- t
|
||
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/medications:
|
||
get:
|
||
tags:
|
||
- Medications
|
||
summary: List medications for the calling user
|
||
description: Returns every medication owned by the caller (active + paused), ordered by `createdAt DESC`. Each row
|
||
carries its nested `schedules`, the joined clinical `category`, the latest non-skipped `lastTakenAt`, and the
|
||
count of intake events scheduled for today (`todayEventCount`). The response is cached server-side for 60 s per
|
||
user; writes flush the cache.
|
||
responses:
|
||
"200":
|
||
description: Medication list.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ListMedicationsResponse"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
post:
|
||
tags:
|
||
- Medications
|
||
summary: Create a medication with at least one schedule
|
||
description: Validates the body against `CreateMedicationRequest`, applies the v1.5 cross-field invariants (one-shot
|
||
consistency, recurring default `FREQ=DAILY`, `timesOfDay` dual-write), and creates the medication + its
|
||
schedules in a single Prisma write. Audits as `medication.create`.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CreateMedicationRequest"
|
||
responses:
|
||
"201":
|
||
description: Created medication with its schedules.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CreateMedicationResponse"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/medications/{id}:
|
||
get:
|
||
tags:
|
||
- Medications
|
||
summary: Fetch a single medication
|
||
description: Returns the medication + its schedules + the joined `category`. Cross-user rows surface as 404 (existence
|
||
channel sealed).
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"200":
|
||
description: Medication detail.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/GetMedicationResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a2
|
||
"429": *a3
|
||
put:
|
||
tags:
|
||
- Medications
|
||
summary: Replace a medication (partial fields)
|
||
description: Every field on the body is optional; omitted fields are left untouched. Supplying `schedules` REPLACES the
|
||
medication's full schedule list (the route deletes existing rows before re-creating). Flipping `active` to false
|
||
stamps `pausedAt`; flipping back to true clears it. v1.5 invariants on the `schedules` array match `POST
|
||
/api/medications`. Audits as `medication.update`.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/UpdateMedicationRequest"
|
||
responses:
|
||
"200":
|
||
description: Updated medication.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/UpdateMedicationResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a2
|
||
"429": *a3
|
||
delete:
|
||
tags:
|
||
- Medications
|
||
summary: Delete a medication
|
||
description: Cascades to the medication's schedules, intake events, dose changes, inventory rows, and side-effect logs.
|
||
Revokes every API token scoped to `medication:<id>:ingest`. Audits as `medication.delete`.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"200":
|
||
description: Deletion succeeded.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/DeleteMedicationResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/medications/{id}/intake:
|
||
post:
|
||
tags:
|
||
- Medications
|
||
summary: Log an intake event for a medication
|
||
description: Records a taken or skipped dose. Idempotent via the `Idempotency-Key` header AND the optional
|
||
`idempotencyKey` body field (the route walks both paths); a re-post inside the 60 s server-side dedup window
|
||
returns the original event. Non-skipped intakes auto-decrement pen inventory (best-effort), refresh the per-day
|
||
compliance rollup, and — for `oneShot:true` medications — flip `active` to false.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MedicationIntakeRequest"
|
||
responses:
|
||
"200":
|
||
description: Idempotent replay — the original event is returned without creating a new row.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ReplayMedicationIntakeResponse"
|
||
"201":
|
||
description: Intake event created.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CreateMedicationIntakeResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/medications/extract:
|
||
post:
|
||
tags:
|
||
- Medications
|
||
summary: Extract scheduling fields from a free-text medication description
|
||
description: Runs the user's free-text description through the Coach provider chain and returns a citation-guarded
|
||
partial payload the wizard merges onto whatever the user already typed. `name` and `dose` are dropped when not
|
||
substring-matched in the original text so the wizard cannot land a hallucinated brand or dose. `cadenceKind` /
|
||
`doseUnit` / `weekdays` are closed enums; numeric fields are clamped. Rate-limited 10 requests / 5 minutes /
|
||
user, gated against the daily Coach token budget.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MedicationExtractRequest"
|
||
responses:
|
||
"200":
|
||
description: Citation-guarded partial extraction.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MedicationExtractResponse"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
"502":
|
||
description: Upstream provider returned an empty, unparseable, or off-schema reply.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"503":
|
||
description: No AI provider configured for the calling user (or operator).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
/api/medications/{id}/cadence:
|
||
get:
|
||
tags:
|
||
- Medications
|
||
summary: Cadence + compliance read for a medication
|
||
description: Returns the expected-vs-actual dose timeline for the requested window plus the four compliance chip values
|
||
that drive the detail-page section. Pure computation — no writes. Day boundaries are resolved in the user's IANA
|
||
timezone so a Tokyo user and a Berlin user see the same chips for the same medication. The `days` query
|
||
parameter caps at 180.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
- in: query
|
||
name: days
|
||
schema:
|
||
description: Window size in days (default 30, max 180).
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 180
|
||
description: Window size in days (default 30, max 180).
|
||
responses:
|
||
"200":
|
||
description: Cadence response.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/GetMedicationCadenceResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/medications/{id}/compliance:
|
||
get:
|
||
tags:
|
||
- Medications
|
||
summary: Adherence read for a medication
|
||
description: Returns the 7- and 30-day adherence summaries, the per-day compliance grid for the history glyph track, and
|
||
the two-row display block. Pure computation — no writes. Day boundaries are resolved in the user's IANA
|
||
timezone, and the expected-dose denominator is cadence-aware (RRULE / rolling / one-shot / PRN / cyclic) and
|
||
clamped to the medication's `createdAt`. Read `compliance30` for the headline 30-day taken-vs-expected
|
||
percentage; build the per-day glyph track from `dailyCompliance` (draw a cell only where `due === true`).
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"200":
|
||
description: Compliance response.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/GetMedicationComplianceResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication not found (or owned by 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
|
||
dataClusters:
|
||
maxItems: 10
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- cardio
|
||
- body
|
||
- activity
|
||
- workouts
|
||
- sleep
|
||
- mood
|
||
- glucose
|
||
- medication
|
||
- mobility
|
||
- environment
|
||
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
|
||
- COMPUTED
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
metricPriority:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
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/user/avatar:
|
||
post:
|
||
tags:
|
||
- Auth
|
||
summary: Upload the calling user's avatar
|
||
description: "Multipart upload (field `file`). Accepts image/jpeg, image/png, image/webp; rejects anything else at the
|
||
magic-byte sniff (the multipart Content-Type header is informational only). Hard caps: 2 MiB body, 2048×2048
|
||
dimensions. Replaces the v1.4.22 Gravatar leak by storing the bytes on the User row + serving them from
|
||
same-origin."
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
multipart/form-data:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
file:
|
||
type: string
|
||
description: Binary image payload (JPEG / PNG / WebP). Hard-capped at 2 MiB and 2048×2048.
|
||
required:
|
||
- file
|
||
responses:
|
||
"201":
|
||
description: Avatar saved. `avatarUrl` is the same value the /me payload returns; the `?v=` suffix busts client caches
|
||
on re-upload.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/UploadAvatarResponse"
|
||
"401": *a1
|
||
"413":
|
||
description: Upload exceeds the 2 MiB byte limit or the 2048×2048 dimension limit.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"415":
|
||
description: Image format is not one of JPEG / PNG / WebP (magic-byte sniff).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a2
|
||
"429": *a3
|
||
delete:
|
||
tags:
|
||
- Auth
|
||
summary: Clear the calling user's avatar
|
||
description: Resets the avatar columns to null. Idempotent — a delete on an already-empty row returns 204 with no
|
||
audit-log row.
|
||
responses:
|
||
"204":
|
||
description: Avatar cleared (or already empty).
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/user/avatar/{id}:
|
||
get:
|
||
tags:
|
||
- Auth
|
||
summary: Serve a user's avatar bytes
|
||
description: Owner-scoped. The `{id}` path segment must match the calling user's id — cross-user reads return 403.
|
||
Response body is the raw image bytes with the persisted `Content-Type`; the /me payload appends
|
||
`?v={updatedAtMs}` so clients can cache aggressively.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
description: User id (must match the calling user).
|
||
required: true
|
||
description: User id (must match the calling user).
|
||
responses:
|
||
"200":
|
||
description: Avatar bytes.
|
||
content:
|
||
image/jpeg:
|
||
schema:
|
||
type: string
|
||
description: Raw JPEG bytes.
|
||
image/png:
|
||
schema:
|
||
type: string
|
||
description: Raw PNG bytes.
|
||
image/webp:
|
||
schema:
|
||
type: string
|
||
description: Raw WebP bytes.
|
||
"401": *a1
|
||
"403":
|
||
description: Caller is not the avatar's owner.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"404":
|
||
description: The user has no uploaded avatar.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/user/profile:
|
||
get:
|
||
tags:
|
||
- Auth
|
||
summary: Read the calling user's profile
|
||
description: Flattened profile fields for the native client. Aliased over the same data exposed by /api/auth/me. The
|
||
KVNR is decrypted server-side; the IKNR (v1.8.6) is returned plaintext.
|
||
responses:
|
||
"200":
|
||
description: The calling user's profile.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/GetProfileResponse"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
patch:
|
||
tags:
|
||
- Auth
|
||
summary: Update the calling user's profile
|
||
description: Partial profile update — every field is optional. An explicit null (or empty string) clears a field. A
|
||
non-empty `insurerIkNumber` must be exactly 9 digits (422 otherwise); a non-empty `insuranceNumber` (KVNR) must
|
||
pass the mod-10 check. `userId` is never accepted from the body.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ProfileUpdateRequest"
|
||
responses:
|
||
"200":
|
||
description: Saved profile echoed back.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/PatchProfileResponse"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/insights/layout:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Read the calling user's Insights tile layout
|
||
description: Returns the per-user Insights tile layout (visibility + order). Falls back to the default layout when the
|
||
user has not customised it. Mirrors the dashboard-widgets contract.
|
||
responses:
|
||
"200":
|
||
description: The resolved layout (custom or default).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsLayout"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
put:
|
||
tags:
|
||
- Insights
|
||
summary: Replace the calling user's Insights tile layout
|
||
description: Persists the full tile layout. The normalised layout is returned. Invalid bodies return the multi-issue 422
|
||
envelope, matching the dashboard-widgets route.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsLayoutBody"
|
||
responses:
|
||
"200":
|
||
description: Layout saved; the normalised layout is echoed back.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsLayoutSaved"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
delete:
|
||
tags:
|
||
- Insights
|
||
summary: Reset the calling user's Insights tile layout
|
||
description: Clears the persisted layout and returns the default layout. Idempotent.
|
||
responses:
|
||
"200":
|
||
description: Layout reset; the default layout is returned.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsLayoutReset"
|
||
"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/dashboard/snapshot:
|
||
get:
|
||
tags:
|
||
- Dashboard
|
||
summary: Unified dashboard first-paint snapshot
|
||
description: "Assembles every above-the-fold tile field in one round-trip from the rollup / mood / widget helpers plus a
|
||
read-only lift of the pre-generated daily briefing. Two-phase: `tiles` always present, `extras` nullable on a
|
||
rollup-coverage miss. No LLM is reachable from this path. Cookie or Bearer auth."
|
||
responses:
|
||
"200":
|
||
description: Dashboard snapshot.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/DashboardSnapshotResponseEnvelope"
|
||
"401": *a1
|
||
"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
|
||
/api/insights/pregenerate:
|
||
post:
|
||
tags:
|
||
- Insights
|
||
summary: Warm all AI assessments for the calling user
|
||
description: v1.8.7.1 — enqueue a full warm of every AI assessment for the authenticated user (comprehensive insight +
|
||
the seven specialised status cards + every data-bearing generic metric assessment) in the active locale, so the
|
||
read-only status GETs serve cached text instantly. Returns immediately; the generation runs out of band on the
|
||
worker. Empty metrics and provider-less accounts never trigger an LLM call. Short anti-spam bucket
|
||
(`insights-warm:<userId>`, one warm per 3 minutes) → 429 on a tight loop. Auth via cookie or Bearer; `userId` is
|
||
taken from the session, never the body.
|
||
requestBody:
|
||
required: false
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsPregenerateRequest"
|
||
responses:
|
||
"200":
|
||
description: Warm accepted and enqueued. The work runs on the worker; poll the read-only status routes for the text.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsPregenerateResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/analytics/range:
|
||
get:
|
||
tags:
|
||
- Analytics
|
||
summary: Single-metric period-over-period range delta
|
||
description: v1.9.0 — returns the current-window aggregate, the previous comparable window, and the composed delta for
|
||
ONE metric type over a `7d` / `30d` / `90d` / `1y` range. Single-type by construction (the metric page is
|
||
single-metric), so the read is one rollup-tier call covering the trailing 2N days sliced into the two halves —
|
||
no per-type fan-out. Additive route; the `/api/analytics` envelope is unchanged. Auth via cookie or Bearer.
|
||
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
|
||
- RESPIRATORY_RATE
|
||
- BODY_MASS_INDEX
|
||
- LEAN_BODY_MASS
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
- IRREGULAR_RHYTHM_NOTIFICATION
|
||
- HIGH_HEART_RATE_EVENT
|
||
- LOW_HEART_RATE_EVENT
|
||
- WALKING_STEADINESS_EVENT
|
||
- BREATHING_DISTURBANCE_EVENT
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
description: "The measurement type to read (single metric — no fan-out). Closed enum: an unknown type 422s."
|
||
required: true
|
||
description: "The measurement type to read (single metric — no fan-out). Closed enum: an unknown type 422s."
|
||
- in: query
|
||
name: range
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- 7d
|
||
- 30d
|
||
- 90d
|
||
- 1y
|
||
description: "Trailing window: `7d` / `30d` / `90d` / `1y`. The previous comparable window is the equally-sized span
|
||
immediately before it."
|
||
required: true
|
||
description: "Trailing window: `7d` / `30d` / `90d` / `1y`. The previous comparable window is the equally-sized span
|
||
immediately before it."
|
||
responses:
|
||
"200":
|
||
description: Current + previous window aggregates and the delta.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/AnalyticsRangeResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/insights/blood-pressure-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Blood-pressure assessment
|
||
description: "Data-driven plain-language assessment of the user's recent blood-pressure readings. Read-only: a cache
|
||
miss warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via
|
||
cookie or Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/BloodPressureStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/insights/pulse-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Pulse assessment
|
||
description: "Data-driven plain-language assessment of the user's recent resting-pulse readings. Read-only: a cache miss
|
||
warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie
|
||
or Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/PulseStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/insights/weight-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Weight assessment
|
||
description: "Data-driven plain-language assessment of the user's recent weight trend. Read-only: a cache miss warms a
|
||
generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or
|
||
Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/WeightStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/insights/bmi-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: BMI assessment
|
||
description: "Data-driven plain-language assessment of the user's body-mass index. Read-only: a cache miss warms a
|
||
generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or
|
||
Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/BmiStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/insights/mood-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Mood assessment
|
||
description: "Data-driven plain-language assessment of the user's recent mood entries. Read-only: a cache miss warms a
|
||
generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or
|
||
Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MoodStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/insights/medication-compliance-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Medication-compliance assessment
|
||
description: "Data-driven plain-language assessment of the user's medication compliance — an overall `summary` plus a
|
||
per-medication note array. Read-only: a cache miss warms a generation out of band and serves the last-good
|
||
envelope meanwhile (stale-while-revalidate). Auth via cookie or Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Compliance assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MedicationComplianceStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/insights/metric-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Generic per-HealthKit-metric assessment
|
||
description: "v1.8.7.1 — data-driven plain-language assessment for any registered HealthKit metric (resting heart rate,
|
||
sleep, glucose, body composition, gait, audio exposure, …). One generic route covering ~30 metric pages via
|
||
archetype prompt templates + per-metric metadata. Read-only: a cache miss warms a generation out of band and
|
||
serves the last-good text meanwhile (stale-while-revalidate). An unknown `metric` 422s against the closed
|
||
registry enum. Auth via cookie or Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: metric
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- RESTING_HEART_RATE
|
||
- HEART_RATE_VARIABILITY
|
||
- OXYGEN_SATURATION
|
||
- RESPIRATORY_RATE
|
||
- BODY_TEMPERATURE
|
||
- SKIN_TEMPERATURE
|
||
- BLOOD_GLUCOSE
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- PULSE_WAVE_VELOCITY
|
||
- VASCULAR_AGE
|
||
- STEPS
|
||
- ACTIVE_ENERGY
|
||
- FLIGHTS_CLIMBED
|
||
- WALKING_RUNNING_DISTANCE
|
||
- TIME_IN_DAYLIGHT
|
||
- VO2_MAX
|
||
- TOTAL_BODY_WATER
|
||
- BONE_MASS
|
||
- FAT_FREE_MASS
|
||
- FAT_MASS
|
||
- MUSCLE_MASS
|
||
- LEAN_BODY_MASS
|
||
- VISCERAL_FAT
|
||
- WALKING_STEADINESS
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- AUDIO_EXPOSURE_ENV
|
||
- AUDIO_EXPOSURE_HEADPHONE
|
||
- AUDIO_EXPOSURE_EVENT
|
||
- SLEEP_DURATION
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
description: "HealthKit metric id to assess (e.g. RESTING_HEART_RATE, SLEEP_DURATION). Closed enum: an unknown id 422s.
|
||
The seven specialised metrics are served by their own routes and are not accepted here."
|
||
required: true
|
||
description: "HealthKit metric id to assess (e.g. RESTING_HEART_RATE, SLEEP_DURATION). Closed enum: an unknown id 422s.
|
||
The seven specialised metrics are served by their own routes and are not accepted here."
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MetricStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/insights/derived:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Derived wellness metric (compute-once)
|
||
description: v1.10.0 — the compute-once `Derived<T>` value for any registered derived wellness metric (personal
|
||
typical-range vitals baseline, cardio-fitness band, vascular-age delta, sleep score, readiness,
|
||
coincident-deviation flag). One generic route over a closed registry enum; an unknown `metric` 422s. Pure
|
||
compute over the rollup tier with a per-type live fallback on a coverage miss — no LLM call, no narrative, no
|
||
cache table. Returns the flat `Derived<T>` union so the native client can decode one stable shape and combine
|
||
values across metrics. Auth via cookie or Bearer.
|
||
parameters:
|
||
- in: query
|
||
name: metric
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- VITALS_BASELINE
|
||
- FITNESS_AGE
|
||
- VASCULAR_AGE_DELTA
|
||
- SLEEP_SCORE
|
||
- READINESS
|
||
- COINCIDENT_DEVIATION
|
||
- HRV_BALANCE
|
||
- BMI
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
description: "Derived-metric id to compute (e.g. VITALS_BASELINE, FITNESS_AGE, VASCULAR_AGE_DELTA, HRV_BALANCE, BMI,
|
||
READINESS). Closed enum: an unknown id 422s. Metrics whose compute has not yet landed return an
|
||
`insufficient` value with reason `not_implemented`."
|
||
required: true
|
||
description: "Derived-metric id to compute (e.g. VITALS_BASELINE, FITNESS_AGE, VASCULAR_AGE_DELTA, HRV_BALANCE, BMI,
|
||
READINESS). Closed enum: an unknown id 422s. Metrics whose compute has not yet landed return an
|
||
`insufficient` value with reason `not_implemented`."
|
||
- in: query
|
||
name: type
|
||
schema:
|
||
description: For VITALS_BASELINE only — the single vital to band (defaults to RESTING_HEART_RATE). Ignored by
|
||
composites. An unsupported value yields an `insufficient` value rather than a 422 so iOS metric
|
||
combinations stay forgiving.
|
||
type: string
|
||
enum:
|
||
- RESTING_HEART_RATE
|
||
- HEART_RATE_VARIABILITY
|
||
- RESPIRATORY_RATE
|
||
- OXYGEN_SATURATION
|
||
- BODY_TEMPERATURE
|
||
- SKIN_TEMPERATURE
|
||
- BLOOD_PRESSURE_SYS
|
||
- BLOOD_PRESSURE_DIA
|
||
- BLOOD_GLUCOSE
|
||
- PULSE
|
||
- WEIGHT
|
||
description: For VITALS_BASELINE only — the single vital to band (defaults to RESTING_HEART_RATE). Ignored by
|
||
composites. An unsupported value yields an `insufficient` value rather than a 422 so iOS metric combinations
|
||
stay forgiving.
|
||
responses:
|
||
"200":
|
||
description: The flat derived-metric value (ok or insufficient).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/DerivedMetricResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/insights/correlations:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Correlation discovery (FDR-controlled)
|
||
description: v1.10.0 — scans a curated behaviour × outcome matrix (daylight / mood / glucose / BP / steps × sleep / HRV
|
||
/ resting HR / weight), lag-joins each behaviour day to the next day's outcome, runs Pearson with the exact
|
||
Student-t p-value, and applies Benjamini-Hochberg FDR control across every tested pair. Only
|
||
statistically-defensible pairs surface, each carrying n, r, p, and the BH-adjusted q. Descriptive, never causal.
|
||
Gated by the operator `correlations` assistant surface. Auth via cookie or Bearer.
|
||
responses:
|
||
"200":
|
||
description: The discovered correlations + the tested-pair count.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CorrelationDiscoveryResponseEnvelope"
|
||
"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
|
||
categoryValue:
|
||
description: v1.10.0 — `HKCategoryValue` codepoint for an EVENT-class category sample (irregular-rhythm, high/low-HR,
|
||
walking-steadiness, breathing-disturbance). Carries the device's own classification verdict / severity,
|
||
which the server resolves to a stored `rhythmClassification`. HealthLog stores ONLY the device's result — it
|
||
never re-classifies. Ignored for non-event identifiers.
|
||
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
|
||
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
|
||
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
|
||
medicationDelivery:
|
||
description: v1.7.0 per-device medication-delivery override. NULL / omitted = inherit the user-level roaming default.
|
||
"server" forces server APNs for this device; "client" forces local. Stored + echoed; cron suppression stays
|
||
user-level.
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- server
|
||
- client
|
||
- type: "null"
|
||
required:
|
||
- token
|
||
- bundleId
|
||
description: Native device registration. Re-registering an APNs token belonging to another user returns 409
|
||
(cross-user-hijack guard).
|
||
CreateMedicationRequest:
|
||
type: object
|
||
properties:
|
||
name:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 100
|
||
dose:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 50
|
||
category:
|
||
type: string
|
||
enum:
|
||
- BLOOD_PRESSURE
|
||
- VITAMIN
|
||
- SUPPLEMENT
|
||
- PAIN_RELIEF
|
||
- ALLERGY
|
||
- DIGESTIVE
|
||
- THYROID
|
||
- HORMONE
|
||
- SKIN
|
||
- SLEEP_AID
|
||
- DIABETES
|
||
- ANTIBIOTIC
|
||
- OTHER
|
||
treatmentClass:
|
||
type: string
|
||
enum:
|
||
- GENERIC
|
||
- GLP1
|
||
dosesPerUnit:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 100
|
||
deliveryForm:
|
||
type: string
|
||
enum:
|
||
- ORAL
|
||
- INJECTION
|
||
- OTHER
|
||
trackInjectionSites:
|
||
description: Per-medication injection-site tracking opt-in. Default false. Only meaningful for an INJECTION delivery
|
||
form; when true the client prompts (skippably) for the site after a taken dose.
|
||
type: boolean
|
||
allowedInjectionSites:
|
||
description: Per-medication allowed / preferred injection sites. Empty array = no per-medication restriction (every site
|
||
offered). The effective pickable set is this list minus the user's global exclusion.
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- ABDOMEN_LEFT
|
||
- ABDOMEN_RIGHT
|
||
- ABDOMEN_UPPER_LEFT
|
||
- ABDOMEN_UPPER_RIGHT
|
||
- THIGH_LEFT
|
||
- THIGH_RIGHT
|
||
- UPPER_ARM_LEFT
|
||
- UPPER_ARM_RIGHT
|
||
notificationsEnabled:
|
||
type: boolean
|
||
liveActivityEnabled:
|
||
description: iOS Live Activity opt-in for this medication's reminders. Default false. The iOS client owns the
|
||
ActivityKit lifecycle; the server only stores + echoes the flag.
|
||
type: boolean
|
||
criticalAlarmEnabled:
|
||
description: iOS 26 AlarmKit critical-reminder opt-in. Default false. Critical alarms bypass the device mute switch /
|
||
Focus; the server stores the preference only and hangs no server-side behaviour off it.
|
||
type: boolean
|
||
atcCode:
|
||
description: Optional WHO ATC classification code (active-substance class, 7 chars, e.g. `A10BX10`).
|
||
User/clinician-asserted; never machine-guessed. Emitted on the FHIR `medicationCodeableConcept` under
|
||
`http://www.whocc.no/atc`. NULL clears it; absent leaves it untouched.
|
||
anyOf:
|
||
- type: string
|
||
pattern: ^[A-Z]\d{2}[A-Z]{2}\d{2}$
|
||
- type: "null"
|
||
rxNormCode:
|
||
description: Optional RxNorm RxCUI (numeric, US identifier, e.g. `2601723`). Secondary coding emitted under
|
||
`http://www.nlm.nih.gov/research/umls/rxnorm` alongside any ATC code, never instead of the free-text name.
|
||
NULL clears it; absent leaves it untouched.
|
||
anyOf:
|
||
- type: string
|
||
maxLength: 20
|
||
pattern: ^\d+$
|
||
- type: "null"
|
||
startsOn:
|
||
description: Date the medication course begins (ISO `YYYY-MM-DD`). Anchors RRULE BYDAY / BYMONTHDAY patterns and the
|
||
rolling-interval countdown's first window. NULL = active from creation (the legacy implicit behaviour).
|
||
Required when `oneShot` is true.
|
||
anyOf:
|
||
- type: string
|
||
format: date
|
||
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])))$
|
||
- type: "null"
|
||
endsOn:
|
||
description: Date the medication course ends (ISO `YYYY-MM-DD`). NULL = no end date (chronic). When `oneShot` is true
|
||
the route normalises `endsOn` to equal `startsOn`.
|
||
anyOf:
|
||
- type: string
|
||
format: date
|
||
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])))$
|
||
- type: "null"
|
||
oneShot:
|
||
description: Single-administration medication (e.g. flu shot, post-op single dose). When true the medication has at most
|
||
one schedule with no `rrule` / `rollingIntervalDays`, and `active` auto-flips to false once the dose is
|
||
logged (non-skipped).
|
||
type: boolean
|
||
schedules:
|
||
minItems: 1
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleInput"
|
||
required:
|
||
- name
|
||
- dose
|
||
- schedules
|
||
description: 'Create-medication body. The route enforces the v1.5 cross-field invariants on top of the per-schedule
|
||
`rrule_xor_rolling` Zod refine: a `oneShot:true` medication may carry at most one schedule and that schedule
|
||
must not declare a recurrence; `endsOn` is normalised to equal `startsOn` for one-shot doses; a recurring
|
||
schedule with no `rrule`, `rollingIntervalDays`, or legacy `daysOfWeek` defaults to `rrule = "FREQ=DAILY"`; and
|
||
`timesOfDay` is dual-written from `windowStart` when the caller omits it.'
|
||
MedicationScheduleInput:
|
||
type: object
|
||
properties:
|
||
windowStart:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):([0-5]\d)$
|
||
description: Legacy single-time-of-intake (HH:mm, user local). v1.5 keeps the field for backwards compatibility with
|
||
pre-wizard iOS clients; the new `timesOfDay` array supersedes it.
|
||
windowEnd:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):([0-5]\d)$
|
||
description: Legacy reminder-window upper bound (HH:mm). Used to derive the late-classification grace span when
|
||
`reminderGraceMinutes` is null.
|
||
label:
|
||
description: Optional human label (e.g. "Morning", "Evening").
|
||
type: string
|
||
maxLength: 50
|
||
dose:
|
||
description: Per-schedule dose override. NULL means the schedule inherits `Medication.dose`.
|
||
type: string
|
||
maxLength: 50
|
||
daysOfWeek:
|
||
description: Legacy day-of-week filter (0=Sunday..6=Saturday). v1.5 reads new writes through `rrule` first; this field
|
||
is preserved for pre-v1.5 rows and is the input the route serialises into the persisted `days_of_week`
|
||
string.
|
||
type: array
|
||
items:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 6
|
||
intervalWeeks:
|
||
description: Legacy multi-week stride (1..4). Bi-weekly + tri-weekly were broken in the pre-v1.5 reminder worker; new
|
||
writes encode the same intent via `rrule` (e.g. `FREQ=WEEKLY;INTERVAL=2;BYDAY=WE`).
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 4
|
||
timesOfDay:
|
||
description: v1.5 first-class points-in-time the dose is taken (HH:mm, user local). Up to 8 entries. Absent or empty
|
||
means the route stamps `[windowStart]` so the new engine always sees a populated array.
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):([0-5]\d)$
|
||
reminderGraceMinutes:
|
||
description: Reminder grace window in minutes. Caps at 24h. NULL falls back to the legacy `windowEnd - windowStart` span.
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 1440
|
||
rrule:
|
||
description: "RFC 5545 RRULE string (subset). Use for daily / weekly-with-BYDAY / multi-week / monthly / yearly
|
||
cadences. Mutually exclusive with `rollingIntervalDays`. Examples: `FREQ=DAILY`,
|
||
`FREQ=WEEKLY;BYDAY=MO,WE,FR`, `FREQ=WEEKLY;INTERVAL=2;BYDAY=WE`, `FREQ=MONTHLY;BYMONTHDAY=1`,
|
||
`FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=1`."
|
||
type: string
|
||
maxLength: 200
|
||
pattern: ^FREQ=(?:DAILY|WEEKLY|MONTHLY|YEARLY)(?:;(?:INTERVAL=\d{1,3}|BYDAY=(?:MO|TU|WE|TH|FR|SA|SU)(?:,(?:MO|TU|WE|TH|FR|SA|SU))*|BYMONTHDAY=-?\d{1,2}(?:,-?\d{1,2})*|BYMONTH=\d{1,2}(?:,\d{1,2})*|COUNT=\d{1,4}|UNTIL=\d{8}T\d{6}Z))*$
|
||
rollingIntervalDays:
|
||
description: Flexible-rolling interval in days, counted forward from the latest `MedicationIntakeEvent.takenAt` (the
|
||
dose re-anchors when logged). Mutually exclusive with `rrule`. Range 1..365.
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 365
|
||
scheduleType:
|
||
description: Schedule type. SCHEDULED (default) = rrule / rolling / legacy cadence. PRN = as-needed (never projected,
|
||
reminded, or counted in compliance expected; still loggable via the intake route). CYCLIC = N weeks on / M
|
||
weeks off, gating whichever inner cadence the rrule / legacy fields describe.
|
||
type: string
|
||
enum:
|
||
- SCHEDULED
|
||
- PRN
|
||
- CYCLIC
|
||
cyclicOnWeeks:
|
||
description: Cyclic "on" weeks (1..52). Required when `scheduleType` is CYCLIC; ignored otherwise.
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 52
|
||
cyclicOffWeeks:
|
||
description: Cyclic "off" weeks (0..52). Required when `scheduleType` is CYCLIC; ignored otherwise.
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 52
|
||
required:
|
||
- windowStart
|
||
- windowEnd
|
||
description: Single schedule entry on a medication. v1.5 introduces `timesOfDay`, `rrule`, `rollingIntervalDays`, and
|
||
`reminderGraceMinutes` as first-class fields; `windowStart`, `windowEnd`, `daysOfWeek`, and `intervalWeeks` are
|
||
preserved through the v1.5.x line for backwards compatibility. **`rrule` and `rollingIntervalDays` are mutually
|
||
exclusive** — supplying both fails 422 (`rrule_xor_rolling`). The DB enforces the same invariant via a CHECK
|
||
constraint.
|
||
UpdateMedicationRequest:
|
||
type: object
|
||
properties:
|
||
name:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 100
|
||
dose:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 50
|
||
category:
|
||
type: string
|
||
enum:
|
||
- BLOOD_PRESSURE
|
||
- VITAMIN
|
||
- SUPPLEMENT
|
||
- PAIN_RELIEF
|
||
- ALLERGY
|
||
- DIGESTIVE
|
||
- THYROID
|
||
- HORMONE
|
||
- SKIN
|
||
- SLEEP_AID
|
||
- DIABETES
|
||
- ANTIBIOTIC
|
||
- OTHER
|
||
treatmentClass:
|
||
type: string
|
||
enum:
|
||
- GENERIC
|
||
- GLP1
|
||
dosesPerUnit:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 1
|
||
maximum: 100
|
||
- type: "null"
|
||
deliveryForm:
|
||
type: string
|
||
enum:
|
||
- ORAL
|
||
- INJECTION
|
||
- OTHER
|
||
trackInjectionSites:
|
||
description: Per-medication injection-site tracking opt-in. Only meaningful for an INJECTION delivery form. Set false to
|
||
deactivate tracking.
|
||
type: boolean
|
||
allowedInjectionSites:
|
||
description: Per-medication allowed / preferred injection sites. Empty array clears the restriction. The effective
|
||
pickable set subtracts the user's global exclusion (deny wins).
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- ABDOMEN_LEFT
|
||
- ABDOMEN_RIGHT
|
||
- ABDOMEN_UPPER_LEFT
|
||
- ABDOMEN_UPPER_RIGHT
|
||
- THIGH_LEFT
|
||
- THIGH_RIGHT
|
||
- UPPER_ARM_LEFT
|
||
- UPPER_ARM_RIGHT
|
||
active:
|
||
type: boolean
|
||
notificationsEnabled:
|
||
type: boolean
|
||
liveActivityEnabled:
|
||
description: iOS Live Activity opt-in for this medication's reminders. The iOS client owns the ActivityKit lifecycle;
|
||
the server only stores + echoes the flag.
|
||
type: boolean
|
||
criticalAlarmEnabled:
|
||
description: iOS 26 AlarmKit critical-reminder opt-in. Critical alarms bypass the device mute switch / Focus; the server
|
||
stores the preference only.
|
||
type: boolean
|
||
atcCode:
|
||
description: Optional WHO ATC classification code (active-substance class, 7 chars, e.g. `A10BX10`).
|
||
User/clinician-asserted; never machine-guessed. Emitted on the FHIR `medicationCodeableConcept` under
|
||
`http://www.whocc.no/atc`. NULL clears it; absent leaves it untouched.
|
||
anyOf:
|
||
- type: string
|
||
pattern: ^[A-Z]\d{2}[A-Z]{2}\d{2}$
|
||
- type: "null"
|
||
rxNormCode:
|
||
description: Optional RxNorm RxCUI (numeric, US identifier, e.g. `2601723`). Secondary coding emitted under
|
||
`http://www.nlm.nih.gov/research/umls/rxnorm` alongside any ATC code, never instead of the free-text name.
|
||
NULL clears it; absent leaves it untouched.
|
||
anyOf:
|
||
- type: string
|
||
maxLength: 20
|
||
pattern: ^\d+$
|
||
- type: "null"
|
||
startsOn:
|
||
description: Date the medication course begins (ISO `YYYY-MM-DD`). Anchors RRULE BYDAY / BYMONTHDAY patterns and the
|
||
rolling-interval countdown's first window. NULL = active from creation (the legacy implicit behaviour).
|
||
Required when `oneShot` is true.
|
||
anyOf:
|
||
- type: string
|
||
format: date
|
||
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])))$
|
||
- type: "null"
|
||
endsOn:
|
||
description: Date the medication course ends (ISO `YYYY-MM-DD`). NULL = no end date (chronic). When `oneShot` is true
|
||
the route normalises `endsOn` to equal `startsOn`.
|
||
anyOf:
|
||
- type: string
|
||
format: date
|
||
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])))$
|
||
- type: "null"
|
||
oneShot:
|
||
description: Single-administration medication (e.g. flu shot, post-op single dose). When true the medication has at most
|
||
one schedule with no `rrule` / `rollingIntervalDays`, and `active` auto-flips to false once the dose is
|
||
logged (non-skipped).
|
||
type: boolean
|
||
schedules:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleInput"
|
||
reminderGraceMinutes:
|
||
description: "Detail-page bridge: primary-schedule reminder-window in minutes. The route maps the value onto the primary
|
||
schedule's `reminderGraceMinutes` field; ignored when a full `schedules` array is also supplied."
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 0
|
||
maximum: 1440
|
||
- type: "null"
|
||
description: Update-medication body. Every field is optional; omitted fields are left untouched. Supplying `schedules`
|
||
REPLACES the medication's full schedule list (the route deletes existing rows before re-creating). Flipping
|
||
`active` to false records the current timestamp on `pausedAt`; flipping back to true clears it. v1.5 invariants
|
||
on the `schedules` array match the create path.
|
||
MedicationIntakeRequest:
|
||
type: object
|
||
properties:
|
||
medicationId:
|
||
type: string
|
||
minLength: 1
|
||
description: Server-narrowed from the URL path. The route layer overwrites whatever the body supplies before Zod parsing
|
||
so a caller cannot log an intake against another medication.
|
||
scheduledFor:
|
||
description: Slot the dose belongs to. Defaults to `takenAt` (or `now()` when both are absent) so the compliance pairing
|
||
logic can pin the dose to a schedule slot.
|
||
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)))$
|
||
takenAt:
|
||
description: When the dose was actually taken. NULL when `skipped` is true; defaults to `now()` for non-skipped intakes.
|
||
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)))$
|
||
skipped:
|
||
default: false
|
||
description: True to log a skipped slot (no consumption, no inventory decrement, one-shot medications stay active).
|
||
type: boolean
|
||
idempotencyKey:
|
||
description: Caller-issued de-dup key. A second POST with the same key returns the original event without creating a new
|
||
row.
|
||
type: string
|
||
maxLength: 128
|
||
injectionSite:
|
||
description: Optional injection site for a taken dose. Honoured only when the medication is an INJECTION with
|
||
site-tracking enabled; validated against the medication's effective allowed set (per-medication allowed
|
||
sites minus the user's global exclusion). A disallowed site returns 422. Omit to record the dose without a
|
||
site.
|
||
type: string
|
||
enum:
|
||
- ABDOMEN_LEFT
|
||
- ABDOMEN_RIGHT
|
||
- ABDOMEN_UPPER_LEFT
|
||
- ABDOMEN_UPPER_RIGHT
|
||
- THIGH_LEFT
|
||
- THIGH_RIGHT
|
||
- UPPER_ARM_LEFT
|
||
- UPPER_ARM_RIGHT
|
||
required:
|
||
- medicationId
|
||
description: Per-medication intake log body. Idempotent via `idempotencyKey`; the server also dedupes by a 60-second
|
||
sliding window when the key is absent. Non-skipped intakes auto-decrement pen inventory (best-effort), refresh
|
||
the per-day compliance rollup, and — for one-shot medications — flip `active` to false. The optional
|
||
`injectionSite` is persisted only for an INJECTION medication with site-tracking enabled and is validated
|
||
against the medication's effective allowed set (422 on a disallowed value).
|
||
MedicationExtractRequest:
|
||
type: object
|
||
properties:
|
||
text:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 2000
|
||
description: Free-text medication description (any locale). Up to 2 000 characters. The model never echoes the text back
|
||
into another tenant — it is only used to produce the structured fields.
|
||
locale:
|
||
description: Optional UI locale hint for the model.
|
||
type: string
|
||
enum:
|
||
- en
|
||
- de
|
||
- es
|
||
- fr
|
||
- it
|
||
- pl
|
||
today:
|
||
description: "Optional override of the reference date used to resolve relative phrases (\"tomorrow\", \"next Monday\").
|
||
Format: `YYYY-MM-DD`. Defaults to the server's UTC day."
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
required:
|
||
- text
|
||
description: Free-text medication description payload. The route runs the text through the Coach provider chain and
|
||
returns a partial structured payload the wizard merges. Rate-limited 10 requests / 5 minutes / user;
|
||
budget-gated against the daily Coach token ceiling.
|
||
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.
|
||
ProfileUpdateRequest:
|
||
type: object
|
||
properties:
|
||
email:
|
||
anyOf:
|
||
- type: string
|
||
format: email
|
||
pattern: ^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
|
||
- type: "null"
|
||
heightCm:
|
||
anyOf:
|
||
- type: number
|
||
minimum: 50
|
||
maximum: 300
|
||
- type: "null"
|
||
dateOfBirth:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
gender:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- MALE
|
||
- FEMALE
|
||
- type: "null"
|
||
displayName:
|
||
anyOf:
|
||
- type: string
|
||
minLength: 1
|
||
maxLength: 80
|
||
- type: "null"
|
||
locale:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
- type: "null"
|
||
timezone:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 64
|
||
moodReminderEnabled:
|
||
type: boolean
|
||
fullName:
|
||
anyOf:
|
||
- type: string
|
||
maxLength: 120
|
||
- type: "null"
|
||
insurerName:
|
||
anyOf:
|
||
- type: string
|
||
maxLength: 120
|
||
- type: "null"
|
||
insuranceNumber:
|
||
description: German KVNR. Empty/null clears it; mod-10 check enforced.
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
insurerIkNumber:
|
||
description: German insurer institution number (IKNR). Optional; empty/null clears it. A non-empty value must be exactly
|
||
9 digits (no checksum enforced).
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Partial profile update. Every field is optional; an omitted field is left untouched, an explicit null (or
|
||
empty string) clears it. `userId` is never accepted — it is narrowed from the session/token.
|
||
InsightsLayoutBody:
|
||
type: object
|
||
properties:
|
||
version:
|
||
type: number
|
||
const: 1
|
||
tiles:
|
||
minItems: 1
|
||
maxItems: 50
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
enum:
|
||
- overview
|
||
- blood-pressure
|
||
- pulse
|
||
- oxygen
|
||
- body-temperature
|
||
- respiratory-rate
|
||
- weight
|
||
- bmi
|
||
- body-water
|
||
- bone-mass
|
||
- fat-free-mass
|
||
- fat-mass
|
||
- muscle-mass
|
||
- visceral-fat
|
||
- lean-body-mass
|
||
- active-energy
|
||
- workouts
|
||
- flights-climbed
|
||
- walking-distance
|
||
- walking-steadiness
|
||
- walking-heart-rate
|
||
- walking-asymmetry
|
||
- double-support-time
|
||
- step-length
|
||
- walking-speed
|
||
- cardio-fitness
|
||
- falls
|
||
- six-minute-walk
|
||
- stair-ascent-speed
|
||
- stair-descent-speed
|
||
- sleep
|
||
- breathing-disturbances
|
||
- resting-pulse
|
||
- hrv
|
||
- pulse-wave-velocity
|
||
- vascular-age
|
||
- cardio-recovery
|
||
- environmental-audio
|
||
- headphone-audio
|
||
- audio-events
|
||
- daylight
|
||
- blood-glucose
|
||
- skin-temperature
|
||
- wrist-temperature
|
||
- mood
|
||
- medications
|
||
- blutdruck
|
||
- puls
|
||
- sauerstoff
|
||
- koerpertemperatur
|
||
- gewicht
|
||
- aktive-energie
|
||
- schlaf
|
||
- ruhepuls
|
||
- stimmung
|
||
- medikamente
|
||
visible:
|
||
type: boolean
|
||
order:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 99
|
||
required:
|
||
- id
|
||
- visible
|
||
- order
|
||
required:
|
||
- version
|
||
- tiles
|
||
description: "Per-user Insights tile layout: an ordered list of tiles with a visibility flag. `version` is the layout
|
||
schema version. Tile ids are a closed enum: the canonical ids are English (matching the routed
|
||
`/insights/<slug>` sub-pages). The legacy German ids (blutdruck, puls, sauerstoff, koerpertemperatur, gewicht,
|
||
aktive-energie, schlaf, ruhepuls, stimmung, medikamente) remain accepted on input for backward compatibility and
|
||
are normalised to their English equivalents before persisting; GET responses always carry the canonical English
|
||
ids. The legacy ids are deprecated and will be removed in a future major version."
|
||
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.
|
||
InsightsPregenerateRequest:
|
||
type: object
|
||
properties: {}
|
||
description: No body fields. The user is taken from the session / Bearer and the locale from the session; the warm
|
||
covers every assessment for that user.
|
||
MedicationTreatmentClass:
|
||
type: string
|
||
enum:
|
||
- GENERIC
|
||
- GLP1
|
||
description: Prisma-level treatment-class discriminator. `GLP1` unlocks the GLP-1 specialist surfaces (injection-site
|
||
rotation, titration history, pen inventory, GLP-1-aware Coach).
|
||
MedicationSchedule:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
medicationId:
|
||
type: string
|
||
windowStart:
|
||
type: string
|
||
description: Legacy single-time-of-intake (HH:mm, user local). Preserved for backwards compatibility; the new
|
||
`timesOfDay` array supersedes it.
|
||
windowEnd:
|
||
type: string
|
||
description: Legacy reminder-window upper bound (HH:mm). Used to derive the late-classification grace span when
|
||
`reminderGraceMinutes` is null.
|
||
label:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
dose:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Per-schedule dose override. NULL means the schedule inherits `Medication.dose`.
|
||
daysOfWeek:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Legacy persisted recurrence encoding (`null` | `1,3,5` | `i2;1,3,5`). v1.5 readers consult `rrule` first;
|
||
the field is kept for pre-v1.5 rows. v1.6.0 drops the column.
|
||
timesOfDay:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: v1.5 first-class points-in-time the dose is taken (HH:mm, user local). Backfilled to `[windowStart]` for
|
||
every pre-v1.5 row.
|
||
reminderGraceMinutes:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Reminder grace window in minutes. NULL falls back to the legacy `windowEnd - windowStart` span.
|
||
rrule:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: RFC 5545 RRULE string (subset). Used for calendar-anchored cadences. **Mutually exclusive with
|
||
`rollingIntervalDays`** — exactly one of the two is non-null on any v1.5+ schedule (or both are null on
|
||
legacy rows that haven't been touched since the migration).
|
||
rollingIntervalDays:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Flexible-rolling interval in days, counted forward from the latest `MedicationIntakeEvent.takenAt`.
|
||
**Mutually exclusive with `rrule`.**
|
||
scheduleType:
|
||
type: string
|
||
enum:
|
||
- SCHEDULED
|
||
- PRN
|
||
- CYCLIC
|
||
description: v1.7.0 schedule-type discriminator. SCHEDULED = rrule / rolling / legacy cadence. PRN = as-needed (never
|
||
projected, reminded, or counted in compliance expected; still loggable via the intake route). CYCLIC = N
|
||
weeks on / M weeks off, gating whichever inner cadence the rrule / legacy fields describe.
|
||
cyclicOnWeeks:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: v1.7.0 cyclic "on" weeks. Only meaningful when `scheduleType` is CYCLIC; null otherwise.
|
||
cyclicOffWeeks:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: v1.7.0 cyclic "off" weeks. Only meaningful when `scheduleType` is CYCLIC; null otherwise.
|
||
required:
|
||
- id
|
||
- medicationId
|
||
- windowStart
|
||
- windowEnd
|
||
- label
|
||
- dose
|
||
- daysOfWeek
|
||
- timesOfDay
|
||
- reminderGraceMinutes
|
||
- rrule
|
||
- rollingIntervalDays
|
||
- scheduleType
|
||
- cyclicOnWeeks
|
||
- cyclicOffWeeks
|
||
description: Schedule entry attached to a medication. v1.5 promotes `timesOfDay` to first-class and introduces `rrule`
|
||
(calendar-anchored cadences) and `rollingIntervalDays` (flexible-rolling cadences). The two recurrence
|
||
primitives are mutually exclusive — enforced by the Zod refine on writes, the route layer, and a DB CHECK
|
||
constraint (`medication_schedules_rrule_xor_rolling`). v1.7.0 adds `scheduleType` (SCHEDULED / PRN / CYCLIC) and
|
||
the cyclic on/off-week fields.
|
||
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."
|
||
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`.
|
||
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
|
||
SyncStateEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/SyncStateResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
SyncStateResponse:
|
||
type: object
|
||
properties:
|
||
userId:
|
||
type: string
|
||
timezone:
|
||
type: string
|
||
lastSyncedAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
serverNow:
|
||
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)))$
|
||
measurements:
|
||
type: object
|
||
properties:
|
||
lastUpdatedAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
liveCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
tombstonedCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- lastUpdatedAt
|
||
- liveCount
|
||
- tombstonedCount
|
||
additionalProperties: false
|
||
mood:
|
||
type: object
|
||
properties:
|
||
lastUpdatedAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
liveCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
tombstonedCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- lastUpdatedAt
|
||
- liveCount
|
||
- tombstonedCount
|
||
additionalProperties: false
|
||
intakes:
|
||
type: object
|
||
properties:
|
||
lastUpdatedAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
liveCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
tombstonedCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- lastUpdatedAt
|
||
- liveCount
|
||
- tombstonedCount
|
||
additionalProperties: false
|
||
sync:
|
||
type: object
|
||
properties:
|
||
incrementalWindowDays:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
description: Days an incremental delta stays valid; tracks the native refresh-token lifetime. Beyond it a device
|
||
re-pairs with a full backfill. iOS derives its window from this rather than hardcoding 60.
|
||
tombstoneRetentionDays:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
description: Horizon past which tombstones may be pruned. A cursor older than this gets `cursorExpired` on
|
||
`/api/sync/changes`.
|
||
required:
|
||
- incrementalWindowDays
|
||
- tombstoneRetentionDays
|
||
additionalProperties: false
|
||
description: Sync-window metadata the client reads instead of hardcoding.
|
||
required:
|
||
- userId
|
||
- timezone
|
||
- lastSyncedAt
|
||
- serverNow
|
||
- measurements
|
||
- mood
|
||
- intakes
|
||
- sync
|
||
additionalProperties: false
|
||
description: iOS SyncMode handshake. Each GET also advances the server-side `lastSyncedAt` checkpoint and returns the
|
||
previous value. The cheap 'should I sync?' summary; the durable delta cursor lives on `/api/sync/changes`.
|
||
SyncChangesEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/SyncChangesResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
SyncChangesResponse:
|
||
type: object
|
||
properties:
|
||
serverNow:
|
||
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)))$
|
||
cursor:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Opaque cursor to echo into the next request.
|
||
hasMore:
|
||
type: boolean
|
||
description: False once the client is caught up as of `serverNow`.
|
||
cursorExpired:
|
||
type: boolean
|
||
description: True when the supplied cursor predates tombstone retention — drop the cursor and do a clean initial sync.
|
||
changes:
|
||
type: object
|
||
properties:
|
||
measurements:
|
||
type: object
|
||
properties:
|
||
upserts:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncMeasurementUpsert"
|
||
tombstones:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncMeasurementTombstone"
|
||
required:
|
||
- upserts
|
||
- tombstones
|
||
additionalProperties: false
|
||
mood:
|
||
type: object
|
||
properties:
|
||
upserts:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncMoodUpsert"
|
||
tombstones:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncMoodTombstone"
|
||
required:
|
||
- upserts
|
||
- tombstones
|
||
additionalProperties: false
|
||
intakes:
|
||
type: object
|
||
properties:
|
||
upserts:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncIntakeUpsert"
|
||
tombstones:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncIntakeTombstone"
|
||
required:
|
||
- upserts
|
||
- tombstones
|
||
additionalProperties: false
|
||
required:
|
||
- measurements
|
||
- mood
|
||
- intakes
|
||
additionalProperties: false
|
||
required:
|
||
- serverNow
|
||
- cursor
|
||
- hasMore
|
||
- cursorExpired
|
||
- changes
|
||
additionalProperties: false
|
||
description: "Multi-domain delta page (v1.7.0): measurements + mood + intakes. One opaque multi-domain keyset cursor;
|
||
tombstones apply before upserts within each domain. Tombstone identity: measurements key on externalId, mood +
|
||
intakes on server id. The iOS consumer is measurements-only this cycle; mood + intakes are forward-prep."
|
||
SyncMeasurementUpsert:
|
||
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
|
||
- RESPIRATORY_RATE
|
||
- BODY_MASS_INDEX
|
||
- LEAN_BODY_MASS
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
- IRREGULAR_RHYTHM_NOTIFICATION
|
||
- HIGH_HEART_RATE_EVENT
|
||
- LOW_HEART_RATE_EVENT
|
||
- WALKING_STEADINESS_EVENT
|
||
- BREATHING_DISTURBANCE_EVENT
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
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
|
||
- COMPUTED
|
||
notes:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
externalId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Cross-device dedup key (UUID string or `stats:<id>:<date>`).
|
||
syncVersion:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
description: LWW reconciliation counter; echo to keep the mirror monotonic.
|
||
updatedAt:
|
||
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
|
||
- type
|
||
- value
|
||
- unit
|
||
- measuredAt
|
||
- source
|
||
- externalId
|
||
- syncVersion
|
||
- updatedAt
|
||
additionalProperties: false
|
||
SyncMeasurementTombstone:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
externalId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: The identity key the client dedups on for measurements.
|
||
syncVersion:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
deletedAt:
|
||
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)))$
|
||
updatedAt:
|
||
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
|
||
- externalId
|
||
- syncVersion
|
||
- deletedAt
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: A soft-deleted measurement. Apply tombstones BEFORE upserts within a page to avoid resurrecting a row.
|
||
SyncMoodUpsert:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
date:
|
||
type: string
|
||
description: YYYY-MM-DD anchored to the row's `tz`.
|
||
mood:
|
||
type: string
|
||
score:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
tags:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: JSON array of tag keys, or null.
|
||
note:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
moodLoggedAt:
|
||
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
|
||
syncVersion:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: LWW reconciliation counter; mood is last-writer-wins by it.
|
||
updatedAt:
|
||
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
|
||
- date
|
||
- mood
|
||
- score
|
||
- tags
|
||
- note
|
||
- moodLoggedAt
|
||
- source
|
||
- syncVersion
|
||
- updatedAt
|
||
additionalProperties: false
|
||
SyncMoodTombstone:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
description: Server id — the identity key the client dedups on for mood.
|
||
syncVersion:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
deletedAt:
|
||
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)))$
|
||
updatedAt:
|
||
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
|
||
- syncVersion
|
||
- deletedAt
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: A soft-deleted mood entry, keyed on server `id`. Apply before upserts within the domain page.
|
||
SyncIntakeUpsert:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
medicationId:
|
||
type: string
|
||
scheduledFor:
|
||
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)))$
|
||
takenAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
skipped:
|
||
type: boolean
|
||
source:
|
||
type: string
|
||
syncVersion:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Reconciliation counter. An intake is immutable; a correction is a tombstone + re-insert.
|
||
updatedAt:
|
||
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
|
||
- medicationId
|
||
- scheduledFor
|
||
- takenAt
|
||
- skipped
|
||
- source
|
||
- syncVersion
|
||
- updatedAt
|
||
additionalProperties: false
|
||
SyncIntakeTombstone:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
description: Server id — the identity key the client dedups on for intakes.
|
||
syncVersion:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
deletedAt:
|
||
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)))$
|
||
updatedAt:
|
||
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
|
||
- syncVersion
|
||
- deletedAt
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: A soft-deleted medication intake, keyed on server `id`. Apply before upserts within the domain page.
|
||
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
|
||
- RESPIRATORY_RATE
|
||
- BODY_MASS_INDEX
|
||
- LEAN_BODY_MASS
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
- IRREGULAR_RHYTHM_NOTIFICATION
|
||
- HIGH_HEART_RATE_EVENT
|
||
- LOW_HEART_RATE_EVENT
|
||
- WALKING_STEADINESS_EVENT
|
||
- BREATHING_DISTURBANCE_EVENT
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
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
|
||
- COMPUTED
|
||
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
|
||
- COMPUTED
|
||
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
|
||
- COMPUTED
|
||
externalId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
metadata:
|
||
anyOf:
|
||
- {}
|
||
- type: "null"
|
||
route:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/WorkoutRouteGeometry"
|
||
- type: "null"
|
||
samples:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/WorkoutHrSeries"
|
||
- type: "null"
|
||
canonicalId:
|
||
type: string
|
||
required:
|
||
- id
|
||
- sportType
|
||
- startedAt
|
||
- endedAt
|
||
- durationSec
|
||
- distanceM
|
||
- activeEnergyKcal
|
||
- avgHr
|
||
- maxHr
|
||
- minHr
|
||
- stepCount
|
||
- elevationM
|
||
- pauseDurationSec
|
||
- source
|
||
- externalId
|
||
- metadata
|
||
- route
|
||
- samples
|
||
- canonicalId
|
||
additionalProperties: false
|
||
WorkoutRouteGeometry:
|
||
type: object
|
||
properties:
|
||
geometry: {}
|
||
sampleTimestamps:
|
||
anyOf:
|
||
- type: array
|
||
items:
|
||
type: string
|
||
- type: "null"
|
||
required:
|
||
- geometry
|
||
- sampleTimestamps
|
||
additionalProperties: false
|
||
WorkoutHrSeries:
|
||
type: object
|
||
properties:
|
||
sampleCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
samples: {}
|
||
required:
|
||
- sampleCount
|
||
- samples
|
||
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
|
||
ListMedicationsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationListEntry"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationListEntry:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
name:
|
||
type: string
|
||
dose:
|
||
type: string
|
||
treatmentClass:
|
||
$ref: "#/components/schemas/MedicationTreatmentClass"
|
||
dosesPerUnit:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Doses per pen / vial for inventory tracking. NULL = inventory tracking off.
|
||
active:
|
||
type: boolean
|
||
notificationsEnabled:
|
||
type: boolean
|
||
liveActivityEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS Live Activity opt-in for this medication's reminders. Default false. The iOS client owns the
|
||
ActivityKit lifecycle; the server only stores + echoes the flag.
|
||
criticalAlarmEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS 26 AlarmKit critical-reminder opt-in. Default false. Critical alarms bypass the device mute
|
||
switch / Focus; the server stores the preference only.
|
||
atcCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional WHO ATC classification code (active-substance class, e.g. `A10BX10`).
|
||
User/clinician-asserted; never machine-guessed. Emitted on the FHIR `medicationCodeableConcept` under
|
||
`http://www.whocc.no/atc`. NULL = no code captured.
|
||
rxNormCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional RxNorm RxCUI (numeric, US identifier, e.g. `2601723`). Secondary FHIR coding under
|
||
`http://www.nlm.nih.gov/research/umls/rxnorm`, alongside any ATC code. NULL = no code captured.
|
||
pausedAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
snoozedUntil:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
nextDueAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: v1.7.0 server-computed next due instant across all the medication's schedules (earliest
|
||
`nextOccurrenceAfter`). Read-only — computed, not stored. NULL when no schedule has an upcoming slot
|
||
(paused, one-shot in the past, `endsOn` crossed, every schedule PRN). The list GET is cached 60 s, so a 60 s
|
||
staleness is accepted.
|
||
startsOn:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: v1.5 course start (ISO date). Anchors RRULE BYDAY / BYMONTHDAY patterns and the rolling-interval
|
||
countdown's first window. NULL means active from creation.
|
||
endsOn:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: v1.5 course end (ISO date). NULL means chronic. Equals `startsOn` when `oneShot` is true.
|
||
oneShot:
|
||
type: boolean
|
||
description: v1.5 single-administration flag. When true the medication has at most one schedule (no `rrule` /
|
||
`rollingIntervalDays`), and `active` auto-flips to false once the dose is logged.
|
||
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)))$
|
||
updatedAt:
|
||
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)))$
|
||
schedules:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleOutput"
|
||
category:
|
||
$ref: "#/components/schemas/MedicationCategory"
|
||
lastTakenAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: Latest non-skipped `MedicationIntakeEvent.takenAt` for the medication. Drives the rolling-cadence countdown
|
||
surface.
|
||
todayEventCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Number of intake events scheduled for today (user-local day window).
|
||
required:
|
||
- id
|
||
- name
|
||
- dose
|
||
- treatmentClass
|
||
- dosesPerUnit
|
||
- active
|
||
- notificationsEnabled
|
||
- liveActivityEnabled
|
||
- criticalAlarmEnabled
|
||
- atcCode
|
||
- rxNormCode
|
||
- pausedAt
|
||
- snoozedUntil
|
||
- nextDueAt
|
||
- startsOn
|
||
- endsOn
|
||
- oneShot
|
||
- createdAt
|
||
- updatedAt
|
||
- schedules
|
||
- category
|
||
- lastTakenAt
|
||
- todayEventCount
|
||
additionalProperties: false
|
||
description: List-row variant of the medication resource enriched with the joined `category`, `lastTakenAt`, and
|
||
`todayEventCount` fields the dashboard + iOS client consume. The base medication fields (`id`, `name`, `dose`,
|
||
`treatmentClass`, `dosesPerUnit`, `active`, `notificationsEnabled`, `pausedAt`, `snoozedUntil`, `startsOn`,
|
||
`endsOn`, `oneShot`, `createdAt`, `updatedAt`, `schedules`) are inlined; see the `Medication` component for
|
||
their semantics.
|
||
MedicationCategory:
|
||
type: string
|
||
enum:
|
||
- BLOOD_PRESSURE
|
||
- VITAMIN
|
||
- SUPPLEMENT
|
||
- PAIN_RELIEF
|
||
- ALLERGY
|
||
- DIGESTIVE
|
||
- THYROID
|
||
- HORMONE
|
||
- SKIN
|
||
- SLEEP_AID
|
||
- DIABETES
|
||
- ANTIBIOTIC
|
||
- OTHER
|
||
description: Clinical taxonomy stored in the `medication_categories` side-table. Orthogonal to `MedicationTreatmentClass`.
|
||
CreateMedicationResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationDetail"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationDetail:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
name:
|
||
type: string
|
||
dose:
|
||
type: string
|
||
treatmentClass:
|
||
$ref: "#/components/schemas/MedicationTreatmentClass"
|
||
dosesPerUnit:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Doses per pen / vial for inventory tracking. NULL = inventory tracking off.
|
||
active:
|
||
type: boolean
|
||
notificationsEnabled:
|
||
type: boolean
|
||
liveActivityEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS Live Activity opt-in for this medication's reminders. Default false. The iOS client owns the
|
||
ActivityKit lifecycle; the server only stores + echoes the flag.
|
||
criticalAlarmEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS 26 AlarmKit critical-reminder opt-in. Default false. Critical alarms bypass the device mute
|
||
switch / Focus; the server stores the preference only.
|
||
atcCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional WHO ATC classification code (active-substance class, e.g. `A10BX10`).
|
||
User/clinician-asserted; never machine-guessed. Emitted on the FHIR `medicationCodeableConcept` under
|
||
`http://www.whocc.no/atc`. NULL = no code captured.
|
||
rxNormCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional RxNorm RxCUI (numeric, US identifier, e.g. `2601723`). Secondary FHIR coding under
|
||
`http://www.nlm.nih.gov/research/umls/rxnorm`, alongside any ATC code. NULL = no code captured.
|
||
pausedAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
snoozedUntil:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
nextDueAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: v1.7.0 server-computed next due instant across all the medication's schedules (earliest
|
||
`nextOccurrenceAfter`). Read-only — computed, not stored. NULL when no schedule has an upcoming slot
|
||
(paused, one-shot in the past, `endsOn` crossed, every schedule PRN). The list GET is cached 60 s, so a 60 s
|
||
staleness is accepted.
|
||
startsOn:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: v1.5 course start (ISO date). Anchors RRULE BYDAY / BYMONTHDAY patterns and the rolling-interval
|
||
countdown's first window. NULL means active from creation.
|
||
endsOn:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: v1.5 course end (ISO date). NULL means chronic. Equals `startsOn` when `oneShot` is true.
|
||
oneShot:
|
||
type: boolean
|
||
description: v1.5 single-administration flag. When true the medication has at most one schedule (no `rrule` /
|
||
`rollingIntervalDays`), and `active` auto-flips to false once the dose is logged.
|
||
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)))$
|
||
updatedAt:
|
||
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)))$
|
||
schedules:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleOutput"
|
||
category:
|
||
$ref: "#/components/schemas/MedicationCategory"
|
||
required:
|
||
- id
|
||
- name
|
||
- dose
|
||
- treatmentClass
|
||
- dosesPerUnit
|
||
- active
|
||
- notificationsEnabled
|
||
- liveActivityEnabled
|
||
- criticalAlarmEnabled
|
||
- atcCode
|
||
- rxNormCode
|
||
- pausedAt
|
||
- snoozedUntil
|
||
- nextDueAt
|
||
- startsOn
|
||
- endsOn
|
||
- oneShot
|
||
- createdAt
|
||
- updatedAt
|
||
- schedules
|
||
- category
|
||
additionalProperties: false
|
||
description: Detail variant of the medication resource enriched with the joined `category`. The base medication fields
|
||
are inlined; see the `Medication` component for their semantics.
|
||
GetMedicationResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationDetail"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
UpdateMedicationResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationDetail"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
DeleteMedicationResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
deleted:
|
||
type: boolean
|
||
required:
|
||
- deleted
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
ReplayMedicationIntakeResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationIntakeEvent"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationIntakeEvent:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
userId:
|
||
type: string
|
||
medicationId:
|
||
type: string
|
||
scheduledFor:
|
||
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)))$
|
||
takenAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
skipped:
|
||
type: boolean
|
||
source:
|
||
type: string
|
||
enum:
|
||
- WEB
|
||
- API
|
||
- REMINDER
|
||
- IMPORT
|
||
idempotencyKey:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
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
|
||
- userId
|
||
- medicationId
|
||
- scheduledFor
|
||
- takenAt
|
||
- skipped
|
||
- source
|
||
- idempotencyKey
|
||
- createdAt
|
||
additionalProperties: false
|
||
description: Single dose log row. `takenAt` is non-null for confirmed intakes; `skipped:true` represents a
|
||
deliberately-missed dose (no inventory consumption).
|
||
CreateMedicationIntakeResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationIntakeEvent"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationExtractResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
name:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 100
|
||
dose:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 50
|
||
doseUnit:
|
||
type: string
|
||
enum:
|
||
- mg
|
||
- ml
|
||
- iu
|
||
- tablets
|
||
- drops
|
||
- puffs
|
||
- sprays
|
||
cadenceKind:
|
||
type: string
|
||
enum:
|
||
- daily
|
||
- weekdays
|
||
- everyNWeeks
|
||
- monthly
|
||
- everyNMonths
|
||
- yearly
|
||
- rolling
|
||
- oneShot
|
||
weekdays:
|
||
maxItems: 7
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MO
|
||
- TU
|
||
- WE
|
||
- TH
|
||
- FR
|
||
- SA
|
||
- SU
|
||
intervalWeeks:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 52
|
||
intervalMonths:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 12
|
||
dayOfMonth:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 31
|
||
rollingIntervalDays:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 365
|
||
timesOfDay:
|
||
maxItems: 12
|
||
type: array
|
||
items:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):[0-5]\d$
|
||
startsOn:
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
endsOn:
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
oneShot:
|
||
type: boolean
|
||
confidence:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 1
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
GetMedicationCadenceResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationCadenceResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationCadenceResponse:
|
||
type: object
|
||
properties:
|
||
windowDays:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
anchorIso:
|
||
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)))$
|
||
next:
|
||
anyOf:
|
||
- type: object
|
||
properties:
|
||
windowStart:
|
||
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)))$
|
||
windowEnd:
|
||
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)))$
|
||
scheduleIndex:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- windowStart
|
||
- windowEnd
|
||
- scheduleIndex
|
||
additionalProperties: false
|
||
- type: "null"
|
||
chips:
|
||
$ref: "#/components/schemas/MedicationCadenceChips"
|
||
timeline:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationCadenceTimelinePoint"
|
||
required:
|
||
- windowDays
|
||
- anchorIso
|
||
- next
|
||
- chips
|
||
- timeline
|
||
additionalProperties: false
|
||
description: Cadence + compliance read for a single medication. `next` is the upcoming-dose envelope (null when the
|
||
course has ended or the rolling clock has no pinning intake yet); `timeline` walks the requested `windowDays`
|
||
worth of slots in ascending time order.
|
||
MedicationCadenceChips:
|
||
type: object
|
||
properties:
|
||
adherenceRate:
|
||
type: number
|
||
streakDays:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
expectedSlots:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
actualDoses:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- adherenceRate
|
||
- streakDays
|
||
- expectedSlots
|
||
- actualDoses
|
||
additionalProperties: false
|
||
description: Four compliance summary values for the medication detail page chip row.
|
||
MedicationCadenceTimelinePoint:
|
||
type: object
|
||
properties:
|
||
day:
|
||
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)))$
|
||
windowStart:
|
||
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)))$
|
||
windowEnd:
|
||
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)))$
|
||
scheduleIndex:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
status:
|
||
type: string
|
||
required:
|
||
- day
|
||
- windowStart
|
||
- windowEnd
|
||
- scheduleIndex
|
||
- status
|
||
additionalProperties: false
|
||
description: One expected-vs-actual dose slot for the cadence timeline chart. `status` is one of `taken | skipped |
|
||
missed | pending | future` and drives the chip colour.
|
||
GetMedicationComplianceResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationComplianceResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationComplianceResponse:
|
||
type: object
|
||
properties:
|
||
compliance7:
|
||
$ref: "#/components/schemas/ComplianceResult"
|
||
compliance30:
|
||
$ref: "#/components/schemas/ComplianceResult"
|
||
dailyCompliance:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties:
|
||
$ref: "#/components/schemas/DailyComplianceEntry"
|
||
description: Flat per-day map keyed `YYYY-MM-DD` in the user timezone, one entry per day for up to 90 days back, clamped
|
||
to the medication's `createdAt` (so a recently-created med has fewer entries). No weekly/monthly collapse —
|
||
this is the raw daily grid.
|
||
complianceDisplay:
|
||
$ref: "#/components/schemas/ComplianceDisplay"
|
||
required:
|
||
- compliance7
|
||
- compliance30
|
||
- dailyCompliance
|
||
- complianceDisplay
|
||
additionalProperties: false
|
||
description: Adherence read for a single medication. `compliance30` is the authoritative 30-day taken-vs-expected
|
||
summary; `dailyCompliance` is the per-day grid for the history glyph track. The graded raw→week→month→year
|
||
series used elsewhere for AI prompts does NOT apply here — this response is never downsampled.
|
||
ComplianceResult:
|
||
type: object
|
||
properties:
|
||
totalExpected:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: "Full denominator over the window: `taken + skipped + missed`. Cadence-aware and clamped to the
|
||
medication's `createdAt`."
|
||
taken:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
skipped:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Doses the user explicitly skipped — excluded from the `rate` denominator.
|
||
missed:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
rate:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 100
|
||
description: Adherence percentage `round(taken / (taken + missed) * 100)` — `skipped` is excluded from the denominator.
|
||
streak:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Consecutive days with every due dose taken.
|
||
required:
|
||
- totalExpected
|
||
- taken
|
||
- skipped
|
||
- missed
|
||
- rate
|
||
- streak
|
||
additionalProperties: false
|
||
description: Rolling-window adherence summary. `compliance30` is the authoritative 'last 30 days, taken vs expected'
|
||
read — clients should display `rate` and use `totalExpected` as the denominator rather than re-deriving it from
|
||
the daily map.
|
||
DailyComplianceEntry:
|
||
type: object
|
||
properties:
|
||
expected:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Engine-computed due-slot count for the day. Equals `expectedCount`; kept for existing consumers.
|
||
expectedCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: True due-slot count for the day (additive field clients key off so they don't infer due-ness from
|
||
`expected`).
|
||
due:
|
||
type: boolean
|
||
description: "`expectedCount > 0`. Paint a per-day glyph as expected/missed ONLY when `due === true`; off-cadence /
|
||
pre-creation / PRN days are not misses."
|
||
taken:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
skipped:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
onTime:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Doses taken in the on-time band, including the `early` bucket (early counts as compliant).
|
||
late:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
veryLate:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
early:
|
||
description: Doses taken before the on-time band's grace start; already folded into `onTime`, surfaced separately for
|
||
consumers that differentiate.
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- expected
|
||
- expectedCount
|
||
- due
|
||
- taken
|
||
- skipped
|
||
- onTime
|
||
- late
|
||
- veryLate
|
||
additionalProperties: false
|
||
description: Per-day compliance cell with the timing breakdown that drives the history glyph track.
|
||
ComplianceDisplay:
|
||
type: object
|
||
properties:
|
||
shortDays:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
longDays:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
expectedShort:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
expectedLong:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
minStableDoses:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
short:
|
||
type: object
|
||
properties:
|
||
rate:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 100
|
||
streak:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- rate
|
||
- streak
|
||
additionalProperties: false
|
||
long:
|
||
type: object
|
||
properties:
|
||
rate:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 100
|
||
required:
|
||
- rate
|
||
additionalProperties: false
|
||
required:
|
||
- shortDays
|
||
- longDays
|
||
- expectedShort
|
||
- expectedLong
|
||
- minStableDoses
|
||
- short
|
||
- long
|
||
additionalProperties: false
|
||
description: The two-row card block whose windows scale with dosing cadence (dense meds keep 7 / 30 days, sparse meds
|
||
step both windows up). NOT the 30-day denominator — read `compliance30.totalExpected` for that.
|
||
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
|
||
dataClusters:
|
||
maxItems: 10
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- cardio
|
||
- body
|
||
- activity
|
||
- workouts
|
||
- sleep
|
||
- mood
|
||
- glucose
|
||
- medication
|
||
- mobility
|
||
- environment
|
||
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
|
||
dataClusters:
|
||
maxItems: 10
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- cardio
|
||
- body
|
||
- activity
|
||
- workouts
|
||
- sleep
|
||
- mood
|
||
- glucose
|
||
- medication
|
||
- mobility
|
||
- environment
|
||
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
|
||
- COMPUTED
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
metricPriority:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
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
|
||
- COMPUTED
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
metricPriority:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
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
|
||
UploadAvatarResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
avatarUrl:
|
||
type: string
|
||
contentType:
|
||
type: string
|
||
enum:
|
||
- image/jpeg
|
||
- image/png
|
||
- image/webp
|
||
updatedAt:
|
||
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:
|
||
- avatarUrl
|
||
- contentType
|
||
- updatedAt
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
GetProfileResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/ProfileResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
ProfileResponse:
|
||
type: object
|
||
properties:
|
||
username:
|
||
type: string
|
||
displayName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
email:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
dateOfBirth:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
gender:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- MALE
|
||
- FEMALE
|
||
- type: "null"
|
||
heightCm:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
locale:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
timezone:
|
||
type: string
|
||
moodReminderEnabled:
|
||
type: boolean
|
||
fullName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
insurerName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
insurerIkNumber:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: German insurer institution number (IKNR), 9 digits.
|
||
insuranceNumber:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: German KVNR, decrypted for the form prefill.
|
||
required:
|
||
- username
|
||
- email
|
||
- dateOfBirth
|
||
- gender
|
||
- heightCm
|
||
- locale
|
||
- timezone
|
||
- moodReminderEnabled
|
||
- fullName
|
||
- insurerName
|
||
- insurerIkNumber
|
||
- insuranceNumber
|
||
additionalProperties: false
|
||
description: Flattened profile fields for the native client (GET). The KVNR is decrypted server-side; the IKNR (v1.8.6)
|
||
is plaintext at rest.
|
||
PatchProfileResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/ProfileUpdateResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
ProfileUpdateResponse:
|
||
type: object
|
||
properties:
|
||
username:
|
||
type: string
|
||
displayName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
email:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
dateOfBirth:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
gender:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- MALE
|
||
- FEMALE
|
||
- type: "null"
|
||
heightCm:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
locale:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
timezone:
|
||
type: string
|
||
moodReminderEnabled:
|
||
type: boolean
|
||
fullName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
insurerName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
insurerIkNumber:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: German insurer institution number (IKNR), 9 digits.
|
||
hasInsuranceNumber:
|
||
type: boolean
|
||
description: Whether a KVNR is on file (the value itself is not echoed).
|
||
required:
|
||
- username
|
||
- email
|
||
- dateOfBirth
|
||
- gender
|
||
- heightCm
|
||
- locale
|
||
- timezone
|
||
- moodReminderEnabled
|
||
- fullName
|
||
- insurerName
|
||
- insurerIkNumber
|
||
- hasInsuranceNumber
|
||
additionalProperties: false
|
||
description: Profile echo returned by the PATCH/PUT update path. Reports KVNR presence as a boolean rather than
|
||
re-emitting the decrypted value.
|
||
InsightsLayout:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightsLayoutBodyOutput"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
InsightsLayoutSaved:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightsLayoutBodyOutput"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
InsightsLayoutReset:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightsLayoutBodyOutput"
|
||
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.
|
||
DashboardSnapshotResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/DashboardSnapshotResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
DashboardSnapshotResponse:
|
||
type: object
|
||
properties:
|
||
user:
|
||
type: object
|
||
properties:
|
||
username:
|
||
type: string
|
||
timezone:
|
||
type: string
|
||
heightCm:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
dateOfBirth:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
gender:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- MALE
|
||
- FEMALE
|
||
- type: "null"
|
||
glucoseUnit:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
onboardingTourCompleted:
|
||
type: boolean
|
||
greetingHour:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- username
|
||
- timezone
|
||
- heightCm
|
||
- dateOfBirth
|
||
- gender
|
||
- glucoseUnit
|
||
- onboardingTourCompleted
|
||
- greetingHour
|
||
additionalProperties: false
|
||
layout:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
layoutCatalogue:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
visible:
|
||
type: boolean
|
||
order:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- id
|
||
- visible
|
||
- order
|
||
additionalProperties: false
|
||
description: Full 27-id widget catalogue (server-known + iOS-only) with per-widget visibility + order. iOS-only ids are
|
||
appended default-invisible. The web dashboard reads `layout`; this block is the cold-launch seed for the
|
||
native client.
|
||
metricStates:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties:
|
||
type: object
|
||
properties:
|
||
value:
|
||
type: number
|
||
measuredAt:
|
||
type: string
|
||
unit:
|
||
type: string
|
||
required:
|
||
- value
|
||
- measuredAt
|
||
- unit
|
||
additionalProperties: false
|
||
description: "Latest reading per chartable metric, keyed by the iOS `MetricKind` raw value (the non-obvious raws:
|
||
`oxygenSaturation`, `totalBodyWater`, `heartRateVariability`, `bodyMassIndex`, `walkingAsymmetryPercentage`,
|
||
`walkingDoubleSupportPercentage`, `environmentalAudioExposure`, `headphoneAudioExposure`,
|
||
`activeEnergyBurned`). Each entry carries `value`, `measuredAt` (ISO8601), and the canonical `unit`. Types
|
||
the user has never logged are omitted."
|
||
tiles:
|
||
type: object
|
||
properties:
|
||
summaries:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
lastSeenByType:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
mood:
|
||
type: object
|
||
properties:
|
||
summary:
|
||
anyOf:
|
||
- type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
- type: "null"
|
||
entries:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
date:
|
||
type: string
|
||
score:
|
||
type: number
|
||
samples:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- date
|
||
- score
|
||
- samples
|
||
additionalProperties: false
|
||
required:
|
||
- summary
|
||
- entries
|
||
additionalProperties: false
|
||
required:
|
||
- summaries
|
||
- lastSeenByType
|
||
- mood
|
||
additionalProperties: false
|
||
extras:
|
||
anyOf:
|
||
- type: object
|
||
properties:
|
||
bpInTargetPct:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
bpInTargetPct7d:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
bpInTargetPct30d:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
bpInTargetPctAllTime:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
bpInTargetPctPriorMonth:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
bpInTargetPctPriorYear:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
glucoseByContext:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
required:
|
||
- bpInTargetPct
|
||
- bpInTargetPct7d
|
||
- bpInTargetPct30d
|
||
- bpInTargetPctAllTime
|
||
- bpInTargetPctPriorMonth
|
||
- bpInTargetPctPriorYear
|
||
- glucoseByContext
|
||
additionalProperties: false
|
||
- type: "null"
|
||
briefing:
|
||
anyOf:
|
||
- type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
- type: "null"
|
||
briefingState:
|
||
type: string
|
||
enum:
|
||
- ready
|
||
- preparing
|
||
- disabled
|
||
briefingUpdatedAt:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
generatedAt:
|
||
type: string
|
||
required:
|
||
- user
|
||
- layout
|
||
- layoutCatalogue
|
||
- metricStates
|
||
- tiles
|
||
- extras
|
||
- briefing
|
||
- briefingState
|
||
- briefingUpdatedAt
|
||
- generatedAt
|
||
additionalProperties: false
|
||
description: Unified above-the-fold dashboard payload. `tiles` always arrives (slim summaries + mood + resolved widget
|
||
layout); `extras` (BD-in-target + per-context glucose) is null on a rollup-coverage miss so the strip never
|
||
waits on the slowest read. `briefing` is lifted read-only from the pre-generated insight cache — never generated
|
||
synchronously — and reports `ready` / `preparing` / `disabled` via `briefingState`. `layoutCatalogue` (full
|
||
27-id widget catalogue) and `metricStates` (latest reading per metric, keyed by iOS `MetricKind` raw value) are
|
||
additive cold-launch seeds for the native client; both derive in-process from data already fetched, adding no DB
|
||
round-trip.
|
||
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.
|
||
InsightsPregenerateResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightsPregenerateResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
InsightsPregenerateResponse:
|
||
type: object
|
||
properties:
|
||
queued:
|
||
type: boolean
|
||
description: True when the full warm was accepted and enqueued.
|
||
locale:
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: The locale the assessments are being warmed in.
|
||
required:
|
||
- queued
|
||
- locale
|
||
additionalProperties: false
|
||
description: Acknowledgement that a full assessment warm was enqueued for the calling user. The generation runs out of
|
||
band; the text lands in the read-only status routes.
|
||
AnalyticsRangeResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/AnalyticsRangeResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
AnalyticsRangeResponse:
|
||
type: object
|
||
properties:
|
||
range:
|
||
type: string
|
||
enum:
|
||
- 7d
|
||
- 30d
|
||
- 90d
|
||
- 1y
|
||
description: The range that was read (echoes the request).
|
||
windowDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Trailing-window length in days for the chosen range.
|
||
granularity:
|
||
type: string
|
||
description: Rollup granularity the read resolved against (`DAY` / `WEEK` / `MONTH` / `YEAR`, or `none` on a coverage
|
||
miss).
|
||
current:
|
||
description: Aggregate over the current window `[now-N, now)`.
|
||
$ref: "#/components/schemas/AnalyticsWindowAggregate"
|
||
previous:
|
||
description: Aggregate over the previous comparable window `[now-2N, now-N)`.
|
||
$ref: "#/components/schemas/AnalyticsWindowAggregate"
|
||
delta:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: "`current.mean - previous.mean`; null when either window has no data (never a misleading 0)."
|
||
deltaPct:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: "`delta / previous.mean` as a fraction (0.03 = +3 %); null when the prior window has no / zero mean (no
|
||
divide-by-zero). The client shows 'no prior-period data' in that case."
|
||
required:
|
||
- range
|
||
- windowDays
|
||
- granularity
|
||
- current
|
||
- previous
|
||
- delta
|
||
- deltaPct
|
||
additionalProperties: false
|
||
description: Single-metric period-over-period aggregate. Reads the current and previous comparable windows from the WMY
|
||
rollup tier and composes a count-weighted-mean delta. `count/min/max/mean/sum` are linearly composable across
|
||
buckets; SD/slope/r² are intentionally excluded (not composable).
|
||
AnalyticsWindowAggregate:
|
||
type: object
|
||
properties:
|
||
count:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Reading count composed across buckets.
|
||
min:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: Window minimum; null when empty.
|
||
max:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: Window maximum; null when empty.
|
||
mean:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: Count-weighted mean across buckets; null when empty.
|
||
sum:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: Cumulative total for cumulative metrics (steps, energy, distance); null when no bucket carries a sum.
|
||
required:
|
||
- count
|
||
- min
|
||
- max
|
||
- mean
|
||
- sum
|
||
additionalProperties: false
|
||
BloodPressureStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
InsightStatusResponse:
|
||
type: object
|
||
properties:
|
||
hasProvider:
|
||
type: boolean
|
||
description: False when the user has no usable AI provider — `text` then carries the generic no-key guidance.
|
||
text:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: The assessment narrative (plain text, rendered as React text children). Null while a first generation is
|
||
preparing.
|
||
cached:
|
||
type: boolean
|
||
description: True when `text` is served from cache (incl. last-good).
|
||
updatedAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: When the served assessment was generated; null when none.
|
||
preparing:
|
||
description: True when a first assessment is being generated out of band and no prior text exists yet — the client polls
|
||
until it lands.
|
||
type: boolean
|
||
revalidating:
|
||
description: True when `text` is served from last-good cache (stale-while-revalidate) while a fresh generation is in
|
||
flight. The client keeps polling on `preparing || revalidating` (bounded) so the open card upgrades to the
|
||
warmed assessment without a remount.
|
||
type: boolean
|
||
required:
|
||
- hasProvider
|
||
- text
|
||
- cached
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: "Specialised per-metric assessment envelope (blood-pressure, pulse, weight, bmi, mood). Identical shape to
|
||
the generic metric-status card so the `InsightStatusCard` consumes it unchanged. Read-only +
|
||
stale-while-revalidate: a cache miss warms a generation out of band and serves the last-good text meanwhile."
|
||
PulseStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
WeightStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
BmiStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MoodStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationComplianceStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationComplianceStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationComplianceStatusResponse:
|
||
type: object
|
||
properties:
|
||
hasProvider:
|
||
type: boolean
|
||
description: False when the user has no usable AI provider — `summary` then carries the generic no-key guidance.
|
||
summary:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: The overall compliance narrative (plain text). Null while a first generation is preparing.
|
||
medications:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationComplianceStatusItem"
|
||
description: Per-medication compliance notes. Empty while preparing or when no medication qualifies.
|
||
cached:
|
||
type: boolean
|
||
description: True when the envelope is served from cache (incl. last-good).
|
||
updatedAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: When the served assessment was generated; null when none.
|
||
preparing:
|
||
description: True when a first assessment is being generated out of band and no prior summary exists yet — the client
|
||
polls until it lands.
|
||
type: boolean
|
||
revalidating:
|
||
description: True when the envelope is served from last-good cache (stale-while-revalidate) while a fresh generation is
|
||
in flight. The client keeps polling on `preparing || revalidating` (bounded).
|
||
type: boolean
|
||
required:
|
||
- hasProvider
|
||
- summary
|
||
- medications
|
||
- cached
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: Medication-compliance assessment envelope. Unlike the other six specialised cards it carries a `summary`
|
||
plus a per-medication `text` array rather than a single `text` field. Read-only + stale-while-revalidate.
|
||
MedicationComplianceStatusItem:
|
||
type: object
|
||
properties:
|
||
medicationId:
|
||
type: string
|
||
description: The medication this note belongs to.
|
||
text:
|
||
type: string
|
||
description: Per-medication compliance note (plain text).
|
||
required:
|
||
- medicationId
|
||
- text
|
||
additionalProperties: false
|
||
MetricStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MetricStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MetricStatusResponse:
|
||
type: object
|
||
properties:
|
||
hasProvider:
|
||
type: boolean
|
||
description: False when the user has no usable AI provider — `text` then carries the generic no-key guidance.
|
||
text:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: The assessment narrative (plain text, rendered as React text children). Null while a first generation is
|
||
preparing, or when the metric has insufficient data.
|
||
cached:
|
||
type: boolean
|
||
description: True when `text` is served from cache (incl. last-good).
|
||
updatedAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: When the served assessment was generated; null when none.
|
||
preparing:
|
||
description: True when a first assessment is being generated out of band and no prior text exists yet — the client polls
|
||
until it lands.
|
||
type: boolean
|
||
revalidating:
|
||
description: True when `text` is served from last-good cache (stale-while-revalidate) while a fresh generation is in
|
||
flight. The payload is otherwise terminal; the client keeps polling on `preparing || revalidating` (bounded)
|
||
so the open card upgrades to the warmed assessment without a remount.
|
||
type: boolean
|
||
insufficient:
|
||
description: True when the metric has no readings; no assessment is generated (no LLM call). The card shows its
|
||
insufficient-data state.
|
||
type: boolean
|
||
required:
|
||
- hasProvider
|
||
- text
|
||
- cached
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: "Generic per-metric assessment envelope. Identical shape to the seven specialised `*-status` cards so the
|
||
`InsightStatusCard` consumes it unchanged. Read-only + stale-while-revalidate: a cache miss warms a generation
|
||
out of band and serves the last-good text meanwhile."
|
||
DerivedMetricResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/DerivedMetricResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
DerivedMetricResponse:
|
||
type: object
|
||
properties:
|
||
metric:
|
||
type: string
|
||
enum:
|
||
- VITALS_BASELINE
|
||
- FITNESS_AGE
|
||
- VASCULAR_AGE_DELTA
|
||
- SLEEP_SCORE
|
||
- READINESS
|
||
- COINCIDENT_DEVIATION
|
||
- HRV_BALANCE
|
||
- BMI
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
description: Echoes the requested derived-metric id (tags the union).
|
||
status:
|
||
type: string
|
||
enum:
|
||
- ok
|
||
- insufficient
|
||
description: "'ok' carries `value` + `confidence`; 'insufficient' carries `reason` and no value, but still carries
|
||
`coverage` + `provenance` so the surface renders the same gating UI."
|
||
value:
|
||
anyOf:
|
||
- type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
- type: "null"
|
||
description: Metric-specific value object when status is 'ok' (e.g. { type, center, low, high, spread, sampleDays, k }
|
||
for VITALS_BASELINE); null when 'insufficient'.
|
||
coverage:
|
||
$ref: "#/components/schemas/DerivedCoverage"
|
||
confidence:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/DerivedConfidence"
|
||
- type: "null"
|
||
description: Present when status is 'ok'; null when 'insufficient'.
|
||
provenance:
|
||
$ref: "#/components/schemas/DerivedProvenance"
|
||
reason:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Why the value could not be produced; null when status is 'ok'.
|
||
required:
|
||
- metric
|
||
- status
|
||
- value
|
||
- coverage
|
||
- confidence
|
||
- provenance
|
||
- reason
|
||
additionalProperties: false
|
||
description: Flat `Derived<T>` envelope for one derived wellness metric. Pure compute over the rollup tier (no LLM, no
|
||
narrative). iOS decodes one stable shape and combines values across metrics; coverage/confidence/provenance let
|
||
it render the same honesty chips.
|
||
DerivedCoverage:
|
||
type: object
|
||
properties:
|
||
requiredInputs:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Inputs the metric wants (its full input set).
|
||
presentInputs:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Inputs actually present in the user's data.
|
||
historyDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Distinct days of history backing the value (the gating floor).
|
||
missing:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Named inputs still missing — drives the 'track N more' nudge.
|
||
required:
|
||
- requiredInputs
|
||
- presentInputs
|
||
- historyDays
|
||
- missing
|
||
additionalProperties: false
|
||
DerivedConfidence:
|
||
type: object
|
||
properties:
|
||
score:
|
||
type: number
|
||
description: 0..100 confidence; feeds the shared coverage meter unchanged.
|
||
band:
|
||
type: string
|
||
enum:
|
||
- high
|
||
- medium
|
||
- low
|
||
- draft
|
||
description: Confidence band the meter renders.
|
||
required:
|
||
- score
|
||
- band
|
||
additionalProperties: false
|
||
DerivedProvenance:
|
||
type: object
|
||
properties:
|
||
inputs:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Named inputs that actually backed the value.
|
||
source:
|
||
type: string
|
||
enum:
|
||
- DAY
|
||
- WEEK
|
||
- MONTH
|
||
- YEAR
|
||
- live
|
||
- none
|
||
description: Granularity the dominant read resolved against. 'live' = a coverage-miss live-SQL fallback; 'none' = no
|
||
data backed the value.
|
||
windowDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Trailing window the value summarises, in days.
|
||
computedAt:
|
||
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)))$
|
||
description: Compute time (for cache-staleness + the 'as of' chip).
|
||
required:
|
||
- inputs
|
||
- source
|
||
- windowDays
|
||
- computedAt
|
||
additionalProperties: false
|
||
CorrelationDiscoveryResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/CorrelationDiscoveryResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CorrelationDiscoveryResponse:
|
||
type: object
|
||
properties:
|
||
discovered:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/DiscoveredCorrelation"
|
||
description: Pairs surviving n ≥ 20, p < 0.05, AND the BH-FDR control.
|
||
pairsTested:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Behaviour × outcome pairs assessed (for the honest footer).
|
||
fdrQ:
|
||
type: number
|
||
description: The FDR target the surface used.
|
||
minPairs:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Minimum paired-day count enforced per pair.
|
||
required:
|
||
- discovered
|
||
- pairsTested
|
||
- fdrQ
|
||
- minPairs
|
||
additionalProperties: false
|
||
description: v1.10.0 — FDR-controlled correlation discovery over a curated behaviour × outcome matrix, lagged behaviour
|
||
→ next-day outcome. Only statistically-defensible pairs surface; descriptive, never causal.
|
||
DiscoveredCorrelation:
|
||
type: object
|
||
properties:
|
||
behaviour:
|
||
type: string
|
||
description: Behaviour channel (lag source), e.g. TIME_IN_DAYLIGHT, MOOD.
|
||
outcome:
|
||
type: string
|
||
description: Outcome channel (lag target), e.g. SLEEP_DURATION, HEART_RATE_VARIABILITY.
|
||
n:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Paired-day count after the day+1 lag join (≥ 20).
|
||
r:
|
||
type: number
|
||
description: Pearson r over the lag-joined daily series.
|
||
pValue:
|
||
type: number
|
||
description: Two-sided exact Student-t p-value (< 0.05).
|
||
qValue:
|
||
type: number
|
||
description: Benjamini-Hochberg FDR-adjusted q-value (≤ the surface threshold).
|
||
interpretation:
|
||
type: string
|
||
description: Conservative, descriptive interpretation — never causal.
|
||
lagDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Lag in days applied (1).
|
||
required:
|
||
- behaviour
|
||
- outcome
|
||
- n
|
||
- r
|
||
- pValue
|
||
- qValue
|
||
- interpretation
|
||
- lagDays
|
||
additionalProperties: false
|
||
MedicationScheduleOutput:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
medicationId:
|
||
type: string
|
||
windowStart:
|
||
type: string
|
||
description: Legacy single-time-of-intake (HH:mm, user local). Preserved for backwards compatibility; the new
|
||
`timesOfDay` array supersedes it.
|
||
windowEnd:
|
||
type: string
|
||
description: Legacy reminder-window upper bound (HH:mm). Used to derive the late-classification grace span when
|
||
`reminderGraceMinutes` is null.
|
||
label:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
dose:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Per-schedule dose override. NULL means the schedule inherits `Medication.dose`.
|
||
daysOfWeek:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Legacy persisted recurrence encoding (`null` | `1,3,5` | `i2;1,3,5`). v1.5 readers consult `rrule` first;
|
||
the field is kept for pre-v1.5 rows. v1.6.0 drops the column.
|
||
timesOfDay:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: v1.5 first-class points-in-time the dose is taken (HH:mm, user local). Backfilled to `[windowStart]` for
|
||
every pre-v1.5 row.
|
||
reminderGraceMinutes:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Reminder grace window in minutes. NULL falls back to the legacy `windowEnd - windowStart` span.
|
||
rrule:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: RFC 5545 RRULE string (subset). Used for calendar-anchored cadences. **Mutually exclusive with
|
||
`rollingIntervalDays`** — exactly one of the two is non-null on any v1.5+ schedule (or both are null on
|
||
legacy rows that haven't been touched since the migration).
|
||
rollingIntervalDays:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Flexible-rolling interval in days, counted forward from the latest `MedicationIntakeEvent.takenAt`.
|
||
**Mutually exclusive with `rrule`.**
|
||
scheduleType:
|
||
type: string
|
||
enum:
|
||
- SCHEDULED
|
||
- PRN
|
||
- CYCLIC
|
||
description: v1.7.0 schedule-type discriminator. SCHEDULED = rrule / rolling / legacy cadence. PRN = as-needed (never
|
||
projected, reminded, or counted in compliance expected; still loggable via the intake route). CYCLIC = N
|
||
weeks on / M weeks off, gating whichever inner cadence the rrule / legacy fields describe.
|
||
cyclicOnWeeks:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: v1.7.0 cyclic "on" weeks. Only meaningful when `scheduleType` is CYCLIC; null otherwise.
|
||
cyclicOffWeeks:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: v1.7.0 cyclic "off" weeks. Only meaningful when `scheduleType` is CYCLIC; null otherwise.
|
||
required:
|
||
- id
|
||
- medicationId
|
||
- windowStart
|
||
- windowEnd
|
||
- label
|
||
- dose
|
||
- daysOfWeek
|
||
- timesOfDay
|
||
- reminderGraceMinutes
|
||
- rrule
|
||
- rollingIntervalDays
|
||
- scheduleType
|
||
- cyclicOnWeeks
|
||
- cyclicOffWeeks
|
||
additionalProperties: false
|
||
description: Schedule entry attached to a medication. v1.5 promotes `timesOfDay` to first-class and introduces `rrule`
|
||
(calendar-anchored cadences) and `rollingIntervalDays` (flexible-rolling cadences). The two recurrence
|
||
primitives are mutually exclusive — enforced by the Zod refine on writes, the route layer, and a DB CHECK
|
||
constraint (`medication_schedules_rrule_xor_rolling`). v1.7.0 adds `scheduleType` (SCHEDULED / PRN / CYCLIC) and
|
||
the cyclic on/off-week fields.
|
||
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.
|
||
InsightsLayoutBodyOutput:
|
||
type: object
|
||
properties:
|
||
version:
|
||
type: number
|
||
const: 1
|
||
tiles:
|
||
minItems: 1
|
||
maxItems: 50
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
enum:
|
||
- overview
|
||
- blood-pressure
|
||
- pulse
|
||
- oxygen
|
||
- body-temperature
|
||
- respiratory-rate
|
||
- weight
|
||
- bmi
|
||
- body-water
|
||
- bone-mass
|
||
- fat-free-mass
|
||
- fat-mass
|
||
- muscle-mass
|
||
- visceral-fat
|
||
- lean-body-mass
|
||
- active-energy
|
||
- workouts
|
||
- flights-climbed
|
||
- walking-distance
|
||
- walking-steadiness
|
||
- walking-heart-rate
|
||
- walking-asymmetry
|
||
- double-support-time
|
||
- step-length
|
||
- walking-speed
|
||
- cardio-fitness
|
||
- falls
|
||
- six-minute-walk
|
||
- stair-ascent-speed
|
||
- stair-descent-speed
|
||
- sleep
|
||
- breathing-disturbances
|
||
- resting-pulse
|
||
- hrv
|
||
- pulse-wave-velocity
|
||
- vascular-age
|
||
- cardio-recovery
|
||
- environmental-audio
|
||
- headphone-audio
|
||
- audio-events
|
||
- daylight
|
||
- blood-glucose
|
||
- skin-temperature
|
||
- wrist-temperature
|
||
- mood
|
||
- medications
|
||
- blutdruck
|
||
- puls
|
||
- sauerstoff
|
||
- koerpertemperatur
|
||
- gewicht
|
||
- aktive-energie
|
||
- schlaf
|
||
- ruhepuls
|
||
- stimmung
|
||
- medikamente
|
||
visible:
|
||
type: boolean
|
||
order:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 99
|
||
required:
|
||
- id
|
||
- visible
|
||
- order
|
||
additionalProperties: false
|
||
required:
|
||
- version
|
||
- tiles
|
||
additionalProperties: false
|
||
description: "Per-user Insights tile layout: an ordered list of tiles with a visibility flag. `version` is the layout
|
||
schema version. Tile ids are a closed enum: the canonical ids are English (matching the routed
|
||
`/insights/<slug>` sub-pages). The legacy German ids (blutdruck, puls, sauerstoff, koerpertemperatur, gewicht,
|
||
aktive-energie, schlaf, ruhepuls, stimmung, medikamente) remain accepted on input for backward compatibility and
|
||
are normalised to their English equivalents before persisting; GET responses always carry the canonical English
|
||
ids. The legacy ids are deprecated and will be removed in a future major version."
|
||
Medication:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
name:
|
||
type: string
|
||
dose:
|
||
type: string
|
||
treatmentClass:
|
||
$ref: "#/components/schemas/MedicationTreatmentClass"
|
||
dosesPerUnit:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Doses per pen / vial for inventory tracking. NULL = inventory tracking off.
|
||
active:
|
||
type: boolean
|
||
notificationsEnabled:
|
||
type: boolean
|
||
liveActivityEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS Live Activity opt-in for this medication's reminders. Default false. The iOS client owns the
|
||
ActivityKit lifecycle; the server only stores + echoes the flag.
|
||
criticalAlarmEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS 26 AlarmKit critical-reminder opt-in. Default false. Critical alarms bypass the device mute
|
||
switch / Focus; the server stores the preference only.
|
||
atcCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional WHO ATC classification code (active-substance class, e.g. `A10BX10`).
|
||
User/clinician-asserted; never machine-guessed. Emitted on the FHIR `medicationCodeableConcept` under
|
||
`http://www.whocc.no/atc`. NULL = no code captured.
|
||
rxNormCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional RxNorm RxCUI (numeric, US identifier, e.g. `2601723`). Secondary FHIR coding under
|
||
`http://www.nlm.nih.gov/research/umls/rxnorm`, alongside any ATC code. NULL = no code captured.
|
||
pausedAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
snoozedUntil:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
nextDueAt:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: v1.7.0 server-computed next due instant across all the medication's schedules (earliest
|
||
`nextOccurrenceAfter`). Read-only — computed, not stored. NULL when no schedule has an upcoming slot
|
||
(paused, one-shot in the past, `endsOn` crossed, every schedule PRN). The list GET is cached 60 s, so a 60 s
|
||
staleness is accepted.
|
||
startsOn:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: v1.5 course start (ISO date). Anchors RRULE BYDAY / BYMONTHDAY patterns and the rolling-interval
|
||
countdown's first window. NULL means active from creation.
|
||
endsOn:
|
||
anyOf:
|
||
- 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)))$
|
||
- type: "null"
|
||
description: v1.5 course end (ISO date). NULL means chronic. Equals `startsOn` when `oneShot` is true.
|
||
oneShot:
|
||
type: boolean
|
||
description: v1.5 single-administration flag. When true the medication has at most one schedule (no `rrule` /
|
||
`rollingIntervalDays`), and `active` auto-flips to false once the dose is logged.
|
||
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)))$
|
||
updatedAt:
|
||
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)))$
|
||
schedules:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleOutput"
|
||
required:
|
||
- id
|
||
- name
|
||
- dose
|
||
- treatmentClass
|
||
- dosesPerUnit
|
||
- active
|
||
- notificationsEnabled
|
||
- liveActivityEnabled
|
||
- criticalAlarmEnabled
|
||
- atcCode
|
||
- rxNormCode
|
||
- pausedAt
|
||
- snoozedUntil
|
||
- nextDueAt
|
||
- startsOn
|
||
- endsOn
|
||
- oneShot
|
||
- createdAt
|
||
- updatedAt
|
||
- schedules
|
||
additionalProperties: false
|
||
description: Server-shaped medication row returned by GET / POST / PUT endpoints. Carries the v1.5 course-window fields
|
||
(`startsOn`, `endsOn`, `oneShot`) at the medication level and the per-schedule cadence fields on the nested
|
||
`schedules` array.
|
||
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.
|