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