mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
Adds three Zod refines that close gaps the audit surfaced: one-shot medications now require a startsOn, course windows refuse endsOn before startsOn, and rolling-cadence schedules cap timesOfDay at one entry. The wizard's reminders toggle is now part of the create payload so the user's choice actually ships. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4377 lines
146 KiB
YAML
4377 lines
146 KiB
YAML
openapi: 3.1.0
|
|
info:
|
|
title: HealthLog API
|
|
version: 1.5.2
|
|
description: >-
|
|
Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest.
|
|
|
|
|
|
## Date-time contract
|
|
|
|
|
|
Every field marked `format: date-time` is an ISO-8601 instant. The schema pattern accepts both `Z` (UTC) and
|
|
`±HH:MM` (offset) suffixes; clients must accept either. Server responses since v1.4.25 emit timestamps with the
|
|
requesting user's display-timezone offset when a user context exists (exports, doctor-report PDF).
|
|
Background-job-authored payloads (backups, admin tools) emit `Z`. Native iOS clients should always parse via a
|
|
Foundation ISO-8601 formatter with the `withInternetDateTime` + `withTimeZone` options enabled.
|
|
license:
|
|
name: AGPL-3.0
|
|
url: https://github.com/MBombeck/HealthLog/blob/main/LICENSE
|
|
contact:
|
|
name: HealthLog
|
|
url: https://healthlog.dev
|
|
servers:
|
|
- url: https://healthlog.bombeck.io
|
|
description: Production
|
|
- url: http://localhost:3000
|
|
description: Local dev
|
|
tags:
|
|
- name: Auth
|
|
- name: Measurements
|
|
- name: Medications
|
|
- name: Mood
|
|
- name: Insights
|
|
- name: Notifications
|
|
- name: Devices
|
|
- name: Admin
|
|
paths:
|
|
/api/auth/login:
|
|
post:
|
|
tags:
|
|
- Auth
|
|
summary: Email-or-username login (password)
|
|
description: "Browser callers receive a session cookie. Native callers (X-Client-Type: native or HealthLog-iOS UA
|
|
prefix) additionally receive a paired access + refresh token."
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
email:
|
|
type: string
|
|
minLength: 1
|
|
password:
|
|
type: string
|
|
minLength: 1
|
|
required:
|
|
- email
|
|
- password
|
|
responses:
|
|
"200":
|
|
description: Login succeeded.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/LoginResponse"
|
|
"401": &a1
|
|
description: Authentication required or invalid credentials.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
"422": &a2
|
|
description: Request validation failed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
"429": &a3
|
|
description: Rate limit exceeded.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
/api/auth/passkey/login-verify:
|
|
post:
|
|
tags:
|
|
- Auth
|
|
summary: Passkey assertion verification
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PasskeyLoginVerifyRequest"
|
|
responses:
|
|
"200":
|
|
description: Assertion verified — session + optional bearer issued.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PasskeyLoginVerifyResponse"
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
/api/auth/refresh:
|
|
post:
|
|
tags:
|
|
- Auth
|
|
summary: Rotate refresh token (one-time use)
|
|
description: Reuse of a consumed refresh token revokes every refresh token still active for the originating device
|
|
(per-device blast radius from v1.4.23). Legacy tokens issued before v1.4.23 with a null deviceId fall back to
|
|
revoke-all-for-user.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RefreshTokenRequest"
|
|
responses:
|
|
"200":
|
|
description: Rotation succeeded — new pair issued.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RefreshResponse"
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
/api/measurements:
|
|
get:
|
|
tags:
|
|
- Measurements
|
|
summary: List measurements
|
|
description: Filter by type + date range. Response is paged via offset/limit; default limit 100, hard cap 500.
|
|
parameters:
|
|
- in: query
|
|
name: type
|
|
schema:
|
|
type: string
|
|
enum:
|
|
- WEIGHT
|
|
- BLOOD_PRESSURE_SYS
|
|
- BLOOD_PRESSURE_DIA
|
|
- PULSE
|
|
- BODY_FAT
|
|
- SLEEP_DURATION
|
|
- ACTIVITY_STEPS
|
|
- BLOOD_GLUCOSE
|
|
- TOTAL_BODY_WATER
|
|
- BONE_MASS
|
|
- OXYGEN_SATURATION
|
|
- HEART_RATE_VARIABILITY
|
|
- RESTING_HEART_RATE
|
|
- ACTIVE_ENERGY_BURNED
|
|
- FLIGHTS_CLIMBED
|
|
- WALKING_RUNNING_DISTANCE
|
|
- VO2_MAX
|
|
- BODY_TEMPERATURE
|
|
- FAT_FREE_MASS
|
|
- FAT_MASS
|
|
- MUSCLE_MASS
|
|
- SKIN_TEMPERATURE
|
|
- PULSE_WAVE_VELOCITY
|
|
- VASCULAR_AGE
|
|
- VISCERAL_FAT
|
|
- AUDIO_EXPOSURE_ENV
|
|
- AUDIO_EXPOSURE_HEADPHONE
|
|
- TIME_IN_DAYLIGHT
|
|
- WALKING_STEADINESS
|
|
- AUDIO_EXPOSURE_EVENT
|
|
- in: query
|
|
name: from
|
|
schema:
|
|
type: string
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
|
- in: query
|
|
name: to
|
|
schema:
|
|
type: string
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
|
- in: query
|
|
name: limit
|
|
schema:
|
|
default: 100
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 5000
|
|
- in: query
|
|
name: offset
|
|
schema:
|
|
default: 0
|
|
type: integer
|
|
minimum: 0
|
|
maximum: 9007199254740991
|
|
- in: query
|
|
name: sortBy
|
|
schema:
|
|
default: measuredAt
|
|
type: string
|
|
enum:
|
|
- type
|
|
- value
|
|
- measuredAt
|
|
- source
|
|
- in: query
|
|
name: sortDir
|
|
schema:
|
|
default: desc
|
|
type: string
|
|
enum:
|
|
- asc
|
|
- desc
|
|
- in: query
|
|
name: aggregate
|
|
schema:
|
|
type: string
|
|
enum:
|
|
- raw
|
|
- daily
|
|
- weekly
|
|
- monthly
|
|
- in: query
|
|
name: source
|
|
schema:
|
|
type: string
|
|
enum:
|
|
- rollup
|
|
- in: query
|
|
name: groupBy
|
|
schema:
|
|
type: string
|
|
enum:
|
|
- day
|
|
- in: query
|
|
name: dayKey
|
|
schema:
|
|
type: string
|
|
pattern: ^\d{4}-\d{2}-\d{2}$
|
|
responses:
|
|
"200":
|
|
description: Measurement page.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ListMeasurementsResponse"
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
post:
|
|
tags:
|
|
- Measurements
|
|
summary: Create one measurement
|
|
description: Single ingest. Use `/api/measurements/batch` for Apple Health upload streams.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
type:
|
|
type: string
|
|
enum:
|
|
- WEIGHT
|
|
- BLOOD_PRESSURE_SYS
|
|
- BLOOD_PRESSURE_DIA
|
|
- PULSE
|
|
- BODY_FAT
|
|
- SLEEP_DURATION
|
|
- ACTIVITY_STEPS
|
|
- BLOOD_GLUCOSE
|
|
- TOTAL_BODY_WATER
|
|
- BONE_MASS
|
|
- OXYGEN_SATURATION
|
|
- HEART_RATE_VARIABILITY
|
|
- RESTING_HEART_RATE
|
|
- ACTIVE_ENERGY_BURNED
|
|
- FLIGHTS_CLIMBED
|
|
- WALKING_RUNNING_DISTANCE
|
|
- VO2_MAX
|
|
- BODY_TEMPERATURE
|
|
- FAT_FREE_MASS
|
|
- FAT_MASS
|
|
- MUSCLE_MASS
|
|
- SKIN_TEMPERATURE
|
|
- PULSE_WAVE_VELOCITY
|
|
- VASCULAR_AGE
|
|
- VISCERAL_FAT
|
|
- AUDIO_EXPOSURE_ENV
|
|
- AUDIO_EXPOSURE_HEADPHONE
|
|
- TIME_IN_DAYLIGHT
|
|
- WALKING_STEADINESS
|
|
- AUDIO_EXPOSURE_EVENT
|
|
value:
|
|
type: number
|
|
measuredAt:
|
|
type: string
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
|
notes:
|
|
type: string
|
|
maxLength: 25
|
|
source:
|
|
default: MANUAL
|
|
type: string
|
|
enum:
|
|
- MANUAL
|
|
- WITHINGS
|
|
- IMPORT
|
|
- APPLE_HEALTH
|
|
glucoseContext:
|
|
type: string
|
|
enum:
|
|
- FASTING
|
|
- POSTPRANDIAL
|
|
- RANDOM
|
|
- BEDTIME
|
|
deviceType:
|
|
anyOf:
|
|
- type: string
|
|
minLength: 1
|
|
maxLength: 32
|
|
- type: "null"
|
|
required:
|
|
- type
|
|
- value
|
|
- measuredAt
|
|
responses:
|
|
"201":
|
|
description: Created.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateMeasurementResponse"
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
/api/measurements/batch:
|
|
post:
|
|
tags:
|
|
- Measurements
|
|
summary: Apple Health batch ingest
|
|
description: Up to 500 HealthKit entries per call. Idempotent via the `Idempotency-Key` header (replay window 24h).
|
|
Per-entry status lets the iOS client advance its sync cursor accurately. v1.4.25 W8c adds an optional
|
|
`deviceType` per entry — feed it from `HKDevice.model` so the cross-source canonical picker can break
|
|
Apple-Watch-vs-iPhone ties; null/absent stays backward-compatible with v1.4.23 clients.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AppleHealthBatchRequest"
|
|
responses:
|
|
"200":
|
|
description: Batch processed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BatchMeasurementsResponse"
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
/api/measurements/by-external-ids:
|
|
delete:
|
|
tags:
|
|
- Measurements
|
|
summary: Delete measurements by external ID (iOS deletion-sync)
|
|
description: Removes the user's measurement rows whose externalId is in the request list. Rows owned by another user are
|
|
silently skipped (cross-user 404 guard). Up to 500 externalIds per call.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/MeasurementsDeleteByExternalIdsRequest"
|
|
responses:
|
|
"200":
|
|
description: Delete batch processed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/MeasurementsDeleteByExternalIdsResponse"
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
/api/workouts:
|
|
get:
|
|
tags:
|
|
- Measurements
|
|
summary: List workouts (v1.4.32)
|
|
description: "Paginated workout list with cross-source canonical-row dedup. The picker reads the per-user
|
|
source-priority ladder and buckets by `(startedAt 5 min slot, sportType)` — twin workouts (Apple Watch +
|
|
Withings ScanWatch) collapse to a single canonical row per cluster. Field names mirror the iOS handoff contract:
|
|
`distanceM`, `activeEnergyKcal`, `avgHr`, `maxHr`."
|
|
parameters:
|
|
- in: query
|
|
name: limit
|
|
schema:
|
|
type: integer
|
|
minimum: 1
|
|
maximum: 200
|
|
- in: query
|
|
name: offset
|
|
schema:
|
|
type: integer
|
|
minimum: 0
|
|
maximum: 9007199254740991
|
|
- in: query
|
|
name: since
|
|
schema:
|
|
type: string
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
|
- in: query
|
|
name: until
|
|
schema:
|
|
type: string
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
|
- in: query
|
|
name: sportType
|
|
schema:
|
|
type: string
|
|
responses:
|
|
"200":
|
|
description: Canonical workout page.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ListWorkoutsResponse"
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
/api/workouts/{id}:
|
|
get:
|
|
tags:
|
|
- Measurements
|
|
summary: Workout detail (v1.4.32)
|
|
description: Single-workout envelope. Owns the optional `WorkoutRoute` GeoJSON geometry + `canonicalId` pointer that
|
|
resolves to the cluster winner so deep-links into non-canonical twin rows can redirect cleanly. Cross-user rows
|
|
surface as 404 (existence channel sealed).
|
|
parameters:
|
|
- in: path
|
|
name: id
|
|
schema:
|
|
type: string
|
|
required: true
|
|
responses:
|
|
"200":
|
|
description: Workout detail.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/GetWorkoutResponse"
|
|
"401": *a1
|
|
"404":
|
|
description: Workout not found (or owned by another user).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
"422": *a2
|
|
"429": *a3
|
|
/api/workouts/batch:
|
|
post:
|
|
tags:
|
|
- Measurements
|
|
summary: Typed workout batch ingest (v1.4.25 W16b)
|
|
description: Server-side ingest endpoint for HKWorkout records (iOS) and Withings activity rows (server-to-server). Up
|
|
to 100 workouts per call; nested route geometry (GeoJSON LineString) is capped at 20 000 points and stored in a
|
|
1:1 `WorkoutRoute` row keyed by `workoutId`. Idempotent via the `Idempotency-Key` header (replay window 24h).
|
|
Per-entry status (`inserted | duplicate | skipped`) lets the iOS sync cursor checkpoint accurately. The request
|
|
body ceiling is 5 MB enforced at the HTTP layer — clients above the ceiling receive a 413 with
|
|
`workout.batch.payload_too_large`.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
workouts:
|
|
minItems: 1
|
|
maxItems: 100
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
sportType:
|
|
type: string
|
|
enum:
|
|
- walking
|
|
- running
|
|
- cycling
|
|
- hiking
|
|
- swimming
|
|
- rowing
|
|
- elliptical
|
|
- stairClimber
|
|
- yoga
|
|
- mindAndBody
|
|
- strength
|
|
- hiit
|
|
- dance
|
|
- golf
|
|
- tennis
|
|
- basketball
|
|
- soccer
|
|
- crossTraining
|
|
- mixedCardio
|
|
- other
|
|
startedAt:
|
|
type: string
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
|
endedAt:
|
|
type: string
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
|
totalEnergyKcal:
|
|
type: number
|
|
minimum: 0
|
|
maximum: 50000
|
|
totalDistanceM:
|
|
type: number
|
|
minimum: 0
|
|
maximum: 1000000
|
|
avgHeartRate:
|
|
type: integer
|
|
minimum: 20
|
|
maximum: 300
|
|
maxHeartRate:
|
|
type: integer
|
|
minimum: 20
|
|
maximum: 300
|
|
minHeartRate:
|
|
type: integer
|
|
minimum: 20
|
|
maximum: 300
|
|
stepCount:
|
|
type: integer
|
|
minimum: 0
|
|
maximum: 200000
|
|
elevationM:
|
|
type: number
|
|
minimum: -500
|
|
maximum: 10000
|
|
pauseDurationSec:
|
|
type: integer
|
|
minimum: 0
|
|
maximum: 86400
|
|
source:
|
|
default: MANUAL
|
|
type: string
|
|
enum:
|
|
- MANUAL
|
|
- WITHINGS
|
|
- IMPORT
|
|
- APPLE_HEALTH
|
|
externalId:
|
|
type: string
|
|
maxLength: 128
|
|
externalSourceVersion:
|
|
type: string
|
|
maxLength: 64
|
|
metadata:
|
|
type: object
|
|
propertyNames:
|
|
type: string
|
|
additionalProperties: {}
|
|
route:
|
|
type: object
|
|
properties:
|
|
geometry:
|
|
type: object
|
|
properties:
|
|
type:
|
|
type: string
|
|
const: LineString
|
|
coordinates:
|
|
minItems: 2
|
|
maxItems: 20000
|
|
type: array
|
|
items:
|
|
type: array
|
|
prefixItems:
|
|
- type: number
|
|
minimum: -180
|
|
maximum: 180
|
|
- type: number
|
|
minimum: -90
|
|
maximum: 90
|
|
items:
|
|
type: number
|
|
required:
|
|
- type
|
|
- coordinates
|
|
sampleTimestamps:
|
|
type: array
|
|
items:
|
|
type: object
|
|
properties:
|
|
t:
|
|
type: string
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
|
speedMs:
|
|
type: number
|
|
minimum: 0
|
|
maximum: 50
|
|
hr:
|
|
type: integer
|
|
minimum: 20
|
|
maximum: 300
|
|
required:
|
|
- t
|
|
required:
|
|
- geometry
|
|
required:
|
|
- sportType
|
|
- startedAt
|
|
- endedAt
|
|
required:
|
|
- workouts
|
|
responses:
|
|
"200":
|
|
description: Batch processed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BatchWorkoutsResponse"
|
|
"400":
|
|
description: Batch validation failed (over-cap, schema reject, or oversized route).
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
"401": *a1
|
|
"413":
|
|
description: Request body exceeds the 5 MB ceiling.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
"422": *a2
|
|
"429": *a3
|
|
/api/devices:
|
|
post:
|
|
tags:
|
|
- Devices
|
|
summary: Register native device + APNs token
|
|
description: Idempotent upsert by `token`. APNs token + environment are paired — supplying one without the other returns
|
|
422. Cross-user re-registration of either identifier returns 409.
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DeviceRegisterRequest"
|
|
responses:
|
|
"201":
|
|
description: Device registered or refreshed.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DeviceRegisterResponse"
|
|
"401": *a1
|
|
"409":
|
|
description: Device or APNs token already registered to another user.
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
"422": *a2
|
|
"429": *a3
|
|
/api/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/{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
|
|
- 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
|
|
- 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.
|
|
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
|
|
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
|
|
- 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
|
|
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.
|