mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
The medication card reads the schedule engine's next-due (fixes interval/one-time schedules that never advanced after a dose), logging a dose confirms itself, Insights status polling is bounded, each Insights category explains itself inline, medication history shows each dose's origin, and an idempotent migration adds the app_settings columns that were only ever applied ad-hoc.
6113 lines
210 KiB
YAML
6113 lines
210 KiB
YAML
openapi: 3.1.0
|
||
info:
|
||
title: HealthLog API
|
||
version: 1.8.4
|
||
description: >-
|
||
Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest.
|
||
|
||
|
||
## Date-time contract
|
||
|
||
|
||
Every field marked `format: date-time` is an ISO-8601 instant. The schema pattern accepts both `Z` (UTC) and
|
||
`±HH:MM` (offset) suffixes; clients must accept either. Server responses since v1.4.25 emit timestamps with the
|
||
requesting user's display-timezone offset when a user context exists (exports, doctor-report PDF).
|
||
Background-job-authored payloads (backups, admin tools) emit `Z`. Native iOS clients should always parse via a
|
||
Foundation ISO-8601 formatter with the `withInternetDateTime` + `withTimeZone` options enabled.
|
||
license:
|
||
name: AGPL-3.0
|
||
url: https://github.com/MBombeck/HealthLog/blob/main/LICENSE
|
||
contact:
|
||
name: HealthLog
|
||
url: https://healthlog.dev
|
||
servers:
|
||
- url: https://healthlog.bombeck.io
|
||
description: Production
|
||
- url: http://localhost:3000
|
||
description: Local dev
|
||
tags:
|
||
- name: Auth
|
||
- name: Measurements
|
||
- name: Medications
|
||
- name: Mood
|
||
- name: Insights
|
||
- name: 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
|
||
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
|
||
- 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
|
||
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
|
||
glucoseContext:
|
||
type: string
|
||
enum:
|
||
- FASTING
|
||
- POSTPRANDIAL
|
||
- RANDOM
|
||
- BEDTIME
|
||
deviceType:
|
||
anyOf:
|
||
- type: string
|
||
minLength: 1
|
||
maxLength: 32
|
||
- type: "null"
|
||
required:
|
||
- type
|
||
- value
|
||
- measuredAt
|
||
responses:
|
||
"201":
|
||
description: Created.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CreateMeasurementResponse"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/measurements/batch:
|
||
post:
|
||
tags:
|
||
- Measurements
|
||
summary: Apple Health batch ingest
|
||
description: Up to 500 HealthKit entries per call. Idempotent via the `Idempotency-Key` header (replay window 24h).
|
||
Per-entry status lets the iOS client advance its sync cursor accurately. v1.4.25 W8c adds an optional
|
||
`deviceType` per entry — feed it from `HKDevice.model` so the cross-source canonical picker can break
|
||
Apple-Watch-vs-iPhone ties; null/absent stays backward-compatible with v1.4.23 clients.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/AppleHealthBatchRequest"
|
||
responses:
|
||
"200":
|
||
description: Batch processed.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/BatchMeasurementsResponse"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/measurements/by-external-ids:
|
||
delete:
|
||
tags:
|
||
- Measurements
|
||
summary: Delete measurements by external ID (iOS deletion-sync)
|
||
description: Removes the user's measurement rows whose externalId is in the request list. Rows owned by another user are
|
||
silently skipped (cross-user 404 guard). Up to 500 externalIds per call.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MeasurementsDeleteByExternalIdsRequest"
|
||
responses:
|
||
"200":
|
||
description: Delete batch processed.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MeasurementsDeleteByExternalIdsResponse"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/workouts:
|
||
get:
|
||
tags:
|
||
- Measurements
|
||
summary: List workouts (v1.4.32)
|
||
description: "Paginated workout list with cross-source canonical-row dedup. The picker reads the per-user
|
||
source-priority ladder and buckets by `(startedAt 5 min slot, sportType)` — twin workouts (Apple Watch +
|
||
Withings ScanWatch) collapse to a single canonical row per cluster. Field names mirror the iOS handoff contract:
|
||
`distanceM`, `activeEnergyKcal`, `avgHr`, `maxHr`."
|
||
parameters:
|
||
- in: query
|
||
name: limit
|
||
schema:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 200
|
||
- in: query
|
||
name: offset
|
||
schema:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
- in: query
|
||
name: since
|
||
schema:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- in: query
|
||
name: until
|
||
schema:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- in: query
|
||
name: sportType
|
||
schema:
|
||
type: string
|
||
responses:
|
||
"200":
|
||
description: Canonical workout page.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ListWorkoutsResponse"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/workouts/{id}:
|
||
get:
|
||
tags:
|
||
- Measurements
|
||
summary: Workout detail (v1.4.32)
|
||
description: Single-workout envelope. Owns the optional `WorkoutRoute` GeoJSON geometry + `canonicalId` pointer that
|
||
resolves to the cluster winner so deep-links into non-canonical twin rows can redirect cleanly. Cross-user rows
|
||
surface as 404 (existence channel sealed).
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"200":
|
||
description: Workout detail.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/GetWorkoutResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Workout not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/workouts/batch:
|
||
post:
|
||
tags:
|
||
- Measurements
|
||
summary: Typed workout batch ingest (v1.4.25 W16b)
|
||
description: Server-side ingest endpoint for HKWorkout records (iOS) and Withings activity rows (server-to-server). Up
|
||
to 100 workouts per call; nested route geometry (GeoJSON LineString) is capped at 20 000 points and stored in a
|
||
1:1 `WorkoutRoute` row keyed by `workoutId`. Idempotent via the `Idempotency-Key` header (replay window 24h).
|
||
Per-entry status (`inserted | duplicate | skipped`) lets the iOS sync cursor checkpoint accurately. The request
|
||
body ceiling is 5 MB enforced at the HTTP layer — clients above the ceiling receive a 413 with
|
||
`workout.batch.payload_too_large`.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
workouts:
|
||
minItems: 1
|
||
maxItems: 100
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
sportType:
|
||
type: string
|
||
enum:
|
||
- walking
|
||
- running
|
||
- cycling
|
||
- hiking
|
||
- swimming
|
||
- rowing
|
||
- elliptical
|
||
- stairClimber
|
||
- yoga
|
||
- mindAndBody
|
||
- strength
|
||
- hiit
|
||
- dance
|
||
- golf
|
||
- tennis
|
||
- basketball
|
||
- soccer
|
||
- crossTraining
|
||
- mixedCardio
|
||
- other
|
||
startedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
endedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
totalEnergyKcal:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 50000
|
||
totalDistanceM:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 1000000
|
||
avgHeartRate:
|
||
type: integer
|
||
minimum: 20
|
||
maximum: 300
|
||
maxHeartRate:
|
||
type: integer
|
||
minimum: 20
|
||
maximum: 300
|
||
minHeartRate:
|
||
type: integer
|
||
minimum: 20
|
||
maximum: 300
|
||
stepCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 200000
|
||
elevationM:
|
||
type: number
|
||
minimum: -500
|
||
maximum: 10000
|
||
pauseDurationSec:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 86400
|
||
source:
|
||
default: MANUAL
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
externalId:
|
||
type: string
|
||
maxLength: 128
|
||
externalSourceVersion:
|
||
type: string
|
||
maxLength: 64
|
||
metadata:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
route:
|
||
type: object
|
||
properties:
|
||
geometry:
|
||
type: object
|
||
properties:
|
||
type:
|
||
type: string
|
||
const: LineString
|
||
coordinates:
|
||
minItems: 2
|
||
maxItems: 20000
|
||
type: array
|
||
items:
|
||
type: array
|
||
prefixItems:
|
||
- type: number
|
||
minimum: -180
|
||
maximum: 180
|
||
- type: number
|
||
minimum: -90
|
||
maximum: 90
|
||
items:
|
||
type: number
|
||
required:
|
||
- type
|
||
- coordinates
|
||
sampleTimestamps:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
t:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
speedMs:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 50
|
||
hr:
|
||
type: integer
|
||
minimum: 20
|
||
maximum: 300
|
||
required:
|
||
- t
|
||
required:
|
||
- geometry
|
||
required:
|
||
- sportType
|
||
- startedAt
|
||
- endedAt
|
||
required:
|
||
- workouts
|
||
responses:
|
||
"200":
|
||
description: Batch processed.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/BatchWorkoutsResponse"
|
||
"400":
|
||
description: Batch validation failed (over-cap, schema reject, or oversized route).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"401": *a1
|
||
"413":
|
||
description: Request body exceeds the 5 MB ceiling.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/devices:
|
||
post:
|
||
tags:
|
||
- Devices
|
||
summary: Register native device + APNs token
|
||
description: Idempotent upsert by `token`. APNs token + environment are paired — supplying one without the other returns
|
||
422. Cross-user re-registration of either identifier returns 409.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/DeviceRegisterRequest"
|
||
responses:
|
||
"201":
|
||
description: Device registered or refreshed.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/DeviceRegisterResponse"
|
||
"401": *a1
|
||
"409":
|
||
description: Device or APNs token already registered to another user.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/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/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
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
metricPriority:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
deviceTypePriority:
|
||
type: object
|
||
properties:
|
||
default:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
additionalProperties:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
responses:
|
||
"200":
|
||
description: Saved priority (defaulted) echoed back.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/PutSourcePriorityResponse"
|
||
"401": *a1
|
||
"422": *a2
|
||
"429": *a3
|
||
/api/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/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
|
||
components:
|
||
schemas:
|
||
PasskeyLoginVerifyRequest:
|
||
type: object
|
||
properties:
|
||
challengeId:
|
||
type: string
|
||
minLength: 1
|
||
description: Server-issued WebAuthn challenge id.
|
||
credential:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
rawId:
|
||
type: string
|
||
type:
|
||
type: string
|
||
const: public-key
|
||
response:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
authenticatorAttachment:
|
||
type: string
|
||
enum:
|
||
- platform
|
||
- cross-platform
|
||
clientExtensionResults:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
required:
|
||
- id
|
||
- rawId
|
||
- type
|
||
- response
|
||
description: SimpleWebAuthn-style assertion response payload.
|
||
required:
|
||
- challengeId
|
||
- credential
|
||
description: Passkey assertion verification. Same native-client token issuance as the password path.
|
||
RefreshTokenRequest:
|
||
type: object
|
||
properties:
|
||
refreshToken:
|
||
type: string
|
||
minLength: 1
|
||
description: Caller-presented refresh token (`hlr_<64hex>`).
|
||
revoke:
|
||
description: When true, revoke the supplied token instead of rotating.
|
||
type: boolean
|
||
required:
|
||
- refreshToken
|
||
description: Exchange a one-time-use refresh token for a fresh access + refresh pair.
|
||
AppleHealthBatchRequest:
|
||
type: object
|
||
properties:
|
||
entries:
|
||
minItems: 1
|
||
maxItems: 500
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/AppleHealthBatchEntry"
|
||
required:
|
||
- entries
|
||
description: Apple Health batch ingest. ≤500 entries per call; idempotent via `Idempotency-Key`.
|
||
AppleHealthBatchEntry:
|
||
type: object
|
||
properties:
|
||
hkIdentifier:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
description: HealthKit identifier (e.g. `HKQuantityTypeIdentifierBodyMass`).
|
||
value:
|
||
type: number
|
||
unit:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 60
|
||
startDate:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
endDate:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
sleepStage:
|
||
description: HKCategoryValueSleepAnalysis codepoint; only for sleep samples.
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 20
|
||
externalId:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
description: HKSample.uuid string — the dedup key.
|
||
externalSourceVersion:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
deviceType:
|
||
description: Device class mapped from `HKDevice.model`. Used by the analytics aggregator to break ties when the same
|
||
source contributed multiple devices for the same day. Omit (or send null) on legacy clients — the server
|
||
treats it as `unknown` and the picker falls through.
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
- type: "null"
|
||
required:
|
||
- hkIdentifier
|
||
- value
|
||
- unit
|
||
- startDate
|
||
- endDate
|
||
- externalId
|
||
MeasurementsDeleteByExternalIdsRequest:
|
||
type: object
|
||
properties:
|
||
externalIds:
|
||
minItems: 0
|
||
maxItems: 500
|
||
type: array
|
||
items:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
required:
|
||
- externalIds
|
||
description: iOS deletion-sync. Up to 500 externalIds per call; matching rows owned by other users are silently skipped
|
||
(cross-user 404 guard).
|
||
DeviceRegisterRequest:
|
||
type: object
|
||
properties:
|
||
token:
|
||
type: string
|
||
minLength: 8
|
||
maxLength: 512
|
||
pattern: ^[A-Za-z0-9+/=._:-]+$
|
||
description: Generic device identifier (legacy; APNs token below).
|
||
bundleId:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 128
|
||
locale:
|
||
type: string
|
||
minLength: 2
|
||
maxLength: 16
|
||
appVersion:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 32
|
||
model:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 64
|
||
apnsToken:
|
||
description: Hex-encoded APNs device token. Must be paired with `apnsEnvironment`.
|
||
type: string
|
||
minLength: 8
|
||
maxLength: 256
|
||
pattern: ^[A-Fa-f0-9]+$
|
||
apnsEnvironment:
|
||
description: Gateway the iOS client received `apnsToken` from. Server never auto-detects.
|
||
type: string
|
||
enum:
|
||
- sandbox
|
||
- production
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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
|
||
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.
|
||
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.
|
||
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
|
||
- weight
|
||
- bmi
|
||
- active-energy
|
||
- workouts
|
||
- sleep
|
||
- resting-pulse
|
||
- hrv
|
||
- 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.
|
||
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
|
||
value:
|
||
type: number
|
||
unit:
|
||
type: string
|
||
measuredAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
source:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
notes:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
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
|
||
value:
|
||
type: number
|
||
unit:
|
||
type: string
|
||
measuredAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
source:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
notes:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
required:
|
||
- id
|
||
- type
|
||
- value
|
||
- unit
|
||
- measuredAt
|
||
- source
|
||
additionalProperties: false
|
||
description: Server-shaped measurement row returned by GET endpoints.
|
||
CreateMeasurementResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MeasurementResource"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
BatchMeasurementsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/AppleHealthBatchResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
AppleHealthBatchResponse:
|
||
type: object
|
||
properties:
|
||
inserted:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
duplicate:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
skipped:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
entries:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/AppleHealthBatchEntryResult"
|
||
required:
|
||
- inserted
|
||
- duplicate
|
||
- skipped
|
||
- entries
|
||
additionalProperties: false
|
||
AppleHealthBatchEntryResult:
|
||
type: object
|
||
properties:
|
||
index:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
status:
|
||
type: string
|
||
enum:
|
||
- inserted
|
||
- duplicate
|
||
- skipped
|
||
reason:
|
||
type: string
|
||
required:
|
||
- index
|
||
- status
|
||
additionalProperties: false
|
||
MeasurementsDeleteByExternalIdsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MeasurementsDeleteByExternalIdsResult"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MeasurementsDeleteByExternalIdsResult:
|
||
type: object
|
||
properties:
|
||
deletedCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- deletedCount
|
||
additionalProperties: false
|
||
ListWorkoutsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/WorkoutListResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
WorkoutListResponse:
|
||
type: object
|
||
properties:
|
||
workouts:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/WorkoutListEntry"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
total:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
limit:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
offset:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
droppedDuplicates:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- total
|
||
- limit
|
||
- offset
|
||
- droppedDuplicates
|
||
additionalProperties: false
|
||
required:
|
||
- workouts
|
||
- meta
|
||
additionalProperties: false
|
||
WorkoutListEntry:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
sportType:
|
||
type: string
|
||
startedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
endedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
durationSec:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
distanceM:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
activeEnergyKcal:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
avgHr:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
maxHr:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
source:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
externalId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
required:
|
||
- id
|
||
- sportType
|
||
- startedAt
|
||
- endedAt
|
||
- durationSec
|
||
- distanceM
|
||
- activeEnergyKcal
|
||
- avgHr
|
||
- maxHr
|
||
- source
|
||
- externalId
|
||
additionalProperties: false
|
||
GetWorkoutResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/WorkoutDetailResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
WorkoutDetailResponse:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
sportType:
|
||
type: string
|
||
startedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
endedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
durationSec:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
distanceM:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
activeEnergyKcal:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
avgHr:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
maxHr:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
minHr:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
stepCount:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
elevationM:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
pauseDurationSec:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
source:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
externalId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
metadata:
|
||
anyOf:
|
||
- {}
|
||
- type: "null"
|
||
route:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/WorkoutRouteGeometry"
|
||
- type: "null"
|
||
canonicalId:
|
||
type: string
|
||
required:
|
||
- id
|
||
- sportType
|
||
- startedAt
|
||
- endedAt
|
||
- durationSec
|
||
- distanceM
|
||
- activeEnergyKcal
|
||
- avgHr
|
||
- maxHr
|
||
- minHr
|
||
- stepCount
|
||
- elevationM
|
||
- pauseDurationSec
|
||
- source
|
||
- externalId
|
||
- metadata
|
||
- route
|
||
- canonicalId
|
||
additionalProperties: false
|
||
WorkoutRouteGeometry:
|
||
type: object
|
||
properties:
|
||
geometry: {}
|
||
sampleTimestamps:
|
||
anyOf:
|
||
- type: array
|
||
items:
|
||
type: string
|
||
- type: "null"
|
||
required:
|
||
- geometry
|
||
- sampleTimestamps
|
||
additionalProperties: false
|
||
BatchWorkoutsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/WorkoutBatchResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
WorkoutBatchResponse:
|
||
type: object
|
||
properties:
|
||
processed:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
inserted:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
duplicates:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
skipped:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
index:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
reason:
|
||
type: string
|
||
required:
|
||
- index
|
||
- reason
|
||
additionalProperties: false
|
||
entries:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/WorkoutBatchEntryResult"
|
||
required:
|
||
- processed
|
||
- inserted
|
||
- duplicates
|
||
- skipped
|
||
- entries
|
||
additionalProperties: false
|
||
WorkoutBatchEntryResult:
|
||
type: object
|
||
properties:
|
||
index:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
status:
|
||
type: string
|
||
enum:
|
||
- inserted
|
||
- duplicate
|
||
- skipped
|
||
reason:
|
||
type: string
|
||
required:
|
||
- index
|
||
- status
|
||
additionalProperties: false
|
||
DeviceRegisterResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
required:
|
||
- id
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
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.
|
||
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
|
||
- 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.
|
||
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
|
||
- 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.
|
||
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
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
metricPriority:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
additionalProperties: false
|
||
deviceTypePriority:
|
||
type: object
|
||
properties:
|
||
default:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
additionalProperties:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
PutSourcePriorityResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
metricPriority:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
additionalProperties: false
|
||
deviceTypePriority:
|
||
type: object
|
||
properties:
|
||
default:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
additionalProperties:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
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
|
||
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.
|
||
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
|
||
- weight
|
||
- bmi
|
||
- active-energy
|
||
- workouts
|
||
- sleep
|
||
- resting-pulse
|
||
- hrv
|
||
- 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.
|
||
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
|
||
- 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.
|