mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
Intl.DateTimeFormat instances are memoised per locale, timezone and options signature — construction dominated the 365-day ledger build and the hot path drops about fivefold. A batched GET /api/medications/compliance serves every card from one request through the same per-medication cache cells the detail page reads, replacing the per-card fan-out; the list response stays untouched so the due line never waits on a ledger build. The medications and compliance buckets serve stale-while-revalidate, interactive writes evict while background bulk sync marks stale, the per-id route defers ownership and schedule reads until a cache miss, and the card body reserves constant slots for the status and cycle lines so late data stops shifting the layout.
12937 lines
444 KiB
YAML
12937 lines
444 KiB
YAML
openapi: 3.1.0
|
||
info:
|
||
title: HealthLog API
|
||
version: 1.16.7
|
||
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: PolyForm-Noncommercial-1.0.0
|
||
url: https://github.com/MBombeck/HealthLog/blob/main/LICENSE
|
||
contact:
|
||
name: HealthLog
|
||
url: https://healthlog.dev
|
||
servers:
|
||
- url: https://healthlog.example.com
|
||
description: Your self-hosted instance
|
||
- url: http://localhost:3000
|
||
description: Local dev
|
||
tags:
|
||
- name: Auth
|
||
- name: Measurements
|
||
- name: Medications
|
||
- name: Mood
|
||
- name: Analytics
|
||
- name: Insights
|
||
- name: Dashboard
|
||
- name: Notifications
|
||
- name: Devices
|
||
- name: Export
|
||
- name: Sync
|
||
- name: Cycle
|
||
- name: Admin
|
||
- name: Meta
|
||
paths:
|
||
/api/cycle/day-logs:
|
||
get:
|
||
tags:
|
||
- Cycle
|
||
summary: Read a single day's cycle day-log (v1.15.0)
|
||
description: Returns the full `CycleDayLogDTO` for the tz-anchored `date`, or `null` when nothing is logged that day.
|
||
Lets a client pre-fill an edit sheet. Gated; owner-scoped; soft-deleted rows excluded.
|
||
parameters:
|
||
- in: query
|
||
name: date
|
||
schema:
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
required: true
|
||
responses:
|
||
"200":
|
||
description: The day-log for that date, or null.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CycleDayLogReadEnvelope"
|
||
"401": &a1
|
||
description: Authentication required or invalid credentials.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"403": &a2
|
||
description: Cycle tracking is not enabled for this account (errorCode `cycle.disabled`).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": &a3
|
||
description: Request validation failed.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"429": &a4
|
||
description: Rate limit exceeded.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
post:
|
||
tags:
|
||
- Cycle
|
||
summary: Capture a single cycle day-log (v1.15.0)
|
||
description: "Upserts on `(userId, source, externalId)` when externalId present, else `(userId, date)`. `note` encrypts
|
||
at rest. 201 on insert, 200 on update. Gated: `cycle.disabled` 403 when the feature is off."
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
date:
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
flow:
|
||
type: string
|
||
enum:
|
||
- NONE
|
||
- SPOTTING
|
||
- LIGHT
|
||
- MEDIUM
|
||
- HEAVY
|
||
intermenstrualBleeding:
|
||
type: boolean
|
||
basalBodyTempC:
|
||
type: number
|
||
minimum: 30
|
||
maximum: 45
|
||
ovulationTest:
|
||
type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE_LH_SURGE
|
||
- ESTROGEN_SURGE
|
||
- INDETERMINATE
|
||
cervicalMucus:
|
||
type: string
|
||
enum:
|
||
- DRY
|
||
- STICKY
|
||
- CREAMY
|
||
- WATERY
|
||
- EGG_WHITE
|
||
sexualActivity:
|
||
type: boolean
|
||
protectedSex:
|
||
anyOf:
|
||
- type: boolean
|
||
- type: "null"
|
||
pregnancyTest:
|
||
type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE
|
||
- INDETERMINATE
|
||
progesteroneTest:
|
||
type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE
|
||
- INDETERMINATE
|
||
contraceptive:
|
||
type: string
|
||
enum:
|
||
- NONE
|
||
- UNSPECIFIED
|
||
- IMPLANT
|
||
- INJECTION
|
||
- IUD
|
||
- INTRAVAGINAL_RING
|
||
- ORAL
|
||
- PATCH
|
||
- EMERGENCY
|
||
symptoms:
|
||
maxItems: 40
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
key:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 80
|
||
severity:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 4
|
||
required:
|
||
- key
|
||
note:
|
||
type: string
|
||
maxLength: 500
|
||
loggedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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:
|
||
default: MANUAL
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- APPLE_HEALTH
|
||
externalId:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
required:
|
||
- date
|
||
- loggedAt
|
||
responses:
|
||
"200":
|
||
description: Existing day-log updated.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CycleDayLogEnvelope"
|
||
"201":
|
||
description: New day-log created.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CycleDayLogCreatedEnvelope"
|
||
"401": *a1
|
||
"403": *a2
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/cycle/day-logs/{id}:
|
||
patch:
|
||
tags:
|
||
- Cycle
|
||
summary: Edit a single cycle day-log (v1.15.0)
|
||
description: Partial edit; an omitted field is left untouched. Owner-scoped (a cross-user id 404s). Gated.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
flow:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NONE
|
||
- SPOTTING
|
||
- LIGHT
|
||
- MEDIUM
|
||
- HEAVY
|
||
- type: "null"
|
||
intermenstrualBleeding:
|
||
type: boolean
|
||
basalBodyTempC:
|
||
anyOf:
|
||
- type: number
|
||
minimum: 30
|
||
maximum: 45
|
||
- type: "null"
|
||
ovulationTest:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE_LH_SURGE
|
||
- ESTROGEN_SURGE
|
||
- INDETERMINATE
|
||
- type: "null"
|
||
cervicalMucus:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- DRY
|
||
- STICKY
|
||
- CREAMY
|
||
- WATERY
|
||
- EGG_WHITE
|
||
- type: "null"
|
||
sexualActivity:
|
||
type: boolean
|
||
protectedSex:
|
||
anyOf:
|
||
- type: boolean
|
||
- type: "null"
|
||
pregnancyTest:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE
|
||
- INDETERMINATE
|
||
- type: "null"
|
||
progesteroneTest:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE
|
||
- INDETERMINATE
|
||
- type: "null"
|
||
contraceptive:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NONE
|
||
- UNSPECIFIED
|
||
- IMPLANT
|
||
- INJECTION
|
||
- IUD
|
||
- INTRAVAGINAL_RING
|
||
- ORAL
|
||
- PATCH
|
||
- EMERGENCY
|
||
- type: "null"
|
||
symptoms:
|
||
maxItems: 40
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
key:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 80
|
||
severity:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 4
|
||
required:
|
||
- key
|
||
note:
|
||
anyOf:
|
||
- type: string
|
||
maxLength: 500
|
||
- type: "null"
|
||
loggedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
responses:
|
||
"200":
|
||
description: Day-log updated.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CycleDayLogPatchEnvelope"
|
||
"401": *a1
|
||
"403": *a2
|
||
"404":
|
||
description: Day-log not found / not owned.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
delete:
|
||
tags:
|
||
- Cycle
|
||
summary: Soft-delete a cycle day-log (v1.15.0)
|
||
description: Sets `deletedAt` + bumps `syncVersion`; surfaces as a tombstone on the next `/api/sync/changes` page. 204.
|
||
Idempotent.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"204":
|
||
description: Soft-deleted (no body).
|
||
"401": *a1
|
||
"403": *a2
|
||
"404":
|
||
description: Day-log not found / not owned.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/cycle/day-logs/bulk:
|
||
post:
|
||
tags:
|
||
- Cycle
|
||
summary: Bulk drain cycle day-logs (Outbox / HealthKit) (v1.15.0)
|
||
description: "Up to 500 entries; `withIdempotency`; rate-limited `cycle:day-logs:bulk:<userId>` 60/min. Per-entry
|
||
status: inserted | duplicate | updated | skipped. Always 200."
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
entries:
|
||
minItems: 1
|
||
maxItems: 500
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
date:
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
flow:
|
||
type: string
|
||
enum:
|
||
- NONE
|
||
- SPOTTING
|
||
- LIGHT
|
||
- MEDIUM
|
||
- HEAVY
|
||
intermenstrualBleeding:
|
||
type: boolean
|
||
basalBodyTempC:
|
||
type: number
|
||
minimum: 30
|
||
maximum: 45
|
||
ovulationTest:
|
||
type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE_LH_SURGE
|
||
- ESTROGEN_SURGE
|
||
- INDETERMINATE
|
||
cervicalMucus:
|
||
type: string
|
||
enum:
|
||
- DRY
|
||
- STICKY
|
||
- CREAMY
|
||
- WATERY
|
||
- EGG_WHITE
|
||
sexualActivity:
|
||
type: boolean
|
||
protectedSex:
|
||
anyOf:
|
||
- type: boolean
|
||
- type: "null"
|
||
pregnancyTest:
|
||
type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE
|
||
- INDETERMINATE
|
||
progesteroneTest:
|
||
type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE
|
||
- INDETERMINATE
|
||
contraceptive:
|
||
type: string
|
||
enum:
|
||
- NONE
|
||
- UNSPECIFIED
|
||
- IMPLANT
|
||
- INJECTION
|
||
- IUD
|
||
- INTRAVAGINAL_RING
|
||
- ORAL
|
||
- PATCH
|
||
- EMERGENCY
|
||
symptoms:
|
||
maxItems: 40
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
key:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 80
|
||
severity:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 4
|
||
required:
|
||
- key
|
||
note:
|
||
type: string
|
||
maxLength: 500
|
||
loggedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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:
|
||
default: MANUAL
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- APPLE_HEALTH
|
||
externalId:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
required:
|
||
- date
|
||
- loggedAt
|
||
required:
|
||
- entries
|
||
responses:
|
||
"200":
|
||
description: Batch processed (per-entry results).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CycleBulkEnvelope"
|
||
"401": *a1
|
||
"403": *a2
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/cycle/period:
|
||
post:
|
||
tags:
|
||
- Cycle
|
||
summary: Period-boundary shortcut (v1.15.0)
|
||
description: One-tap started/ended period. `start` opens a new cycle (closing the prior); `end` stamps periodEndDate.
|
||
Writes the boundary day-log. Gated.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
action:
|
||
type: string
|
||
enum:
|
||
- start
|
||
- end
|
||
date:
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
externalId:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
loggedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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:
|
||
- action
|
||
- date
|
||
- loggedAt
|
||
responses:
|
||
"200":
|
||
description: Cycle + boundary day-log.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CyclePeriodEnvelope"
|
||
"401": *a1
|
||
"403": *a2
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/cycle/calendar:
|
||
get:
|
||
tags:
|
||
- Cycle
|
||
summary: Predicted cycle calendar (v1.15.0)
|
||
description: "Runs the deterministic engine to build `{ profile, prediction, days }`. Fertile-window fields are
|
||
server-suppressed unless goal is TRYING_TO_CONCEIVE. Default range: today − 90d … +180d. Gated."
|
||
parameters:
|
||
- in: query
|
||
name: from
|
||
schema:
|
||
type: string
|
||
- in: query
|
||
name: to
|
||
schema:
|
||
type: string
|
||
responses:
|
||
"200":
|
||
description: Calendar grid + forecast.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CycleCalendarEnvelope"
|
||
"401": *a1
|
||
"403": *a2
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/cycle/cycles:
|
||
get:
|
||
tags:
|
||
- Cycle
|
||
summary: Cycle history + stats (v1.15.0)
|
||
description: Most-recent cycles (newest first) + `{ avgLengthDays, lengthVariabilityDays (MAD), avgPeriodLengthDays,
|
||
regularity }`. `limit` default 24. Gated.
|
||
parameters:
|
||
- in: query
|
||
name: limit
|
||
schema:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 60
|
||
responses:
|
||
"200":
|
||
description: Cycle history.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CycleHistoryEnvelope"
|
||
"401": *a1
|
||
"403": *a2
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/cycle/insights:
|
||
get:
|
||
tags:
|
||
- Cycle
|
||
summary: Cycle-phase correlation insights (v1.15.0)
|
||
description: "FDR-guarded luteal-vs-follicular phase contrast per outcome metric (RHR / HRV / sleep / steps / weight /
|
||
temperatures), plus the single headline finding (resting-heart-rate-by-phase, falling back to HRV). The same
|
||
Welch t-test + Benjamini-Hochberg machinery the mood-factor crosstab runs; only rows with p < 0.05 AND q ≤ 0.10
|
||
surface. Strictly gender-gated — phase never appears on the general `/api/insights/correlations` route.
|
||
Observational only, never causal. Gated: `cycle.disabled` 403 when the feature is off."
|
||
responses:
|
||
"200":
|
||
description: Phase-correlation rows + headline.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CycleInsightsEnvelope"
|
||
"401": *a1
|
||
"403": *a2
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/cycle/cycles/{id}:
|
||
delete:
|
||
tags:
|
||
- Cycle
|
||
summary: Soft-delete a menstrual cycle (v1.15.0)
|
||
description: Sets `deletedAt` + bumps `syncVersion`; tombstones on the next sync page. 204. Idempotent.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"204":
|
||
description: Soft-deleted (no body).
|
||
"401": *a1
|
||
"403": *a2
|
||
"404":
|
||
description: Cycle not found / not owned.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/cycle/all:
|
||
delete:
|
||
tags:
|
||
- Cycle
|
||
summary: Hard-purge all cycle data (v1.15.0)
|
||
description: "One-click privacy purge: HARD-deletes every cycle day-log (+ symptom links by cascade), menstrual cycle,
|
||
prediction, the cycle audit trail, and the cycle reminder rows in the push-attempts ledger — no dated
|
||
reproductive trace survives. The CycleProfile row is left in place. Gated + owner-scoped + audited."
|
||
responses:
|
||
"200":
|
||
description: Purge counts.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CyclePurgeEnvelope"
|
||
"401": *a1
|
||
"403": *a2
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/cycle/profile:
|
||
get:
|
||
tags:
|
||
- Cycle
|
||
summary: Read the full cycle profile (v1.15.0)
|
||
description: Returns the resolved CycleProfileDTO. Gated.
|
||
responses:
|
||
"200":
|
||
description: Cycle profile.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CycleProfileEnvelope"
|
||
"401": *a1
|
||
"403": *a2
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/auth/me/cycle-prefs:
|
||
get:
|
||
tags:
|
||
- Cycle
|
||
summary: Read cycle preferences (v1.15.0)
|
||
description: Returns the resolved CycleProfileDTO. NOT gated — this is the surface that flips the gate.
|
||
responses:
|
||
"200":
|
||
description: Cycle preferences.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CyclePrefsGetEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
patch:
|
||
tags:
|
||
- Cycle
|
||
summary: Update cycle preferences (v1.15.0)
|
||
description: Deep-merges the supplied fields. `enabled` flips `cycleTrackingEnabled`. Returns the merged
|
||
CycleProfileDTO. NOT gated.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
enabled:
|
||
type: boolean
|
||
goal:
|
||
type: string
|
||
enum:
|
||
- GENERAL_HEALTH
|
||
- AVOID_PREGNANCY
|
||
- TRYING_TO_CONCEIVE
|
||
- PERIMENOPAUSE
|
||
- OFF
|
||
rawChartMode:
|
||
type: boolean
|
||
predictionEnabled:
|
||
type: boolean
|
||
discreetNotifications:
|
||
type: boolean
|
||
sensitiveCategoryEncryption:
|
||
type: boolean
|
||
typicalCycleLength:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 15
|
||
maximum: 60
|
||
- type: "null"
|
||
typicalPeriodLength:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 1
|
||
maximum: 15
|
||
- type: "null"
|
||
lutealPhaseLength:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 10
|
||
maximum: 16
|
||
- type: "null"
|
||
responses:
|
||
"200":
|
||
description: Merged cycle preferences.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CyclePrefsPatchEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/meta/capabilities:
|
||
get:
|
||
tags:
|
||
- Meta
|
||
summary: Live server capability / id-vocabulary discovery
|
||
description: Returns the server's REAL id vocabularies (derived-metric ids, vitals-baseline types, layout tile-ids,
|
||
metric-status ids, the HealthKit ingest mapping, the FHIR coding constants) plus the running API contract
|
||
version. Every list is derived server-side from the canonical registry it documents, so a client can gate its UI
|
||
/ decoder against what the server actually ships rather than a hand-maintained copy. Auth via cookie or Bearer
|
||
(not admin).
|
||
responses:
|
||
"200":
|
||
description: Capability snapshot.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CapabilitiesEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/export/health-record:
|
||
post:
|
||
tags:
|
||
- Export
|
||
summary: Generate a health-record export (PDF / FHIR / package)
|
||
description: "v1.7.0 flagship export. Returns the doctor-handover artefact in the requested `format`: `pdf` →
|
||
application/pdf, `fhir` → application/fhir+json (HL7 FHIR R4 document Bundle), `package` → application/zip (PDF
|
||
+ FHIR + README). Auth via cookie or Bearer; shared `export:<userId>` rate bucket (10/h). Strict validation:
|
||
unknown keys 422."
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
format:
|
||
type: string
|
||
enum:
|
||
- pdf
|
||
- fhir
|
||
- package
|
||
range:
|
||
type: object
|
||
properties:
|
||
startDate:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
endDate:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
days:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 365
|
||
additionalProperties: false
|
||
sections:
|
||
type: object
|
||
properties:
|
||
vitals:
|
||
type: object
|
||
properties:
|
||
weight:
|
||
type: boolean
|
||
bp:
|
||
type: boolean
|
||
pulse:
|
||
type: boolean
|
||
oxygenSaturation:
|
||
type: boolean
|
||
bodyFat:
|
||
type: boolean
|
||
bodyComposition:
|
||
type: boolean
|
||
cardioFitness:
|
||
type: object
|
||
properties:
|
||
restingHeartRate:
|
||
type: boolean
|
||
hrv:
|
||
type: boolean
|
||
vo2max:
|
||
type: boolean
|
||
activity:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
type: boolean
|
||
distance:
|
||
type: boolean
|
||
energy:
|
||
type: boolean
|
||
sleep:
|
||
type: boolean
|
||
glucose:
|
||
type: boolean
|
||
medications:
|
||
type: object
|
||
properties:
|
||
list:
|
||
type: boolean
|
||
compliance:
|
||
type: boolean
|
||
glp1:
|
||
type: boolean
|
||
sideEffects:
|
||
type: boolean
|
||
mood:
|
||
type: boolean
|
||
bmi:
|
||
type: boolean
|
||
cycle:
|
||
type: boolean
|
||
locale:
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
- fr
|
||
- es
|
||
- it
|
||
- pl
|
||
practiceName:
|
||
type: string
|
||
maxLength: 120
|
||
includeCharts:
|
||
type: boolean
|
||
includeAiSummary:
|
||
type: boolean
|
||
germanAtc:
|
||
type: boolean
|
||
required:
|
||
- format
|
||
additionalProperties: false
|
||
responses:
|
||
"200":
|
||
description: "Export generated. Content-Type varies by `format`: application/pdf, application/fhir+json, or
|
||
application/zip."
|
||
content:
|
||
application/pdf:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
application/fhir+json:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
application/zip:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/share-links:
|
||
post:
|
||
tags:
|
||
- Export
|
||
summary: Create a clinician share link (v1.11.0)
|
||
description: "Owner-only. Mints an `hls_` token (192-bit), stores only its HMAC hash, and returns the raw token EXACTLY
|
||
ONCE in the response. Every scope column (window, sections, FHIR resource types, API toggle) is frozen
|
||
write-once. `expiresAt` is required and capped at 90 days. Auth via cookie or Bearer; rate-limited
|
||
(`share-link:<userId>`, 20/h). Strict: unknown keys 422."
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
label:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
rangeStart:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
rangeEnd:
|
||
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"
|
||
sections:
|
||
type: object
|
||
properties:
|
||
vitals:
|
||
type: object
|
||
properties:
|
||
weight:
|
||
type: boolean
|
||
bp:
|
||
type: boolean
|
||
pulse:
|
||
type: boolean
|
||
oxygenSaturation:
|
||
type: boolean
|
||
bodyFat:
|
||
type: boolean
|
||
bodyComposition:
|
||
type: boolean
|
||
cardioFitness:
|
||
type: object
|
||
properties:
|
||
restingHeartRate:
|
||
type: boolean
|
||
hrv:
|
||
type: boolean
|
||
vo2max:
|
||
type: boolean
|
||
activity:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
type: boolean
|
||
distance:
|
||
type: boolean
|
||
energy:
|
||
type: boolean
|
||
sleep:
|
||
type: boolean
|
||
glucose:
|
||
type: boolean
|
||
medications:
|
||
type: object
|
||
properties:
|
||
list:
|
||
type: boolean
|
||
compliance:
|
||
type: boolean
|
||
glp1:
|
||
type: boolean
|
||
sideEffects:
|
||
type: boolean
|
||
mood:
|
||
type: boolean
|
||
bmi:
|
||
type: boolean
|
||
cycle:
|
||
type: boolean
|
||
resourceTypes:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- Patient
|
||
- Observation
|
||
- MedicationStatement
|
||
- MedicationAdministration
|
||
allowFhirApi:
|
||
type: boolean
|
||
expiresAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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:
|
||
- label
|
||
- rangeStart
|
||
- expiresAt
|
||
additionalProperties: false
|
||
responses:
|
||
"201":
|
||
description: Share link created. `token` carries the raw `hls_` value and is unrecoverable after this response.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ShareLinkCreated"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
get:
|
||
tags:
|
||
- Export
|
||
summary: List own clinician share links (v1.11.0)
|
||
description: Owner-only. Returns the caller's own share links (never the raw token — it is unrecoverable after
|
||
creation). Auth via cookie or Bearer.
|
||
responses:
|
||
"200":
|
||
description: Share links owned by the caller.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ShareLinkList"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/share-links/{id}:
|
||
delete:
|
||
tags:
|
||
- Export
|
||
summary: Revoke a clinician share link (v1.11.0)
|
||
description: Owner-only. Sets `revokedAt` on the caller's own link. A cross-user or unknown id is sealed as 404. Auth
|
||
via cookie or Bearer; rate-limited.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"200":
|
||
description: Link revoked.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ShareLinkRevoked"
|
||
"401": *a1
|
||
"404":
|
||
description: Link not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/fhir/metadata:
|
||
get:
|
||
tags:
|
||
- FHIR
|
||
summary: FHIR R4 CapabilityStatement (v1.11.0)
|
||
description: "Read-only FHIR R4 capability statement for the REST face. Declares the served resource types (Patient,
|
||
Observation, MedicationStatement, MedicationAdministration), the `$everything` operation, and the
|
||
`application/fhir+json` format. Auth: `fhir:read` scope (cookie sessions also pass)."
|
||
responses:
|
||
"200":
|
||
description: CapabilityStatement (application/fhir+json).
|
||
content:
|
||
application/fhir+json:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/fhir/Patient:
|
||
get:
|
||
tags:
|
||
- FHIR
|
||
summary: FHIR R4 Patient search (v1.11.0)
|
||
description: "Read-only `searchset` Bundle of the caller's own Patient resource. Auth: `fhir:read` scope. Offset paging
|
||
via `_count` (clamped ≤200) / `_offset`. `userId` is narrowed from auth."
|
||
parameters:
|
||
- in: query
|
||
name: _count
|
||
schema:
|
||
type: number
|
||
- in: query
|
||
name: _offset
|
||
schema:
|
||
type: number
|
||
responses:
|
||
"200":
|
||
description: searchset Bundle (application/fhir+json).
|
||
content:
|
||
application/fhir+json:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/fhir/Observation:
|
||
get:
|
||
tags:
|
||
- FHIR
|
||
summary: FHIR R4 Observation search (v1.11.0)
|
||
description: "Read-only `searchset` Bundle of the caller's own Observations (vitals / activity / lab / survey). Auth:
|
||
`fhir:read` scope. Offset paging via `_count` (clamped ≤200) / `_offset`."
|
||
parameters:
|
||
- in: query
|
||
name: _count
|
||
schema:
|
||
type: number
|
||
- in: query
|
||
name: _offset
|
||
schema:
|
||
type: number
|
||
responses:
|
||
"200":
|
||
description: searchset Bundle (application/fhir+json).
|
||
content:
|
||
application/fhir+json:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/fhir/MedicationStatement:
|
||
get:
|
||
tags:
|
||
- FHIR
|
||
summary: FHIR R4 MedicationStatement search (v1.11.0)
|
||
description: "Read-only `searchset` Bundle of the caller's own active-medication statements. Auth: `fhir:read` scope.
|
||
Offset paging via `_count` (≤200) / `_offset`."
|
||
parameters:
|
||
- in: query
|
||
name: _count
|
||
schema:
|
||
type: number
|
||
- in: query
|
||
name: _offset
|
||
schema:
|
||
type: number
|
||
responses:
|
||
"200":
|
||
description: searchset Bundle (application/fhir+json).
|
||
content:
|
||
application/fhir+json:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/fhir/MedicationAdministration:
|
||
get:
|
||
tags:
|
||
- FHIR
|
||
summary: FHIR R4 MedicationAdministration search (v1.11.0)
|
||
description: "Read-only `searchset` Bundle of the caller's own acted intakes (completed / not-done). Auth: `fhir:read`
|
||
scope. Offset paging via `_count` (≤200) / `_offset`."
|
||
parameters:
|
||
- in: query
|
||
name: _count
|
||
schema:
|
||
type: number
|
||
- in: query
|
||
name: _offset
|
||
schema:
|
||
type: number
|
||
responses:
|
||
"200":
|
||
description: searchset Bundle (application/fhir+json).
|
||
content:
|
||
application/fhir+json:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/fhir/$everything:
|
||
get:
|
||
tags:
|
||
- FHIR
|
||
summary: FHIR R4 $everything (v1.11.0)
|
||
description: "Read-only `$everything` operation: every resource in the caller's own record (Patient, Coverage,
|
||
Observations, MedicationStatements, MedicationAdministrations) in one `searchset` Bundle. Auth: `fhir:read`
|
||
scope. Offset paging via `_count` (≤200) / `_offset`."
|
||
parameters:
|
||
- in: query
|
||
name: _count
|
||
schema:
|
||
type: number
|
||
- in: query
|
||
name: _offset
|
||
schema:
|
||
type: number
|
||
responses:
|
||
"200":
|
||
description: searchset Bundle (application/fhir+json).
|
||
content:
|
||
application/fhir+json:
|
||
schema:
|
||
type: string
|
||
format: binary
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/auth/login:
|
||
post:
|
||
tags:
|
||
- Auth
|
||
summary: Email-or-username login (password)
|
||
description: "Browser callers receive a session cookie. Native callers (X-Client-Type: native or HealthLog-iOS UA
|
||
prefix) additionally receive a paired access + refresh token."
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
email:
|
||
type: string
|
||
minLength: 1
|
||
password:
|
||
type: string
|
||
minLength: 1
|
||
required:
|
||
- email
|
||
- password
|
||
responses:
|
||
"200":
|
||
description: Login succeeded.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/LoginResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
/api/auth/refresh:
|
||
post:
|
||
tags:
|
||
- Auth
|
||
summary: Rotate refresh token (one-time use)
|
||
description: >-
|
||
Reuse of a consumed refresh token revokes every refresh token still active for the originating device
|
||
(per-device blast radius from v1.4.23). Legacy tokens issued before v1.4.23 with a null deviceId fall back to
|
||
revoke-all-for-user.
|
||
|
||
|
||
On a 401, `meta.errorCode` is a stable machine code so the client can branch terminal re-auth from a transient
|
||
blip without parsing the prose `error`: `auth.refresh.reuse` (a consumed token was replayed — device family
|
||
revoked, re-pair required), `auth.refresh.revoked` (family revoked out-of-band — re-pair required),
|
||
`auth.refresh.invalid` (not found / expired — drop the token and re-authenticate).
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/RefreshTokenRequest"
|
||
responses:
|
||
"200":
|
||
description: Rotation succeeded — new pair issued.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/RefreshResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/sync/state:
|
||
get:
|
||
tags:
|
||
- Sync
|
||
summary: Sync handshake + window metadata
|
||
description: Cheap 'should I sync?' summary. Returns the previous `lastSyncedAt` checkpoint and advances it server-side
|
||
on each call. The `sync` block carries the incremental-delta window + tombstone retention so the client reads
|
||
them rather than hardcoding.
|
||
responses:
|
||
"200":
|
||
description: Sync state summary.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/SyncStateEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/sync/changes:
|
||
get:
|
||
tags:
|
||
- Sync
|
||
summary: Measurements delta feed
|
||
description: "Incremental catch-up after the first-pair backfill (never a replacement for it). Pages over an opaque
|
||
keyset cursor; each page carries `tombstones` (soft-deleted rows, keyed on `externalId`) and `upserts` (live
|
||
rows). Apply tombstones before upserts within a page. `cursorExpired: true` forces a clean re-init."
|
||
parameters:
|
||
- in: query
|
||
name: cursor
|
||
schema:
|
||
description: Opaque multi-domain keyset cursor from the previous page. Treat as fully opaque — echo, never parse. Omit
|
||
for the initial sync.
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 2048
|
||
description: Opaque multi-domain keyset cursor from the previous page. Treat as fully opaque — echo, never parse. Omit
|
||
for the initial sync.
|
||
- in: query
|
||
name: limit
|
||
schema:
|
||
description: Page size, default 200, hard cap 500.
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 500
|
||
description: Page size, default 200, hard cap 500.
|
||
responses:
|
||
"200":
|
||
description: Delta page.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/SyncChangesEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/measurements:
|
||
get:
|
||
tags:
|
||
- Measurements
|
||
summary: List measurements
|
||
description: Filter by type + date range. Response is paged via offset/limit; default limit 100, hard cap 500.
|
||
parameters:
|
||
- in: query
|
||
name: type
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- WEIGHT
|
||
- BLOOD_PRESSURE_SYS
|
||
- BLOOD_PRESSURE_DIA
|
||
- PULSE
|
||
- BODY_FAT
|
||
- SLEEP_DURATION
|
||
- ACTIVITY_STEPS
|
||
- BLOOD_GLUCOSE
|
||
- TOTAL_BODY_WATER
|
||
- BONE_MASS
|
||
- OXYGEN_SATURATION
|
||
- HEART_RATE_VARIABILITY
|
||
- RESTING_HEART_RATE
|
||
- ACTIVE_ENERGY_BURNED
|
||
- FLIGHTS_CLIMBED
|
||
- WALKING_RUNNING_DISTANCE
|
||
- VO2_MAX
|
||
- BODY_TEMPERATURE
|
||
- FAT_FREE_MASS
|
||
- FAT_MASS
|
||
- MUSCLE_MASS
|
||
- SKIN_TEMPERATURE
|
||
- PULSE_WAVE_VELOCITY
|
||
- VASCULAR_AGE
|
||
- VISCERAL_FAT
|
||
- AUDIO_EXPOSURE_ENV
|
||
- AUDIO_EXPOSURE_HEADPHONE
|
||
- TIME_IN_DAYLIGHT
|
||
- WALKING_STEADINESS
|
||
- AUDIO_EXPOSURE_EVENT
|
||
- RESPIRATORY_RATE
|
||
- BODY_MASS_INDEX
|
||
- LEAN_BODY_MASS
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
- IRREGULAR_RHYTHM_NOTIFICATION
|
||
- HIGH_HEART_RATE_EVENT
|
||
- LOW_HEART_RATE_EVENT
|
||
- WALKING_STEADINESS_EVENT
|
||
- BREATHING_DISTURBANCE_EVENT
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
- HRV_RMSSD
|
||
- DAY_STRAIN
|
||
- WORKOUT_STRAIN
|
||
- SLEEP_PERFORMANCE
|
||
- SLEEP_EFFICIENCY
|
||
- SLEEP_CONSISTENCY
|
||
- SLEEP_NEED
|
||
- ENERGY_EXPENDITURE_KJ
|
||
- AVERAGE_HEART_RATE
|
||
- MAX_HEART_RATE
|
||
- SLEEP_DISTURBANCE_COUNT
|
||
- 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: sourceEq
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
- 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": *a3
|
||
"429": *a4
|
||
post:
|
||
tags:
|
||
- Measurements
|
||
summary: Create one measurement
|
||
description: Single ingest. Use `/api/measurements/batch` for Apple Health upload streams.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
type:
|
||
type: string
|
||
enum:
|
||
- WEIGHT
|
||
- BLOOD_PRESSURE_SYS
|
||
- BLOOD_PRESSURE_DIA
|
||
- PULSE
|
||
- BODY_FAT
|
||
- SLEEP_DURATION
|
||
- ACTIVITY_STEPS
|
||
- BLOOD_GLUCOSE
|
||
- TOTAL_BODY_WATER
|
||
- BONE_MASS
|
||
- OXYGEN_SATURATION
|
||
- HEART_RATE_VARIABILITY
|
||
- RESTING_HEART_RATE
|
||
- ACTIVE_ENERGY_BURNED
|
||
- FLIGHTS_CLIMBED
|
||
- WALKING_RUNNING_DISTANCE
|
||
- VO2_MAX
|
||
- BODY_TEMPERATURE
|
||
- FAT_FREE_MASS
|
||
- FAT_MASS
|
||
- MUSCLE_MASS
|
||
- SKIN_TEMPERATURE
|
||
- PULSE_WAVE_VELOCITY
|
||
- VASCULAR_AGE
|
||
- VISCERAL_FAT
|
||
- AUDIO_EXPOSURE_ENV
|
||
- AUDIO_EXPOSURE_HEADPHONE
|
||
- TIME_IN_DAYLIGHT
|
||
- WALKING_STEADINESS
|
||
- AUDIO_EXPOSURE_EVENT
|
||
- RESPIRATORY_RATE
|
||
- BODY_MASS_INDEX
|
||
- LEAN_BODY_MASS
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
- IRREGULAR_RHYTHM_NOTIFICATION
|
||
- HIGH_HEART_RATE_EVENT
|
||
- LOW_HEART_RATE_EVENT
|
||
- WALKING_STEADINESS_EVENT
|
||
- BREATHING_DISTURBANCE_EVENT
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
- HRV_RMSSD
|
||
- DAY_STRAIN
|
||
- WORKOUT_STRAIN
|
||
- SLEEP_PERFORMANCE
|
||
- SLEEP_EFFICIENCY
|
||
- SLEEP_CONSISTENCY
|
||
- SLEEP_NEED
|
||
- ENERGY_EXPENDITURE_KJ
|
||
- AVERAGE_HEART_RATE
|
||
- MAX_HEART_RATE
|
||
- SLEEP_DISTURBANCE_COUNT
|
||
value:
|
||
type: number
|
||
measuredAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
notes:
|
||
type: string
|
||
maxLength: 200
|
||
source:
|
||
default: MANUAL
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- 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": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
/api/measurements/bulk-delete:
|
||
post:
|
||
tags:
|
||
- Measurements
|
||
summary: Bulk soft-delete measurements
|
||
description: Soft-deletes (tombstones) up to 200 of the caller's measurement rows in one call, mirroring the
|
||
single-DELETE contract. Idempotent via the `Idempotency-Key` header; rate-limited
|
||
`measurements:bulk-delete:<userId>`. Forged / foreign / already-deleted ids are silently skipped — `deleted` is
|
||
the count of rows actually tombstoned. The deletions surface in `/api/sync/changes` as tombstones.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MeasurementsBulkDeleteRequest"
|
||
responses:
|
||
"200":
|
||
description: Bulk delete processed.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MeasurementsBulkDeleteResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/measurements/restore:
|
||
post:
|
||
tags:
|
||
- Measurements
|
||
summary: Restore soft-deleted measurements
|
||
description: Un-tombstones up to 200 of the caller's soft-deleted measurement rows in one call — the delete-Undo
|
||
counterpart to `/api/measurements/bulk-delete`. Idempotent via the `Idempotency-Key` header; rate-limited
|
||
`measurements:restore:<userId>`. Forged / foreign / not-deleted ids are silently skipped — `restored` is the
|
||
count of rows actually un-tombstoned. The restored rows re-surface in `/api/sync/changes` as upserts.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MeasurementsRestoreRequest"
|
||
responses:
|
||
"200":
|
||
description: Restore processed.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MeasurementsRestoreResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/sleep/night:
|
||
get:
|
||
tags:
|
||
- Measurements
|
||
summary: Reconstructed sleep night (hypnogram source, v1.11.5)
|
||
description: "Returns one night's reconstructed sleep session for the phase-progression (hypnogram) view: the canonical
|
||
source's stage segments (each with an absolute start/end), the per-stage breakdown, asleep/in-bed/awake totals,
|
||
the mid-sleep awakenings count, and same-wake-day naps surfaced separately. Session-clustered, keyed by the
|
||
local wake day, collapsed to one source via the sleep priority ladder so two sources never overlay. `date`
|
||
omitted returns the most recent night. A read-only view over existing per-stage SLEEP_DURATION rows — no schema,
|
||
no new measurement type."
|
||
parameters:
|
||
- in: query
|
||
name: date
|
||
schema:
|
||
description: Wake-day key (YYYY-MM-DD). Omit for the most recent night.
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
description: Wake-day key (YYYY-MM-DD). Omit for the most recent night.
|
||
responses:
|
||
"200":
|
||
description: Reconstructed night.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/SleepNightResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
/api/workouts/batch:
|
||
post:
|
||
tags:
|
||
- Measurements
|
||
summary: Typed workout batch ingest (v1.4.25 W16b)
|
||
description: Server-side ingest endpoint for HKWorkout records (iOS) and Withings activity rows (server-to-server). Up
|
||
to 100 workouts per call; nested route geometry (GeoJSON LineString) is capped at 20 000 points and stored in a
|
||
1:1 `WorkoutRoute` row keyed by `workoutId`. Idempotent via the `Idempotency-Key` header (replay window 24h).
|
||
Per-entry status (`inserted | duplicate | skipped`) lets the iOS sync cursor checkpoint accurately. The request
|
||
body ceiling is 5 MB enforced at the HTTP layer — clients above the ceiling receive a 413 with
|
||
`workout.batch.payload_too_large`.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
workouts:
|
||
minItems: 1
|
||
maxItems: 100
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
sportType:
|
||
type: string
|
||
enum:
|
||
- walking
|
||
- running
|
||
- cycling
|
||
- hiking
|
||
- swimming
|
||
- rowing
|
||
- elliptical
|
||
- stairClimber
|
||
- yoga
|
||
- mindAndBody
|
||
- strength
|
||
- hiit
|
||
- dance
|
||
- golf
|
||
- tennis
|
||
- basketball
|
||
- soccer
|
||
- crossTraining
|
||
- mixedCardio
|
||
- other
|
||
startedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
endedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
totalEnergyKcal:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 50000
|
||
totalDistanceM:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 1000000
|
||
avgHeartRate:
|
||
type: integer
|
||
minimum: 20
|
||
maximum: 300
|
||
maxHeartRate:
|
||
type: integer
|
||
minimum: 20
|
||
maximum: 300
|
||
minHeartRate:
|
||
type: integer
|
||
minimum: 20
|
||
maximum: 300
|
||
stepCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 200000
|
||
elevationM:
|
||
type: number
|
||
minimum: -500
|
||
maximum: 10000
|
||
pauseDurationSec:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 86400
|
||
source:
|
||
default: MANUAL
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
externalId:
|
||
type: string
|
||
maxLength: 128
|
||
externalSourceVersion:
|
||
type: string
|
||
maxLength: 64
|
||
metadata:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
route:
|
||
type: object
|
||
properties:
|
||
geometry:
|
||
type: object
|
||
properties:
|
||
type:
|
||
type: string
|
||
const: LineString
|
||
coordinates:
|
||
minItems: 2
|
||
maxItems: 20000
|
||
type: array
|
||
items:
|
||
type: array
|
||
prefixItems:
|
||
- type: number
|
||
minimum: -180
|
||
maximum: 180
|
||
- type: number
|
||
minimum: -90
|
||
maximum: 90
|
||
items:
|
||
type: number
|
||
required:
|
||
- type
|
||
- coordinates
|
||
sampleTimestamps:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
t:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
speedMs:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 50
|
||
hr:
|
||
type: integer
|
||
minimum: 20
|
||
maximum: 300
|
||
required:
|
||
- t
|
||
required:
|
||
- geometry
|
||
samples:
|
||
minItems: 1
|
||
maxItems: 30000
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
t:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
hr:
|
||
type: integer
|
||
minimum: 20
|
||
maximum: 300
|
||
speedMs:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 50
|
||
power:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 3000
|
||
cadence:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 400
|
||
required:
|
||
- t
|
||
required:
|
||
- sportType
|
||
- startedAt
|
||
- endedAt
|
||
required:
|
||
- workouts
|
||
responses:
|
||
"200":
|
||
description: Batch processed.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/BatchWorkoutsResponse"
|
||
"400":
|
||
description: Batch validation failed (over-cap, schema reject, or oversized route).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"401": *a1
|
||
"413":
|
||
description: Request body exceeds the 5 MB ceiling.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
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": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
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": *a3
|
||
"429": *a4
|
||
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": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
/api/medications/extract:
|
||
post:
|
||
tags:
|
||
- Medications
|
||
summary: Extract scheduling fields from a free-text medication description
|
||
description: Runs the user's free-text description through the Coach provider chain and returns a citation-guarded
|
||
partial payload the wizard merges onto whatever the user already typed. `name` and `dose` are dropped when not
|
||
substring-matched in the original text so the wizard cannot land a hallucinated brand or dose. `cadenceKind` /
|
||
`doseUnit` / `weekdays` are closed enums; numeric fields are clamped. Rate-limited 10 requests / 5 minutes /
|
||
user, gated against the daily Coach token budget.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MedicationExtractRequest"
|
||
responses:
|
||
"200":
|
||
description: Citation-guarded partial extraction.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MedicationExtractResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
"502":
|
||
description: Upstream provider returned an empty, unparseable, or off-schema reply.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"503":
|
||
description: No AI provider configured for the calling user (or operator).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
/api/medications/{id}/cadence:
|
||
get:
|
||
tags:
|
||
- Medications
|
||
summary: Cadence + compliance read for a medication
|
||
description: Returns the expected-vs-actual dose timeline for the requested window plus the four compliance chip values
|
||
that drive the detail-page section. Pure computation — no writes. Day boundaries are resolved in the user's IANA
|
||
timezone so a Tokyo user and a Berlin user see the same chips for the same medication. The `days` query
|
||
parameter caps at 180.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
- in: query
|
||
name: days
|
||
schema:
|
||
description: Window size in days (default 30, max 180).
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 180
|
||
description: Window size in days (default 30, max 180).
|
||
responses:
|
||
"200":
|
||
description: Cadence response.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/GetMedicationCadenceResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/medications/compliance:
|
||
get:
|
||
tags:
|
||
- Medications
|
||
summary: Batched adherence read for every medication of the caller
|
||
description: Returns one compact adherence row per medication the caller owns (active + paused), ordered by `createdAt
|
||
DESC` — the single round trip the medication cards consume instead of fanning out one
|
||
`/api/medications/{id}/compliance` request per card. Each row carries the 7-/30-day summaries and the
|
||
cadence-scaled display block; the per-day grid stays on the per-medication endpoint. Pure computation — no
|
||
writes. Served through the same per-medication server cache as the per-id read, so the two endpoints warm each
|
||
other.
|
||
responses:
|
||
"200":
|
||
description: One adherence row per medication.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ListMedicationComplianceResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/medications/{id}/compliance:
|
||
get:
|
||
tags:
|
||
- Medications
|
||
summary: Adherence read for a medication
|
||
description: Returns the 7- and 30-day adherence summaries, the per-day compliance grid for the history glyph track, and
|
||
the two-row display block. Pure computation — no writes. Day boundaries are resolved in the user's IANA
|
||
timezone, and the expected-dose denominator is cadence-aware (RRULE / rolling / one-shot / PRN / cyclic) and
|
||
clamped to the medication's `createdAt`. Read `compliance30` for the headline 30-day taken-vs-expected
|
||
percentage; build the per-day glyph track from `dailyCompliance` (draw a cell only where `due === true`).
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"200":
|
||
description: Compliance response.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/GetMedicationComplianceResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/medications/{id}/schedule-revisions:
|
||
get:
|
||
tags:
|
||
- Medications
|
||
summary: List a medication's archived schedule eras
|
||
description: Returns every archived schedule era (newest first) plus `currentSince`, the instant the live plan took
|
||
over. The dose-history ledger and compliance tallies already mint past days against these eras; this read powers
|
||
the Zeitplan-tab history timeline.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"200":
|
||
description: Era list.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ListScheduleRevisionsResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
post:
|
||
tags:
|
||
- Medications
|
||
summary: Append a manual schedule era (pre-tracking history)
|
||
description: Records that the medication dosed at the given daily times during `[validFrom, validUntil)` — history from
|
||
before the schedule was edited in the app. The era must end at or before the start of the live plan and must not
|
||
overlap an existing era; violations return 422. The snapshot is shaped exactly like a write-path archive
|
||
(`FREQ=DAILY`, window pulled to the min/max of the times), so every historical surface reads it transparently.
|
||
Audits as `medication.schedule_revision.created`.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CreateScheduleRevisionRequest"
|
||
responses:
|
||
"201":
|
||
description: Manual era created.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CreateScheduleRevisionResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/medications/{id}/schedule-revisions/{revisionId}:
|
||
patch:
|
||
tags:
|
||
- Medications
|
||
summary: Correct a recorded schedule era
|
||
description: "Replaces an era's bounds and daily times. A `MANUAL` era updates in place; an `ARCHIVED` era stays as the
|
||
immutable audit record and the correction is minted as a superseding `MANUAL` revision that takes its place in
|
||
every historical surface (the response carries the correction's id). Validation mirrors the sibling POST: the
|
||
era must end at or before the start of the live plan and must not overlap another active era; violations return
|
||
422. An era that has already been corrected refuses with 409. Audits as `medication.schedule_revision.updated`."
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
- in: path
|
||
name: revisionId
|
||
schema:
|
||
type: string
|
||
required: true
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/UpdateScheduleRevisionRequest"
|
||
responses:
|
||
"200":
|
||
description: Era corrected.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/UpdateScheduleRevisionResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication or revision not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"409":
|
||
description: The revision has already been superseded by a correction.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
delete:
|
||
tags:
|
||
- Medications
|
||
summary: Delete a manually added schedule era
|
||
description: "Removes a `MANUAL` era — one appended through the sibling POST, or a correction minted by PATCH (deleting
|
||
a correction restores the archived original it superseded). Write-path archives (`source: ARCHIVED`) are
|
||
immutable history and refuse with 409. Audits as `medication.schedule_revision.deleted`."
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
- in: path
|
||
name: revisionId
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"200":
|
||
description: Deletion succeeded.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/DeleteScheduleRevisionResponse"
|
||
"401": *a1
|
||
"404":
|
||
description: Medication or revision not found (or owned by another user).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"409":
|
||
description: The revision is a write-path archive (`ARCHIVED`) and cannot be deleted.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
put:
|
||
tags:
|
||
- Auth
|
||
summary: Replace per-user Coach prompt-tuning preferences
|
||
description: Persists the supplied prefs. Body is validated against `CoachPrefs`; missing keys fall back to the
|
||
documented defaults.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
tone:
|
||
default: warm
|
||
type: string
|
||
enum:
|
||
- warm
|
||
- neutral
|
||
- concise
|
||
verbosity:
|
||
default: default
|
||
type: string
|
||
enum:
|
||
- brief
|
||
- default
|
||
- detailed
|
||
excludeMetrics:
|
||
default: []
|
||
maxItems: 11
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- bp
|
||
- weight
|
||
- pulse
|
||
- mood
|
||
- compliance
|
||
- hrv
|
||
- sleep
|
||
- resting_hr
|
||
- steps
|
||
- medications
|
||
- anthropometrics
|
||
showEvidenceByDefault:
|
||
default: false
|
||
type: boolean
|
||
defaultWindow:
|
||
default: allTime
|
||
type: string
|
||
enum:
|
||
- last7days
|
||
- last30days
|
||
- last90days
|
||
- allTime
|
||
dataClusters:
|
||
maxItems: 10
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- cardio
|
||
- body
|
||
- activity
|
||
- workouts
|
||
- sleep
|
||
- mood
|
||
- glucose
|
||
- medication
|
||
- mobility
|
||
- environment
|
||
responses:
|
||
"200":
|
||
description: Saved preferences echoed back.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/PutCoachPrefsResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
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": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
put:
|
||
tags:
|
||
- Auth
|
||
summary: Replace per-user source priority
|
||
description: Persists the supplied priority. Body is validated against `SourcePriority`; missing keys read as
|
||
`DEFAULT_SOURCE_PRIORITY` at the call site.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
skinTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
respiratoryRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
recovery:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
metricPriority:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
skinTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
respiratoryRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
recovery:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
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": *a3
|
||
"429": *a4
|
||
/api/user/avatar:
|
||
post:
|
||
tags:
|
||
- Auth
|
||
summary: Upload the calling user's avatar
|
||
description: "Multipart upload (field `file`). Accepts image/jpeg, image/png, image/webp; rejects anything else at the
|
||
magic-byte sniff (the multipart Content-Type header is informational only). Hard caps: 2 MiB body, 2048×2048
|
||
dimensions. Replaces the v1.4.22 Gravatar leak by storing the bytes on the User row + serving them from
|
||
same-origin."
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
multipart/form-data:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
file:
|
||
type: string
|
||
description: Binary image payload (JPEG / PNG / WebP). Hard-capped at 2 MiB and 2048×2048.
|
||
required:
|
||
- file
|
||
responses:
|
||
"201":
|
||
description: Avatar saved. `avatarUrl` is the same value the /me payload returns; the `?v=` suffix busts client caches
|
||
on re-upload.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/UploadAvatarResponse"
|
||
"401": *a1
|
||
"413":
|
||
description: Upload exceeds the 2 MiB byte limit or the 2048×2048 dimension limit.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"415":
|
||
description: Image format is not one of JPEG / PNG / WebP (magic-byte sniff).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
delete:
|
||
tags:
|
||
- Auth
|
||
summary: Clear the calling user's avatar
|
||
description: Resets the avatar columns to null. Idempotent — a delete on an already-empty row returns 204 with no
|
||
audit-log row.
|
||
responses:
|
||
"204":
|
||
description: Avatar cleared (or already empty).
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/user/avatar/{id}:
|
||
get:
|
||
tags:
|
||
- Auth
|
||
summary: Serve a user's avatar bytes
|
||
description: Owner-scoped. The `{id}` path segment must match the calling user's id — cross-user reads return 403.
|
||
Response body is the raw image bytes with the persisted `Content-Type`; the /me payload appends
|
||
`?v={updatedAtMs}` so clients can cache aggressively.
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
description: User id (must match the calling user).
|
||
required: true
|
||
description: User id (must match the calling user).
|
||
responses:
|
||
"200":
|
||
description: Avatar bytes.
|
||
content:
|
||
image/jpeg:
|
||
schema:
|
||
type: string
|
||
description: Raw JPEG bytes.
|
||
image/png:
|
||
schema:
|
||
type: string
|
||
description: Raw PNG bytes.
|
||
image/webp:
|
||
schema:
|
||
type: string
|
||
description: Raw WebP bytes.
|
||
"401": *a1
|
||
"403":
|
||
description: Caller is not the avatar's owner.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"404":
|
||
description: The user has no uploaded avatar.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ErrorEnvelope"
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/user/profile:
|
||
get:
|
||
tags:
|
||
- Auth
|
||
summary: Read the calling user's profile
|
||
description: Flattened profile fields for the native client. Aliased over the same data exposed by /api/auth/me. The
|
||
KVNR is decrypted server-side; the IKNR (v1.8.6) is returned plaintext.
|
||
responses:
|
||
"200":
|
||
description: The calling user's profile.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/GetProfileResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
patch:
|
||
tags:
|
||
- Auth
|
||
summary: Update the calling user's profile
|
||
description: Partial profile update — every field is optional. An explicit null (or empty string) clears a field. A
|
||
non-empty `insurerIkNumber` must be exactly 9 digits (422 otherwise); a non-empty `insuranceNumber` (KVNR) must
|
||
pass the mod-10 check. `userId` is never accepted from the body.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/ProfileUpdateRequest"
|
||
responses:
|
||
"200":
|
||
description: Saved profile echoed back.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/PatchProfileResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/layout:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Read the calling user's Insights tile layout
|
||
description: Returns the per-user Insights tile layout (visibility + order). Falls back to the default layout when the
|
||
user has not customised it. Mirrors the dashboard-widgets contract.
|
||
responses:
|
||
"200":
|
||
description: The resolved layout (custom or default).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsLayout"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
put:
|
||
tags:
|
||
- Insights
|
||
summary: Replace the calling user's Insights tile layout
|
||
description: Persists the full tile layout. The normalised layout is returned. Invalid bodies return the multi-issue 422
|
||
envelope, matching the dashboard-widgets route.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsLayoutBody"
|
||
responses:
|
||
"200":
|
||
description: Layout saved; the normalised layout is echoed back.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsLayoutSaved"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
delete:
|
||
tags:
|
||
- Insights
|
||
summary: Reset the calling user's Insights tile layout
|
||
description: Clears the persisted layout and returns the default layout. Idempotent.
|
||
responses:
|
||
"200":
|
||
description: Layout reset; the default layout is returned.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsLayoutReset"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/coach/facts:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: List the caller's durable Coach facts
|
||
description: v1.11.1 — returns the active facts the Coach has extracted about the caller (highest-confidence then newest
|
||
first), each decrypted on the fly. The GDPR 'what do you know about me' surface. Coach-gated
|
||
(`requireAssistantSurface("coach")`). Auth via cookie or Bearer; the owner is always narrowed from the session,
|
||
never the body. Undecryptable rows are omitted rather than failing the read.
|
||
responses:
|
||
"200":
|
||
description: The caller's active facts.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CoachFactsList"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
delete:
|
||
tags:
|
||
- Insights
|
||
summary: Forget all of the caller's Coach facts
|
||
description: "v1.11.1 — bulk 'forget what you know about me': soft-deletes every active fact for the caller and returns
|
||
the count cleared. Idempotent (a second call clears 0). Coach-gated. Auth via cookie or Bearer."
|
||
responses:
|
||
"200":
|
||
description: All active facts cleared; the count is returned.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CoachFactsCleared"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/coach/facts/{id}:
|
||
delete:
|
||
tags:
|
||
- Insights
|
||
summary: Forget one Coach fact
|
||
description: "v1.11.1 — soft-deletes a single fact owned by the caller. An unknown / cross-user / already-deleted id is
|
||
an idempotent no-op returning `{ deleted: false }`, never revealing whether the id exists under another account.
|
||
Coach-gated. Auth via cookie or Bearer."
|
||
responses:
|
||
"200":
|
||
description: "The fact was soft-deleted (`deleted: true`) or the id matched nothing the caller owns (`deleted: false`)."
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CoachFactDeleted"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/coach/about-me:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Read the caller's self-context
|
||
description: v1.16.0 — returns the structured self-context (free text plus chronic conditions, allergies, coach focus)
|
||
the Coach system prompt and the daily briefing inject as a delimited, user-provided context block, alongside any
|
||
pending clarifying questions. Every field is stored encrypted at rest; an undecryptable payload reads as null
|
||
(fail closed). Auth via cookie or Bearer; the owner is always narrowed from the session.
|
||
responses:
|
||
"200":
|
||
description: The stored fields (null when never written / cleared) plus pending clarifying questions.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/GetCoachAboutMeResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
put:
|
||
tags:
|
||
- Insights
|
||
summary: Write (or clear) the caller's self-context
|
||
description: "v1.16.0 — persists the free text (4 000-char cap) and the three structured fields (500-char cap each)
|
||
encrypted at rest; caps are enforced before encryption. Structured fields are optional: omitted leaves the
|
||
stored value untouched, an empty string clears it. After a non-empty save the server derives up to 3 clarifying
|
||
questions (AI when a provider and the daily Coach token budget allow, deterministic completion hints otherwise)
|
||
and returns them as `pendingQuestions`. Rate-limited per user."
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
aboutMe:
|
||
type: string
|
||
maxLength: 4000
|
||
conditions:
|
||
type: string
|
||
maxLength: 500
|
||
allergies:
|
||
type: string
|
||
maxLength: 500
|
||
coachFocus:
|
||
type: string
|
||
maxLength: 500
|
||
required:
|
||
- aboutMe
|
||
responses:
|
||
"200":
|
||
description: The effective (trimmed) state echoed back plus the freshly derived pending questions.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/PutCoachAboutMeResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/coach/about-me/questions:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Read the pending clarifying questions
|
||
description: v1.16.0 — the up-to-3 clarifying questions derived after the last self-context save. The Coach composer
|
||
renders them as tappable suggestion chips. Stored encrypted; an undecryptable payload reads as an empty list
|
||
(fail closed).
|
||
responses:
|
||
"200":
|
||
description: The pending questions (possibly empty).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/GetCoachAboutMeQuestionsResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
delete:
|
||
tags:
|
||
- Insights
|
||
summary: Dismiss pending clarifying questions
|
||
description: v1.16.0 — dismisses one question (body `{ question }`, exact match) or all of them (empty body). Tapping a
|
||
chip in the Coach composer inserts the question into the chat input and dismisses it here.
|
||
requestBody:
|
||
required: false
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
question:
|
||
description: Exact question text to dismiss. Omitted = dismiss all.
|
||
type: string
|
||
responses:
|
||
"200":
|
||
description: The remaining questions after the dismissal.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/DeleteCoachAboutMeQuestionsResponse"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/admin/invites:
|
||
get:
|
||
tags:
|
||
- Admin
|
||
summary: List registration invites
|
||
description: v1.16.0 — every invite with creator / consumer usernames, use counters, soft-revocation timestamp, and the
|
||
full per-signup redemption ledger (`redemptions`; `consumer` only carries the LAST account on a multi-use
|
||
invite). Metadata only — the raw token is never derivable from this endpoint (only its HMAC hash is persisted).
|
||
Admin session cookie required; Bearer tokens cannot reach admin endpoints.
|
||
responses:
|
||
"200":
|
||
description: All invites, newest first.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/AdminInviteList"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
post:
|
||
tags:
|
||
- Admin
|
||
summary: Mint a registration invite
|
||
description: v1.15.20 — creates an invite that admits a signup even while open registration is disabled. The raw
|
||
`hlv_<64hex>` token and the composed registration URL appear EXACTLY ONCE in this response; only the keyed hash
|
||
is persisted. Lifetime is capped at 30 days; `maxUses` makes multi-use invites possible (consumption is an
|
||
atomic guarded increment). Admin session cookie required.
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
expiresInDays:
|
||
default: 7
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 30
|
||
maxUses:
|
||
default: 1
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 50
|
||
responses:
|
||
"201":
|
||
description: The minted invite, including the one-time raw token.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/AdminInviteCreated"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/admin/invites/{id}:
|
||
delete:
|
||
tags:
|
||
- Admin
|
||
summary: Revoke a registration invite
|
||
description: "v1.16.0 — soft-revokes the invite (`revokedAt`): the row keeps its redemption history visible in the admin
|
||
table while the consume path refuses the link like an expired one. Idempotent: an unknown or already-revoked id
|
||
returns `{ revoked: false }` instead of 404. Admin session cookie required."
|
||
parameters:
|
||
- in: path
|
||
name: id
|
||
schema:
|
||
type: string
|
||
required: true
|
||
responses:
|
||
"200":
|
||
description: "The invite was revoked (`revoked: true`) or nothing matched (`revoked: false`)."
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/AdminInviteRevoked"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
/api/dashboard/snapshot:
|
||
get:
|
||
tags:
|
||
- Dashboard
|
||
summary: Unified dashboard first-paint snapshot
|
||
description: "Assembles every above-the-fold tile field in one round-trip from the rollup / mood / widget helpers plus a
|
||
read-only lift of the pre-generated daily briefing. Two-phase: `tiles` always present, `extras` nullable on a
|
||
rollup-coverage miss. No LLM is reachable from this path. Cookie or Bearer auth."
|
||
responses:
|
||
"200":
|
||
description: Dashboard snapshot.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/DashboardSnapshotResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/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": *a3
|
||
"429": *a4
|
||
/api/insights/pregenerate:
|
||
post:
|
||
tags:
|
||
- Insights
|
||
summary: Warm all AI assessments for the calling user
|
||
description: v1.8.7.1 — enqueue a full warm of every AI assessment for the authenticated user (comprehensive insight +
|
||
the seven specialised status cards + every data-bearing generic metric assessment) in the active locale, so the
|
||
read-only status GETs serve cached text instantly. Returns immediately; the generation runs out of band on the
|
||
worker. Empty metrics and provider-less accounts never trigger an LLM call. Short anti-spam bucket
|
||
(`insights-warm:<userId>`, one warm per 3 minutes) → 429 on a tight loop. Auth via cookie or Bearer; `userId` is
|
||
taken from the session, never the body.
|
||
requestBody:
|
||
required: false
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsPregenerateRequest"
|
||
responses:
|
||
"200":
|
||
description: Warm accepted and enqueued. The work runs on the worker; poll the read-only status routes for the text.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/InsightsPregenerateResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/analytics/range:
|
||
get:
|
||
tags:
|
||
- Analytics
|
||
summary: Single-metric period-over-period range delta
|
||
description: v1.9.0 — returns the current-window aggregate, the previous comparable window, and the composed delta for
|
||
ONE metric type over a `7d` / `30d` / `90d` / `1y` range. Single-type by construction (the metric page is
|
||
single-metric), so the read is one rollup-tier call covering the trailing 2N days sliced into the two halves —
|
||
no per-type fan-out. Additive route; the `/api/analytics` envelope is unchanged. Auth via cookie or Bearer.
|
||
parameters:
|
||
- in: query
|
||
name: type
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- WEIGHT
|
||
- BLOOD_PRESSURE_SYS
|
||
- BLOOD_PRESSURE_DIA
|
||
- PULSE
|
||
- BODY_FAT
|
||
- SLEEP_DURATION
|
||
- ACTIVITY_STEPS
|
||
- BLOOD_GLUCOSE
|
||
- TOTAL_BODY_WATER
|
||
- BONE_MASS
|
||
- OXYGEN_SATURATION
|
||
- HEART_RATE_VARIABILITY
|
||
- RESTING_HEART_RATE
|
||
- ACTIVE_ENERGY_BURNED
|
||
- FLIGHTS_CLIMBED
|
||
- WALKING_RUNNING_DISTANCE
|
||
- VO2_MAX
|
||
- BODY_TEMPERATURE
|
||
- FAT_FREE_MASS
|
||
- FAT_MASS
|
||
- MUSCLE_MASS
|
||
- SKIN_TEMPERATURE
|
||
- PULSE_WAVE_VELOCITY
|
||
- VASCULAR_AGE
|
||
- VISCERAL_FAT
|
||
- AUDIO_EXPOSURE_ENV
|
||
- AUDIO_EXPOSURE_HEADPHONE
|
||
- TIME_IN_DAYLIGHT
|
||
- WALKING_STEADINESS
|
||
- AUDIO_EXPOSURE_EVENT
|
||
- RESPIRATORY_RATE
|
||
- BODY_MASS_INDEX
|
||
- LEAN_BODY_MASS
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
- IRREGULAR_RHYTHM_NOTIFICATION
|
||
- HIGH_HEART_RATE_EVENT
|
||
- LOW_HEART_RATE_EVENT
|
||
- WALKING_STEADINESS_EVENT
|
||
- BREATHING_DISTURBANCE_EVENT
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
- HRV_RMSSD
|
||
- DAY_STRAIN
|
||
- WORKOUT_STRAIN
|
||
- SLEEP_PERFORMANCE
|
||
- SLEEP_EFFICIENCY
|
||
- SLEEP_CONSISTENCY
|
||
- SLEEP_NEED
|
||
- ENERGY_EXPENDITURE_KJ
|
||
- AVERAGE_HEART_RATE
|
||
- MAX_HEART_RATE
|
||
- SLEEP_DISTURBANCE_COUNT
|
||
description: "The measurement type to read (single metric — no fan-out). Closed enum: an unknown type 422s."
|
||
required: true
|
||
description: "The measurement type to read (single metric — no fan-out). Closed enum: an unknown type 422s."
|
||
- in: query
|
||
name: range
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- 7d
|
||
- 30d
|
||
- 90d
|
||
- 1y
|
||
description: "Trailing window: `7d` / `30d` / `90d` / `1y`. The previous comparable window is the equally-sized span
|
||
immediately before it."
|
||
required: true
|
||
description: "Trailing window: `7d` / `30d` / `90d` / `1y`. The previous comparable window is the equally-sized span
|
||
immediately before it."
|
||
responses:
|
||
"200":
|
||
description: Current + previous window aggregates and the delta.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/AnalyticsRangeResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/blood-pressure-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Blood-pressure assessment
|
||
description: "Data-driven plain-language assessment of the user's recent blood-pressure readings. Read-only: a cache
|
||
miss warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via
|
||
cookie or Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/BloodPressureStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/pulse-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Pulse assessment
|
||
description: "Data-driven plain-language assessment of the user's recent resting-pulse readings. Read-only: a cache miss
|
||
warms a generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie
|
||
or Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/PulseStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/weight-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Weight assessment
|
||
description: "Data-driven plain-language assessment of the user's recent weight trend. Read-only: a cache miss warms a
|
||
generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or
|
||
Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/WeightStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/bmi-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: BMI assessment
|
||
description: "Data-driven plain-language assessment of the user's body-mass index. Read-only: a cache miss warms a
|
||
generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or
|
||
Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/BmiStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/mood-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Mood assessment
|
||
description: "Data-driven plain-language assessment of the user's recent mood entries. Read-only: a cache miss warms a
|
||
generation out of band and serves the last-good text meanwhile (stale-while-revalidate). Auth via cookie or
|
||
Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MoodStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/medication-compliance-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Medication-compliance assessment
|
||
description: "Data-driven plain-language assessment of the user's medication compliance — an overall `summary` plus a
|
||
per-medication note array. Read-only: a cache miss warms a generation out of band and serves the last-good
|
||
envelope meanwhile (stale-while-revalidate). Auth via cookie or Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Compliance assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MedicationComplianceStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/metric-status:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Generic per-HealthKit-metric assessment
|
||
description: "v1.8.7.1 — data-driven plain-language assessment for any registered HealthKit metric (resting heart rate,
|
||
sleep, glucose, body composition, gait, audio exposure, …). One generic route covering ~30 metric pages via
|
||
archetype prompt templates + per-metric metadata. Read-only: a cache miss warms a generation out of band and
|
||
serves the last-good text meanwhile (stale-while-revalidate). An unknown `metric` 422s against the closed
|
||
registry enum. Auth via cookie or Bearer."
|
||
parameters:
|
||
- in: query
|
||
name: metric
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- RESTING_HEART_RATE
|
||
- HEART_RATE_VARIABILITY
|
||
- OXYGEN_SATURATION
|
||
- RESPIRATORY_RATE
|
||
- BODY_TEMPERATURE
|
||
- SKIN_TEMPERATURE
|
||
- BLOOD_GLUCOSE
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- PULSE_WAVE_VELOCITY
|
||
- VASCULAR_AGE
|
||
- STEPS
|
||
- ACTIVE_ENERGY
|
||
- FLIGHTS_CLIMBED
|
||
- WALKING_RUNNING_DISTANCE
|
||
- TIME_IN_DAYLIGHT
|
||
- VO2_MAX
|
||
- TOTAL_BODY_WATER
|
||
- BONE_MASS
|
||
- FAT_FREE_MASS
|
||
- FAT_MASS
|
||
- MUSCLE_MASS
|
||
- LEAN_BODY_MASS
|
||
- VISCERAL_FAT
|
||
- WALKING_STEADINESS
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- AUDIO_EXPOSURE_ENV
|
||
- AUDIO_EXPOSURE_HEADPHONE
|
||
- AUDIO_EXPOSURE_EVENT
|
||
- SLEEP_DURATION
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
description: "HealthKit metric id to assess (e.g. RESTING_HEART_RATE, SLEEP_DURATION). Closed enum: an unknown id 422s.
|
||
The seven specialised metrics are served by their own routes and are not accepted here."
|
||
required: true
|
||
description: "HealthKit metric id to assess (e.g. RESTING_HEART_RATE, SLEEP_DURATION). Closed enum: an unknown id 422s.
|
||
The seven specialised metrics are served by their own routes and are not accepted here."
|
||
- in: query
|
||
name: locale
|
||
schema:
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: Optional UI-locale override; defaults to the session locale.
|
||
responses:
|
||
"200":
|
||
description: Assessment envelope (fresh, cached, or preparing).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/MetricStatusResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/derived:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Derived wellness metric (compute-once)
|
||
description: v1.10.0 — the compute-once `Derived<T>` value for any registered derived wellness metric (personal
|
||
typical-range vitals baseline, cardio-fitness band, vascular-age delta, sleep score, readiness,
|
||
coincident-deviation flag). One generic route over a closed registry enum; an unknown `metric` 422s. Pure
|
||
compute over the rollup tier with a per-type live fallback on a coverage miss — no LLM call, no narrative, no
|
||
cache table. Returns the flat `Derived<T>` union so the native client can decode one stable shape and combine
|
||
values across metrics. Auth via cookie or Bearer.
|
||
parameters:
|
||
- in: query
|
||
name: metric
|
||
schema:
|
||
type: string
|
||
enum:
|
||
- VITALS_BASELINE
|
||
- FITNESS_AGE
|
||
- VASCULAR_AGE_DELTA
|
||
- SLEEP_SCORE
|
||
- READINESS
|
||
- COINCIDENT_DEVIATION
|
||
- HRV_BALANCE
|
||
- BMI
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
- WRIST_TEMPERATURE_BASELINE
|
||
- STAIR_ASCENT_SPEED_BASELINE
|
||
- STAIR_DESCENT_SPEED_BASELINE
|
||
- SIX_MINUTE_WALK_BAND
|
||
- TRAJECTORY
|
||
description: "Derived-metric id to compute (e.g. VITALS_BASELINE, FITNESS_AGE, VASCULAR_AGE_DELTA, HRV_BALANCE, BMI,
|
||
READINESS). Closed enum: an unknown id 422s. Metrics whose compute has not yet landed return an
|
||
`insufficient` value with reason `not_implemented`."
|
||
required: true
|
||
description: "Derived-metric id to compute (e.g. VITALS_BASELINE, FITNESS_AGE, VASCULAR_AGE_DELTA, HRV_BALANCE, BMI,
|
||
READINESS). Closed enum: an unknown id 422s. Metrics whose compute has not yet landed return an
|
||
`insufficient` value with reason `not_implemented`."
|
||
- in: query
|
||
name: type
|
||
schema:
|
||
description: For VITALS_BASELINE only — the single vital to band (defaults to RESTING_HEART_RATE). Ignored by
|
||
composites. An unsupported value yields an `insufficient` value rather than a 422 so iOS metric
|
||
combinations stay forgiving.
|
||
type: string
|
||
enum:
|
||
- RESTING_HEART_RATE
|
||
- HEART_RATE_VARIABILITY
|
||
- RESPIRATORY_RATE
|
||
- OXYGEN_SATURATION
|
||
- BODY_TEMPERATURE
|
||
- SKIN_TEMPERATURE
|
||
- BLOOD_PRESSURE_SYS
|
||
- BLOOD_PRESSURE_DIA
|
||
- BLOOD_GLUCOSE
|
||
- PULSE
|
||
- WEIGHT
|
||
description: For VITALS_BASELINE only — the single vital to band (defaults to RESTING_HEART_RATE). Ignored by
|
||
composites. An unsupported value yields an `insufficient` value rather than a 422 so iOS metric combinations
|
||
stay forgiving.
|
||
responses:
|
||
"200":
|
||
description: The flat derived-metric value (ok or insufficient).
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/DerivedMetricResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/derived/batch:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Derived wellness metrics (batched compute-once)
|
||
description: v1.10.0 — resolve several derived wellness metrics in ONE request. The `metrics` CSV names the metrics (a
|
||
`metric:type` token sub-targets a VITALS_BASELINE vital); the server fans out under a bounded limiter with the
|
||
profile loaded once and returns a map keyed by the per-request token. Collapses the Insights cold-mount fan-out
|
||
of 14+ independent single-metric requests — the pool-starvation class that surfaces as a hang-then-recover. The
|
||
single-metric route stays for the per-score detail pages. Auth via cookie or Bearer.
|
||
parameters:
|
||
- in: query
|
||
name: metrics
|
||
schema:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 1024
|
||
description: Comma-separated derived-metric tokens. Each is a `<DERIVED_METRIC_ID>` or
|
||
`<DERIVED_METRIC_ID>:<MeasurementType>` (the colon sub-targets a VITALS_BASELINE vital). An unknown id
|
||
422s; at most 24 tokens; duplicates collapse.
|
||
required: true
|
||
description: Comma-separated derived-metric tokens. Each is a `<DERIVED_METRIC_ID>` or
|
||
`<DERIVED_METRIC_ID>:<MeasurementType>` (the colon sub-targets a VITALS_BASELINE vital). An unknown id 422s;
|
||
at most 24 tokens; duplicates collapse.
|
||
responses:
|
||
"200":
|
||
description: The map of derived-metric values, keyed by token.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/DerivedBatchResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
/api/insights/correlations:
|
||
get:
|
||
tags:
|
||
- Insights
|
||
summary: Correlation discovery (FDR-controlled)
|
||
description: v1.10.0 — scans a curated behaviour × outcome matrix (daylight / mood / glucose / BP / steps × sleep / HRV
|
||
/ resting HR / weight), lag-joins each behaviour day to the next day's outcome, runs Pearson with the exact
|
||
Student-t p-value, and applies Benjamini-Hochberg FDR control across every tested pair. Only
|
||
statistically-defensible pairs surface, each carrying n, r, p, and the BH-adjusted q. Descriptive, never causal.
|
||
Gated by the operator `correlations` assistant surface. Auth via cookie or Bearer.
|
||
responses:
|
||
"200":
|
||
description: The discovered correlations + the tested-pair count.
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: "#/components/schemas/CorrelationDiscoveryResponseEnvelope"
|
||
"401": *a1
|
||
"422": *a3
|
||
"429": *a4
|
||
components:
|
||
schemas:
|
||
PasskeyLoginVerifyRequest:
|
||
type: object
|
||
properties:
|
||
challengeId:
|
||
type: string
|
||
minLength: 1
|
||
description: Server-issued WebAuthn challenge id.
|
||
credential:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
rawId:
|
||
type: string
|
||
type:
|
||
type: string
|
||
const: public-key
|
||
response:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
authenticatorAttachment:
|
||
type: string
|
||
enum:
|
||
- platform
|
||
- cross-platform
|
||
clientExtensionResults:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
required:
|
||
- id
|
||
- rawId
|
||
- type
|
||
- response
|
||
description: SimpleWebAuthn-style assertion response payload.
|
||
required:
|
||
- challengeId
|
||
- credential
|
||
description: Passkey assertion verification. Same native-client token issuance as the password path.
|
||
RefreshTokenRequest:
|
||
type: object
|
||
properties:
|
||
refreshToken:
|
||
type: string
|
||
minLength: 1
|
||
description: Caller-presented refresh token (`hlr_<64hex>`).
|
||
revoke:
|
||
description: When true, revoke the supplied token instead of rotating.
|
||
type: boolean
|
||
required:
|
||
- refreshToken
|
||
description: Exchange a one-time-use refresh token for a fresh access + refresh pair.
|
||
AppleHealthBatchRequest:
|
||
type: object
|
||
properties:
|
||
entries:
|
||
minItems: 1
|
||
maxItems: 500
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/AppleHealthBatchEntry"
|
||
required:
|
||
- entries
|
||
description: Apple Health batch ingest. ≤500 entries per call; idempotent via `Idempotency-Key`.
|
||
AppleHealthBatchEntry:
|
||
type: object
|
||
properties:
|
||
hkIdentifier:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
description: HealthKit identifier (e.g. `HKQuantityTypeIdentifierBodyMass`).
|
||
value:
|
||
type: number
|
||
unit:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 60
|
||
startDate:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
endDate:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
sleepStage:
|
||
description: HKCategoryValueSleepAnalysis codepoint; only for sleep samples.
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 20
|
||
categoryValue:
|
||
description: v1.10.0 — `HKCategoryValue` codepoint for an EVENT-class category sample (irregular-rhythm, high/low-HR,
|
||
walking-steadiness, breathing-disturbance). Carries the device's own classification verdict / severity,
|
||
which the server resolves to a stored `rhythmClassification`. HealthLog stores ONLY the device's result — it
|
||
never re-classifies. Ignored for non-event identifiers.
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 20
|
||
externalId:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
description: HKSample.uuid string — the dedup key.
|
||
externalSourceVersion:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
source:
|
||
description: Source attribution for the row. Defaults to `APPLE_HEALTH` when omitted. Send `MANUAL` to tag rows the user
|
||
entered by hand on-device (e.g. the standalone adopt-on-pair backfill). Part of the `(userId, type, source,
|
||
externalId)` dedup key, so the same externalId under a different source is a distinct row. `WITHINGS` /
|
||
`IMPORT` are not accepted here.
|
||
type: string
|
||
enum:
|
||
- APPLE_HEALTH
|
||
- MANUAL
|
||
deviceType:
|
||
description: Device class mapped from `HKDevice.model`. Used by the analytics aggregator to break ties when the same
|
||
source contributed multiple devices for the same day. Omit (or send null) on legacy clients — the server
|
||
treats it as `unknown` and the picker falls through.
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
- type: "null"
|
||
required:
|
||
- hkIdentifier
|
||
- value
|
||
- unit
|
||
- startDate
|
||
- endDate
|
||
- externalId
|
||
MeasurementsDeleteByExternalIdsRequest:
|
||
type: object
|
||
properties:
|
||
externalIds:
|
||
minItems: 0
|
||
maxItems: 500
|
||
type: array
|
||
items:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
required:
|
||
- externalIds
|
||
description: iOS deletion-sync. Up to 500 externalIds per call; matching rows owned by other users are silently skipped
|
||
(cross-user 404 guard).
|
||
MeasurementsBulkDeleteRequest:
|
||
type: object
|
||
properties:
|
||
ids:
|
||
minItems: 1
|
||
maxItems: 200
|
||
type: array
|
||
items:
|
||
type: string
|
||
minLength: 1
|
||
required:
|
||
- ids
|
||
description: 1..200 measurement ids, scoped to the caller. Forged / foreign ids are silently skipped (no existence leak).
|
||
MeasurementsRestoreRequest:
|
||
type: object
|
||
properties:
|
||
ids:
|
||
minItems: 1
|
||
maxItems: 200
|
||
type: array
|
||
items:
|
||
type: string
|
||
minLength: 1
|
||
required:
|
||
- ids
|
||
description: 1..200 measurement ids, scoped to the caller. Forged / foreign / not-deleted ids are silently skipped (no
|
||
existence leak).
|
||
DeviceRegisterRequest:
|
||
type: object
|
||
properties:
|
||
token:
|
||
type: string
|
||
minLength: 8
|
||
maxLength: 512
|
||
pattern: ^[A-Za-z0-9+/=._:-]+$
|
||
description: Generic device identifier (legacy; APNs token below).
|
||
bundleId:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 128
|
||
locale:
|
||
type: string
|
||
minLength: 2
|
||
maxLength: 16
|
||
appVersion:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 32
|
||
model:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 64
|
||
apnsToken:
|
||
description: Hex-encoded APNs device token. Must be paired with `apnsEnvironment`.
|
||
type: string
|
||
minLength: 8
|
||
maxLength: 256
|
||
pattern: ^[A-Fa-f0-9]+$
|
||
apnsEnvironment:
|
||
description: Gateway the iOS client received `apnsToken` from. Server never auto-detects.
|
||
type: string
|
||
enum:
|
||
- sandbox
|
||
- production
|
||
medicationDelivery:
|
||
description: v1.7.0 per-device medication-delivery override. NULL / omitted = inherit the user-level roaming default.
|
||
"server" forces server APNs for this device; "client" forces local. Stored + echoed; cron suppression stays
|
||
user-level.
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- server
|
||
- client
|
||
- type: "null"
|
||
required:
|
||
- token
|
||
- bundleId
|
||
description: Native device registration. Re-registering an APNs token belonging to another user returns 409
|
||
(cross-user-hijack guard).
|
||
CreateMedicationRequest:
|
||
type: object
|
||
properties:
|
||
name:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 100
|
||
dose:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 50
|
||
category:
|
||
type: string
|
||
enum:
|
||
- BLOOD_PRESSURE
|
||
- VITAMIN
|
||
- SUPPLEMENT
|
||
- PAIN_RELIEF
|
||
- ALLERGY
|
||
- DIGESTIVE
|
||
- THYROID
|
||
- HORMONE
|
||
- SKIN
|
||
- SLEEP_AID
|
||
- DIABETES
|
||
- ANTIBIOTIC
|
||
- OTHER
|
||
treatmentClass:
|
||
type: string
|
||
enum:
|
||
- GENERIC
|
||
- GLP1
|
||
dosesPerUnit:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 100
|
||
deliveryForm:
|
||
type: string
|
||
enum:
|
||
- ORAL
|
||
- INJECTION
|
||
- OTHER
|
||
trackInjectionSites:
|
||
description: Per-medication injection-site tracking opt-in. Default false. Only meaningful for an INJECTION delivery
|
||
form; when true the client prompts (skippably) for the site after a taken dose.
|
||
type: boolean
|
||
allowedInjectionSites:
|
||
description: Per-medication allowed / preferred injection sites. Empty array = no per-medication restriction (every site
|
||
offered). The effective pickable set is this list minus the user's global exclusion.
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- ABDOMEN_LEFT
|
||
- ABDOMEN_RIGHT
|
||
- ABDOMEN_UPPER_LEFT
|
||
- ABDOMEN_UPPER_RIGHT
|
||
- THIGH_LEFT
|
||
- THIGH_RIGHT
|
||
- UPPER_ARM_LEFT
|
||
- UPPER_ARM_RIGHT
|
||
notificationsEnabled:
|
||
type: boolean
|
||
liveActivityEnabled:
|
||
description: iOS Live Activity opt-in for this medication's reminders. Default false. The iOS client owns the
|
||
ActivityKit lifecycle; the server only stores + echoes the flag.
|
||
type: boolean
|
||
criticalAlarmEnabled:
|
||
description: iOS 26 AlarmKit critical-reminder opt-in. Default false. Critical alarms bypass the device mute switch /
|
||
Focus; the server stores the preference only and hangs no server-side behaviour off it.
|
||
type: boolean
|
||
atcCode:
|
||
description: Optional WHO ATC classification code (active-substance class, 7 chars, e.g. `A10BX10`).
|
||
User/clinician-asserted; never machine-guessed. Emitted on the FHIR `medicationCodeableConcept` under
|
||
`http://www.whocc.no/atc`. NULL clears it; absent leaves it untouched.
|
||
anyOf:
|
||
- type: string
|
||
pattern: ^[A-Z]\d{2}[A-Z]{2}\d{2}$
|
||
- type: "null"
|
||
rxNormCode:
|
||
description: Optional RxNorm RxCUI (numeric, US identifier, e.g. `2601723`). Secondary coding emitted under
|
||
`http://www.nlm.nih.gov/research/umls/rxnorm` alongside any ATC code, never instead of the free-text name.
|
||
NULL clears it; absent leaves it untouched.
|
||
anyOf:
|
||
- type: string
|
||
maxLength: 20
|
||
pattern: ^\d+$
|
||
- type: "null"
|
||
startsOn:
|
||
description: Date the medication course begins (ISO `YYYY-MM-DD`). Anchors RRULE BYDAY / BYMONTHDAY patterns and the
|
||
rolling-interval countdown's first window. NULL = active from creation (the legacy implicit behaviour).
|
||
Required when `oneShot` is true.
|
||
anyOf:
|
||
- type: string
|
||
format: date
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))$
|
||
- type: "null"
|
||
endsOn:
|
||
description: Date the medication course ends (ISO `YYYY-MM-DD`). NULL = no end date (chronic). When `oneShot` is true
|
||
the route normalises `endsOn` to equal `startsOn`.
|
||
anyOf:
|
||
- type: string
|
||
format: date
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))$
|
||
- type: "null"
|
||
oneShot:
|
||
description: Single-administration medication (e.g. flu shot, post-op single dose). When true the medication has at most
|
||
one schedule with no `rrule` / `rollingIntervalDays`, and `active` auto-flips to false once the dose is
|
||
logged (non-skipped).
|
||
type: boolean
|
||
schedules:
|
||
minItems: 1
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleInput"
|
||
required:
|
||
- name
|
||
- dose
|
||
- schedules
|
||
description: 'Create-medication body. The route enforces the v1.5 cross-field invariants on top of the per-schedule
|
||
`rrule_xor_rolling` Zod refine: a `oneShot:true` medication may carry at most one schedule and that schedule
|
||
must not declare a recurrence; `endsOn` is normalised to equal `startsOn` for one-shot doses; a recurring
|
||
schedule with no `rrule`, `rollingIntervalDays`, or legacy `daysOfWeek` defaults to `rrule = "FREQ=DAILY"`; and
|
||
`timesOfDay` is dual-written from `windowStart` when the caller omits it.'
|
||
MedicationScheduleInput:
|
||
type: object
|
||
properties:
|
||
windowStart:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):([0-5]\d)$
|
||
description: Legacy single-time-of-intake (HH:mm, user local). v1.5 keeps the field for backwards compatibility with
|
||
pre-wizard iOS clients; the new `timesOfDay` array supersedes it.
|
||
windowEnd:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):([0-5]\d)$
|
||
description: Legacy reminder-window upper bound (HH:mm). Used to derive the late-classification grace span when
|
||
`reminderGraceMinutes` is null.
|
||
label:
|
||
description: Optional human label (e.g. "Morning", "Evening").
|
||
type: string
|
||
maxLength: 50
|
||
dose:
|
||
description: Per-schedule dose override. NULL means the schedule inherits `Medication.dose`.
|
||
type: string
|
||
maxLength: 50
|
||
daysOfWeek:
|
||
description: Legacy day-of-week filter (0=Sunday..6=Saturday). v1.5 reads new writes through `rrule` first; this field
|
||
is preserved for pre-v1.5 rows and is the input the route serialises into the persisted `days_of_week`
|
||
string.
|
||
type: array
|
||
items:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 6
|
||
intervalWeeks:
|
||
description: Legacy multi-week stride (1..4). Bi-weekly + tri-weekly were broken in the pre-v1.5 reminder worker; new
|
||
writes encode the same intent via `rrule` (e.g. `FREQ=WEEKLY;INTERVAL=2;BYDAY=WE`).
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 4
|
||
timesOfDay:
|
||
description: v1.5 first-class points-in-time the dose is taken (HH:mm, user local). Up to 8 entries. Absent or empty
|
||
means the route stamps `[windowStart]` so the new engine always sees a populated array.
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):([0-5]\d)$
|
||
reminderGraceMinutes:
|
||
description: Reminder grace window in minutes. Caps at 24h. NULL falls back to the legacy `windowEnd - windowStart` span.
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 1440
|
||
rrule:
|
||
description: "RFC 5545 RRULE string (subset). Use for daily / weekly-with-BYDAY / multi-week / monthly / yearly
|
||
cadences. Mutually exclusive with `rollingIntervalDays`. Examples: `FREQ=DAILY`,
|
||
`FREQ=WEEKLY;BYDAY=MO,WE,FR`, `FREQ=WEEKLY;INTERVAL=2;BYDAY=WE`, `FREQ=MONTHLY;BYMONTHDAY=1`,
|
||
`FREQ=YEARLY;BYMONTH=1;BYMONTHDAY=1`."
|
||
type: string
|
||
maxLength: 200
|
||
pattern: ^FREQ=(?:DAILY|WEEKLY|MONTHLY|YEARLY)(?:;(?:INTERVAL=\d{1,3}|BYDAY=(?:MO|TU|WE|TH|FR|SA|SU)(?:,(?:MO|TU|WE|TH|FR|SA|SU))*|BYMONTHDAY=-?\d{1,2}(?:,-?\d{1,2})*|BYMONTH=\d{1,2}(?:,\d{1,2})*|COUNT=\d{1,4}|UNTIL=\d{8}T\d{6}Z))*$
|
||
rollingIntervalDays:
|
||
description: Flexible-rolling interval in days, counted forward from the latest `MedicationIntakeEvent.takenAt` (the
|
||
dose re-anchors when logged). Mutually exclusive with `rrule`. Range 1..365.
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 365
|
||
scheduleType:
|
||
description: Schedule type. SCHEDULED (default) = rrule / rolling / legacy cadence. PRN = as-needed (never projected,
|
||
reminded, or counted in compliance expected; still loggable via the intake route). CYCLIC = N weeks on / M
|
||
weeks off, gating whichever inner cadence the rrule / legacy fields describe.
|
||
type: string
|
||
enum:
|
||
- SCHEDULED
|
||
- PRN
|
||
- CYCLIC
|
||
cyclicOnWeeks:
|
||
description: Cyclic "on" weeks (1..52). Required when `scheduleType` is CYCLIC; ignored otherwise.
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 52
|
||
cyclicOffWeeks:
|
||
description: Cyclic "off" weeks (0..52). Required when `scheduleType` is CYCLIC; ignored otherwise.
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 52
|
||
doseWindows:
|
||
description: Per-dose on-time intake windows. Each `{ timeOfDay, start, end }` HH:mm triple sets the explicit on-time
|
||
band for the matching dose time; a dose time with no entry keeps the symmetric ±1h default. `timeOfDay` must
|
||
match one of `timesOfDay` (or `windowStart`); `start <= end`. Up to 8 entries. Absent leaves every slot on
|
||
the default derivation. The late tail stays cadence-derived.
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/DoseWindowEntry"
|
||
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.
|
||
DoseWindowEntry:
|
||
type: object
|
||
properties:
|
||
timeOfDay:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):([0-5]\d)$
|
||
description: The dose time this window applies to (matches a `timesOfDay` entry).
|
||
start:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):([0-5]\d)$
|
||
description: On-time band lower bound (HH:mm, user local).
|
||
end:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):([0-5]\d)$
|
||
description: On-time band upper bound (HH:mm, user local). Must be >= `start`.
|
||
required:
|
||
- timeOfDay
|
||
- start
|
||
- end
|
||
description: One explicit per-dose on-time intake window. `timeOfDay` matches a schedule dose time; `[start, end]`
|
||
(HH:mm, user local, `start <= end`) is the on-time band. Outside it the cadence-derived late tail applies, then
|
||
ad-hoc.
|
||
UpdateMedicationRequest:
|
||
type: object
|
||
properties:
|
||
name:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 100
|
||
dose:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 50
|
||
category:
|
||
type: string
|
||
enum:
|
||
- BLOOD_PRESSURE
|
||
- VITAMIN
|
||
- SUPPLEMENT
|
||
- PAIN_RELIEF
|
||
- ALLERGY
|
||
- DIGESTIVE
|
||
- THYROID
|
||
- HORMONE
|
||
- SKIN
|
||
- SLEEP_AID
|
||
- DIABETES
|
||
- ANTIBIOTIC
|
||
- OTHER
|
||
treatmentClass:
|
||
type: string
|
||
enum:
|
||
- GENERIC
|
||
- GLP1
|
||
dosesPerUnit:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 1
|
||
maximum: 100
|
||
- type: "null"
|
||
deliveryForm:
|
||
type: string
|
||
enum:
|
||
- ORAL
|
||
- INJECTION
|
||
- OTHER
|
||
trackInjectionSites:
|
||
description: Per-medication injection-site tracking opt-in. Only meaningful for an INJECTION delivery form. Set false to
|
||
deactivate tracking.
|
||
type: boolean
|
||
allowedInjectionSites:
|
||
description: Per-medication allowed / preferred injection sites. Empty array clears the restriction. The effective
|
||
pickable set subtracts the user's global exclusion (deny wins).
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- ABDOMEN_LEFT
|
||
- ABDOMEN_RIGHT
|
||
- ABDOMEN_UPPER_LEFT
|
||
- ABDOMEN_UPPER_RIGHT
|
||
- THIGH_LEFT
|
||
- THIGH_RIGHT
|
||
- UPPER_ARM_LEFT
|
||
- UPPER_ARM_RIGHT
|
||
active:
|
||
type: boolean
|
||
notificationsEnabled:
|
||
type: boolean
|
||
liveActivityEnabled:
|
||
description: iOS Live Activity opt-in for this medication's reminders. The iOS client owns the ActivityKit lifecycle;
|
||
the server only stores + echoes the flag.
|
||
type: boolean
|
||
criticalAlarmEnabled:
|
||
description: iOS 26 AlarmKit critical-reminder opt-in. Critical alarms bypass the device mute switch / Focus; the server
|
||
stores the preference only.
|
||
type: boolean
|
||
atcCode:
|
||
description: Optional WHO ATC classification code (active-substance class, 7 chars, e.g. `A10BX10`).
|
||
User/clinician-asserted; never machine-guessed. Emitted on the FHIR `medicationCodeableConcept` under
|
||
`http://www.whocc.no/atc`. NULL clears it; absent leaves it untouched.
|
||
anyOf:
|
||
- type: string
|
||
pattern: ^[A-Z]\d{2}[A-Z]{2}\d{2}$
|
||
- type: "null"
|
||
rxNormCode:
|
||
description: Optional RxNorm RxCUI (numeric, US identifier, e.g. `2601723`). Secondary coding emitted under
|
||
`http://www.nlm.nih.gov/research/umls/rxnorm` alongside any ATC code, never instead of the free-text name.
|
||
NULL clears it; absent leaves it untouched.
|
||
anyOf:
|
||
- type: string
|
||
maxLength: 20
|
||
pattern: ^\d+$
|
||
- type: "null"
|
||
startsOn:
|
||
description: Date the medication course begins (ISO `YYYY-MM-DD`). Anchors RRULE BYDAY / BYMONTHDAY patterns and the
|
||
rolling-interval countdown's first window. NULL = active from creation (the legacy implicit behaviour).
|
||
Required when `oneShot` is true.
|
||
anyOf:
|
||
- type: string
|
||
format: date
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))$
|
||
- type: "null"
|
||
endsOn:
|
||
description: Date the medication course ends (ISO `YYYY-MM-DD`). NULL = no end date (chronic). When `oneShot` is true
|
||
the route normalises `endsOn` to equal `startsOn`.
|
||
anyOf:
|
||
- type: string
|
||
format: date
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))$
|
||
- type: "null"
|
||
oneShot:
|
||
description: Single-administration medication (e.g. flu shot, post-op single dose). When true the medication has at most
|
||
one schedule with no `rrule` / `rollingIntervalDays`, and `active` auto-flips to false once the dose is
|
||
logged (non-skipped).
|
||
type: boolean
|
||
schedules:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleInput"
|
||
reminderGraceMinutes:
|
||
description: "Detail-page bridge: primary-schedule reminder-window in minutes. The route maps the value onto the primary
|
||
schedule's `reminderGraceMinutes` field; ignored when a full `schedules` array is also supplied."
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 0
|
||
maximum: 1440
|
||
- type: "null"
|
||
description: Update-medication body. Every field is optional; omitted fields are left untouched. Supplying `schedules`
|
||
REPLACES the medication's full schedule list (the route deletes existing rows before re-creating). Flipping
|
||
`active` to false records the current timestamp on `pausedAt`; flipping back to true clears it. v1.5 invariants
|
||
on the `schedules` array match the create path.
|
||
MedicationIntakeRequest:
|
||
type: object
|
||
properties:
|
||
medicationId:
|
||
type: string
|
||
minLength: 1
|
||
description: Server-narrowed from the URL path. The route layer overwrites whatever the body supplies before Zod parsing
|
||
so a caller cannot log an intake against another medication.
|
||
scheduledFor:
|
||
description: Slot the dose belongs to. Defaults to `takenAt` (or `now()` when both are absent) so the compliance pairing
|
||
logic can pin the dose to a schedule slot.
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
takenAt:
|
||
description: When the dose was actually taken. NULL when `skipped` is true; defaults to `now()` for non-skipped intakes.
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
skipped:
|
||
default: false
|
||
description: True to log a skipped slot (no consumption, no inventory decrement, one-shot medications stay active).
|
||
type: boolean
|
||
idempotencyKey:
|
||
description: Caller-issued de-dup key. A second POST with the same key returns the original event without creating a new
|
||
row.
|
||
type: string
|
||
maxLength: 128
|
||
injectionSite:
|
||
description: Optional injection site for a taken dose. Honoured only when the medication is an INJECTION with
|
||
site-tracking enabled; validated against the medication's effective allowed set (per-medication allowed
|
||
sites minus the user's global exclusion). A disallowed site returns 422. Omit to record the dose without a
|
||
site.
|
||
type: string
|
||
enum:
|
||
- ABDOMEN_LEFT
|
||
- ABDOMEN_RIGHT
|
||
- ABDOMEN_UPPER_LEFT
|
||
- ABDOMEN_UPPER_RIGHT
|
||
- THIGH_LEFT
|
||
- THIGH_RIGHT
|
||
- UPPER_ARM_LEFT
|
||
- UPPER_ARM_RIGHT
|
||
forceSlotInstant:
|
||
description: "Late-take override: pin this taken dose onto the named scheduled slot instead of orphaning it to an ad-hoc
|
||
row. Must be a real scheduled slot of this medication on its day (validated server-side against the
|
||
dose-window band anchors); an instant that is not a slot returns 422. Absent applies the default window-band
|
||
attribution."
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
doseTaken:
|
||
description: Dose actually consumed for THIS intake when it deviates from (or documents) the medication's configured
|
||
dose, e.g. a half tablet or a titration step. Free text, max 50 characters. Omit to record the take under
|
||
the medication's configured dose.
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 50
|
||
required:
|
||
- medicationId
|
||
description: Per-medication intake log body. Idempotent via `idempotencyKey`; the server also dedupes by a 60-second
|
||
sliding window when the key is absent. Non-skipped intakes auto-decrement pen inventory (best-effort), refresh
|
||
the per-day compliance rollup, and — for one-shot medications — flip `active` to false. The optional
|
||
`injectionSite` is persisted only for an INJECTION medication with site-tracking enabled and is validated
|
||
against the medication's effective allowed set (422 on a disallowed value).
|
||
MedicationExtractRequest:
|
||
type: object
|
||
properties:
|
||
text:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 2000
|
||
description: Free-text medication description (any locale). Up to 2 000 characters. The model never echoes the text back
|
||
into another tenant — it is only used to produce the structured fields.
|
||
locale:
|
||
description: Optional UI locale hint for the model.
|
||
type: string
|
||
enum:
|
||
- en
|
||
- de
|
||
- es
|
||
- fr
|
||
- it
|
||
- pl
|
||
today:
|
||
description: "Optional override of the reference date used to resolve relative phrases (\"tomorrow\", \"next Monday\").
|
||
Format: `YYYY-MM-DD`. Defaults to the server's UTC day."
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
required:
|
||
- text
|
||
description: Free-text medication description payload. The route runs the text through the Coach provider chain and
|
||
returns a partial structured payload the wizard merges. Rate-limited 10 requests / 5 minutes / user;
|
||
budget-gated against the daily Coach token ceiling.
|
||
CreateScheduleRevisionRequest:
|
||
type: object
|
||
properties:
|
||
validFrom:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
description: Inclusive start instant of the manual era (ISO 8601).
|
||
validUntil:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
description: Exclusive end instant of the manual era (ISO 8601). Must precede the current plan.
|
||
timesOfDay:
|
||
description: Daily dose times (HH:mm, user local) the era ran at. 1–8 entries; duplicates are collapsed.
|
||
minItems: 1
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):([0-5]\d)$
|
||
required:
|
||
- validFrom
|
||
- validUntil
|
||
- timesOfDay
|
||
description: "Manual schedule-era payload: era bounds plus the daily dose times that were live during the era."
|
||
UpdateScheduleRevisionRequest:
|
||
description: "Corrected schedule-era payload: replacement era bounds plus the daily dose times that were live during the era."
|
||
$ref: "#/components/schemas/CreateScheduleRevisionRequest"
|
||
DisableCoachFlag:
|
||
type: object
|
||
properties:
|
||
disableCoach:
|
||
type: boolean
|
||
required:
|
||
- disableCoach
|
||
description: Per-account Coach opt-out toggle (v1.4.47 W3). `true` hides the Coach FAB and short-circuits its API gates.
|
||
ProfileUpdateRequest:
|
||
type: object
|
||
properties:
|
||
email:
|
||
anyOf:
|
||
- type: string
|
||
format: email
|
||
pattern: ^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
|
||
- type: "null"
|
||
heightCm:
|
||
anyOf:
|
||
- type: number
|
||
minimum: 50
|
||
maximum: 300
|
||
- type: "null"
|
||
dateOfBirth:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
gender:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- MALE
|
||
- FEMALE
|
||
- type: "null"
|
||
displayName:
|
||
anyOf:
|
||
- type: string
|
||
minLength: 1
|
||
maxLength: 80
|
||
- type: "null"
|
||
locale:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
- type: "null"
|
||
timezone:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 64
|
||
timeFormat:
|
||
description: Hour-cycle display preference. AUTO follows the locale convention, H12 forces AM/PM, H24 forces 24-hour.
|
||
type: string
|
||
enum:
|
||
- AUTO
|
||
- H12
|
||
- H24
|
||
moodReminderEnabled:
|
||
type: boolean
|
||
fullName:
|
||
anyOf:
|
||
- type: string
|
||
maxLength: 120
|
||
- type: "null"
|
||
insurerName:
|
||
anyOf:
|
||
- type: string
|
||
maxLength: 120
|
||
- type: "null"
|
||
insuranceNumber:
|
||
description: German KVNR. Empty/null clears it; mod-10 check enforced.
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
insurerIkNumber:
|
||
description: German insurer institution number (IKNR). Optional; empty/null clears it. A non-empty value must be exactly
|
||
9 digits (no checksum enforced).
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Partial profile update. Every field is optional; an omitted field is left untouched, an explicit null (or
|
||
empty string) clears it. `userId` is never accepted — it is narrowed from the session/token.
|
||
InsightsLayoutBody:
|
||
type: object
|
||
properties:
|
||
version:
|
||
anyOf:
|
||
- type: number
|
||
const: 1
|
||
- type: number
|
||
const: 2
|
||
sections:
|
||
maxItems: 50
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
enum:
|
||
- wellness-scores
|
||
- daily-briefing
|
||
- vitals
|
||
- trends
|
||
- period-review
|
||
- cycle-summary
|
||
- signals
|
||
- rhythm-events
|
||
visible:
|
||
type: boolean
|
||
order:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 99
|
||
required:
|
||
- id
|
||
- visible
|
||
- order
|
||
tiles:
|
||
minItems: 1
|
||
maxItems: 50
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
enum:
|
||
- overview
|
||
- blood-pressure
|
||
- pulse
|
||
- oxygen
|
||
- body-temperature
|
||
- respiratory-rate
|
||
- weight
|
||
- bmi
|
||
- body-water
|
||
- bone-mass
|
||
- fat-free-mass
|
||
- fat-mass
|
||
- muscle-mass
|
||
- visceral-fat
|
||
- lean-body-mass
|
||
- steps
|
||
- active-energy
|
||
- workouts
|
||
- flights-climbed
|
||
- walking-distance
|
||
- walking-steadiness
|
||
- walking-heart-rate
|
||
- walking-asymmetry
|
||
- double-support-time
|
||
- step-length
|
||
- walking-speed
|
||
- cardio-fitness
|
||
- falls
|
||
- six-minute-walk
|
||
- stair-ascent-speed
|
||
- stair-descent-speed
|
||
- sleep
|
||
- breathing-disturbances
|
||
- resting-pulse
|
||
- hrv
|
||
- pulse-wave-velocity
|
||
- vascular-age
|
||
- cardio-recovery
|
||
- environmental-audio
|
||
- headphone-audio
|
||
- audio-events
|
||
- daylight
|
||
- blood-glucose
|
||
- skin-temperature
|
||
- wrist-temperature
|
||
- mood
|
||
- medications
|
||
- blutdruck
|
||
- puls
|
||
- sauerstoff
|
||
- koerpertemperatur
|
||
- gewicht
|
||
- aktive-energie
|
||
- schlaf
|
||
- ruhepuls
|
||
- stimmung
|
||
- medikamente
|
||
visible:
|
||
type: boolean
|
||
order:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 99
|
||
required:
|
||
- id
|
||
- visible
|
||
- order
|
||
required:
|
||
- version
|
||
description: "Per-user Insights layout (v2). `tiles` is the per-metric pill list (ordered, with a visibility flag);
|
||
`sections` is the additive v2 list of the overview's big semantic blocks (wellness-scores, daily-briefing,
|
||
vitals, trends, period-review, cycle-summary, signals, rhythm-events), each with order + visibility. `version`
|
||
is the layout schema version: BOTH 1 and 2 are accepted on input (a pre-v2 iOS client still PUTs `version: 1`);
|
||
the server always normalises to the canonical v2 blob before persisting, and GET responses always carry
|
||
`version: 2`. Both arrays are optional on input — a client sending only `tiles` (the pre-v2 contract) still
|
||
validates, and the server fills missing defaults; a v1 blob with no `sections` resolves forward with all
|
||
sections default-visible. Tile ids are a closed enum: the canonical ids are English (matching the routed
|
||
`/insights/<slug>` sub-pages). The legacy German tile ids (blutdruck, puls, sauerstoff, koerpertemperatur,
|
||
gewicht, aktive-energie, schlaf, ruhepuls, stimmung, medikamente) remain accepted on input for backward
|
||
compatibility and are normalised to their English equivalents before persisting; GET responses always carry the
|
||
canonical English ids. Section ids are English-only. The legacy tile ids are deprecated and will be removed in a
|
||
future major version."
|
||
CoachMessageFeedbackRequest:
|
||
type: object
|
||
properties:
|
||
rating:
|
||
type: string
|
||
enum:
|
||
- helpful
|
||
- unhelpful
|
||
reason:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 200
|
||
required:
|
||
- rating
|
||
description: Per-message helpful/unhelpful feedback (v1.4.23 H7). Optional `reason` is free-form prose, capped at 200 chars.
|
||
InsightsPregenerateRequest:
|
||
type: object
|
||
properties: {}
|
||
description: No body fields. The user is taken from the session / Bearer and the locale from the session; the warm
|
||
covers every assessment for that user.
|
||
MedicationTreatmentClass:
|
||
type: string
|
||
enum:
|
||
- GENERIC
|
||
- GLP1
|
||
description: Prisma-level treatment-class discriminator. `GLP1` unlocks the GLP-1 specialist surfaces (injection-site
|
||
rotation, titration history, pen inventory, GLP-1-aware Coach).
|
||
MedicationSchedule:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
medicationId:
|
||
type: string
|
||
windowStart:
|
||
type: string
|
||
description: Legacy single-time-of-intake (HH:mm, user local). Preserved for backwards compatibility; the new
|
||
`timesOfDay` array supersedes it.
|
||
windowEnd:
|
||
type: string
|
||
description: Legacy reminder-window upper bound (HH:mm). Used to derive the late-classification grace span when
|
||
`reminderGraceMinutes` is null.
|
||
label:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
dose:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Per-schedule dose override. NULL means the schedule inherits `Medication.dose`.
|
||
daysOfWeek:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Legacy persisted recurrence encoding (`null` | `1,3,5` | `i2;1,3,5`). v1.5 readers consult `rrule` first;
|
||
the field is kept for pre-v1.5 rows. v1.6.0 drops the column.
|
||
timesOfDay:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: v1.5 first-class points-in-time the dose is taken (HH:mm, user local). Backfilled to `[windowStart]` for
|
||
every pre-v1.5 row.
|
||
reminderGraceMinutes:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Reminder grace window in minutes. NULL falls back to the legacy `windowEnd - windowStart` span.
|
||
rrule:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: RFC 5545 RRULE string (subset). Used for calendar-anchored cadences. **Mutually exclusive with
|
||
`rollingIntervalDays`** — exactly one of the two is non-null on any v1.5+ schedule (or both are null on
|
||
legacy rows that haven't been touched since the migration).
|
||
rollingIntervalDays:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Flexible-rolling interval in days, counted forward from the latest `MedicationIntakeEvent.takenAt`.
|
||
**Mutually exclusive with `rrule`.**
|
||
scheduleType:
|
||
type: string
|
||
enum:
|
||
- SCHEDULED
|
||
- PRN
|
||
- CYCLIC
|
||
description: v1.7.0 schedule-type discriminator. SCHEDULED = rrule / rolling / legacy cadence. PRN = as-needed (never
|
||
projected, reminded, or counted in compliance expected; still loggable via the intake route). CYCLIC = N
|
||
weeks on / M weeks off, gating whichever inner cadence the rrule / legacy fields describe.
|
||
cyclicOnWeeks:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: v1.7.0 cyclic "on" weeks. Only meaningful when `scheduleType` is CYCLIC; null otherwise.
|
||
cyclicOffWeeks:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: v1.7.0 cyclic "off" weeks. Only meaningful when `scheduleType` is CYCLIC; null otherwise.
|
||
required:
|
||
- id
|
||
- medicationId
|
||
- windowStart
|
||
- windowEnd
|
||
- label
|
||
- dose
|
||
- daysOfWeek
|
||
- timesOfDay
|
||
- reminderGraceMinutes
|
||
- rrule
|
||
- rollingIntervalDays
|
||
- scheduleType
|
||
- cyclicOnWeeks
|
||
- cyclicOffWeeks
|
||
description: Schedule entry attached to a medication. v1.5 promotes `timesOfDay` to first-class and introduces `rrule`
|
||
(calendar-anchored cadences) and `rollingIntervalDays` (flexible-rolling cadences). The two recurrence
|
||
primitives are mutually exclusive — enforced by the Zod refine on writes, the route layer, and a DB CHECK
|
||
constraint (`medication_schedules_rrule_xor_rolling`). v1.7.0 adds `scheduleType` (SCHEDULED / PRN / CYCLIC) and
|
||
the cyclic on/off-week fields.
|
||
CycleDayLogReadEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/CycleDayLogDTO"
|
||
- type: "null"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CycleDayLogDTO:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
date:
|
||
type: string
|
||
cycleId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
flow:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NONE
|
||
- SPOTTING
|
||
- LIGHT
|
||
- MEDIUM
|
||
- HEAVY
|
||
- type: "null"
|
||
intermenstrualBleeding:
|
||
type: boolean
|
||
basalBodyTempC:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
ovulationTest:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE_LH_SURGE
|
||
- ESTROGEN_SURGE
|
||
- INDETERMINATE
|
||
- type: "null"
|
||
cervicalMucus:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- DRY
|
||
- STICKY
|
||
- CREAMY
|
||
- WATERY
|
||
- EGG_WHITE
|
||
- type: "null"
|
||
sexualActivity:
|
||
type: boolean
|
||
protectedSex:
|
||
anyOf:
|
||
- type: boolean
|
||
- type: "null"
|
||
pregnancyTest:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE
|
||
- INDETERMINATE
|
||
- type: "null"
|
||
progesteroneTest:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE
|
||
- INDETERMINATE
|
||
- type: "null"
|
||
contraceptive:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
symptoms:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
key:
|
||
type: string
|
||
severity:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 1
|
||
maximum: 4
|
||
- type: "null"
|
||
required:
|
||
- key
|
||
- severity
|
||
additionalProperties: false
|
||
note:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
source:
|
||
type: string
|
||
externalId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
syncVersion:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
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)))$
|
||
deletedAt:
|
||
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"
|
||
required:
|
||
- id
|
||
- date
|
||
- cycleId
|
||
- flow
|
||
- intermenstrualBleeding
|
||
- basalBodyTempC
|
||
- ovulationTest
|
||
- cervicalMucus
|
||
- sexualActivity
|
||
- protectedSex
|
||
- pregnancyTest
|
||
- progesteroneTest
|
||
- contraceptive
|
||
- symptoms
|
||
- note
|
||
- source
|
||
- externalId
|
||
- syncVersion
|
||
- updatedAt
|
||
- deletedAt
|
||
additionalProperties: false
|
||
description: The canonical day-log row iOS mirrors. `note` is decrypted on read. Soft-deleted rows ride
|
||
`/api/sync/changes` as tombstones.
|
||
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."
|
||
CycleDayLogEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/CycleDayLogDTO"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CycleDayLogCreatedEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/CycleDayLogDTO"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CycleDayLogPatchEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/CycleDayLogDTO"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CycleBulkEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
processed:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
inserted:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
updated:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
duplicates:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
skipped:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
entries:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
index:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
status:
|
||
type: string
|
||
enum:
|
||
- inserted
|
||
- duplicate
|
||
- updated
|
||
- skipped
|
||
id:
|
||
type: string
|
||
externalId:
|
||
type: string
|
||
reason:
|
||
type: string
|
||
required:
|
||
- index
|
||
- status
|
||
additionalProperties: false
|
||
required:
|
||
- processed
|
||
- inserted
|
||
- updated
|
||
- duplicates
|
||
- skipped
|
||
- entries
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CyclePeriodEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
cycle:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/MenstrualCycleDTO"
|
||
- type: "null"
|
||
dayLog:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/CycleDayLogDTO"
|
||
- type: "null"
|
||
required:
|
||
- cycle
|
||
- dayLog
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MenstrualCycleDTO:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
startDate:
|
||
type: string
|
||
endDate:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
periodEndDate:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
lengthDays:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
ovulationDate:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
ovulationConfirmed:
|
||
type: boolean
|
||
isPredicted:
|
||
type: boolean
|
||
syncVersion:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- id
|
||
- startDate
|
||
- endDate
|
||
- periodEndDate
|
||
- lengthDays
|
||
- ovulationDate
|
||
- ovulationConfirmed
|
||
- isPredicted
|
||
- syncVersion
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: One menstrual cycle (observed or forward-predicted).
|
||
CycleCalendarEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
profile:
|
||
type: object
|
||
properties:
|
||
goal:
|
||
type: string
|
||
enum:
|
||
- GENERAL_HEALTH
|
||
- AVOID_PREGNANCY
|
||
- TRYING_TO_CONCEIVE
|
||
- PERIMENOPAUSE
|
||
- OFF
|
||
rawChartMode:
|
||
type: boolean
|
||
predictionEnabled:
|
||
type: boolean
|
||
cyclesObserved:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- goal
|
||
- rawChartMode
|
||
- predictionEnabled
|
||
- cyclesObserved
|
||
additionalProperties: false
|
||
prediction:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/CyclePredictionDTO"
|
||
- type: "null"
|
||
days:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
date:
|
||
type: string
|
||
phase:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/CyclePhase"
|
||
- type: "null"
|
||
isPredictedPeriod:
|
||
type: boolean
|
||
isFertileWindow:
|
||
type: boolean
|
||
isPredictedOvulation:
|
||
type: boolean
|
||
isPeriodLogged:
|
||
type: boolean
|
||
flow:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NONE
|
||
- SPOTTING
|
||
- LIGHT
|
||
- MEDIUM
|
||
- HEAVY
|
||
- type: "null"
|
||
hasSymptoms:
|
||
type: boolean
|
||
confidence:
|
||
type: number
|
||
basalBodyTempC:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
ovulationTest:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- NEGATIVE
|
||
- POSITIVE_LH_SURGE
|
||
- ESTROGEN_SURGE
|
||
- INDETERMINATE
|
||
- type: "null"
|
||
cervicalMucus:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- DRY
|
||
- STICKY
|
||
- CREAMY
|
||
- WATERY
|
||
- EGG_WHITE
|
||
- type: "null"
|
||
required:
|
||
- date
|
||
- phase
|
||
- isPredictedPeriod
|
||
- isFertileWindow
|
||
- isPredictedOvulation
|
||
- isPeriodLogged
|
||
- flow
|
||
- hasSymptoms
|
||
- confidence
|
||
- basalBodyTempC
|
||
- ovulationTest
|
||
- cervicalMucus
|
||
additionalProperties: false
|
||
meta:
|
||
type: object
|
||
properties:
|
||
generatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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:
|
||
- generatedAt
|
||
additionalProperties: false
|
||
required:
|
||
- profile
|
||
- prediction
|
||
- days
|
||
- meta
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CyclePredictionDTO:
|
||
type: object
|
||
properties:
|
||
method:
|
||
$ref: "#/components/schemas/CyclePredictionMethod"
|
||
nextPeriodStart:
|
||
type: string
|
||
nextPeriodStartLow:
|
||
type: string
|
||
nextPeriodStartHigh:
|
||
type: string
|
||
fertileWindowStart:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
fertileWindowEnd:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
predictedOvulation:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
ovulationConfirmed:
|
||
type: boolean
|
||
confidence:
|
||
type: number
|
||
cyclesObserved:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
stillLearning:
|
||
type: boolean
|
||
disclaimer:
|
||
type: string
|
||
required:
|
||
- method
|
||
- nextPeriodStart
|
||
- nextPeriodStartLow
|
||
- nextPeriodStartHigh
|
||
- fertileWindowStart
|
||
- fertileWindowEnd
|
||
- predictedOvulation
|
||
- ovulationConfirmed
|
||
- confidence
|
||
- cyclesObserved
|
||
- stillLearning
|
||
- disclaimer
|
||
additionalProperties: false
|
||
description: The materialised forecast. Fertile-window fields (and predictedOvulation/ovulationConfirmed) are
|
||
server-suppressed (null/false) unless the goal is TRYING_TO_CONCEIVE or AVOID_PREGNANCY.
|
||
CyclePredictionMethod:
|
||
type: string
|
||
enum:
|
||
- CALENDAR
|
||
- SYMPTOTHERMAL
|
||
- TEMPERATURE_TREND
|
||
- BLENDED
|
||
CyclePhase:
|
||
type: string
|
||
enum:
|
||
- MENSTRUAL
|
||
- FOLLICULAR
|
||
- OVULATORY
|
||
- LUTEAL
|
||
CycleHistoryEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
cycles:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MenstrualCycleDTO"
|
||
stats:
|
||
type: object
|
||
properties:
|
||
avgLengthDays:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
lengthVariabilityDays:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
avgPeriodLengthDays:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
regularity:
|
||
type: string
|
||
enum:
|
||
- REGULAR
|
||
- IRREGULAR
|
||
- LEARNING
|
||
required:
|
||
- avgLengthDays
|
||
- lengthVariabilityDays
|
||
- avgPeriodLengthDays
|
||
- regularity
|
||
additionalProperties: false
|
||
required:
|
||
- cycles
|
||
- stats
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CycleInsightsEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
rows:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/CyclePhaseCrosstabRow"
|
||
headline:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/CyclePhaseCrosstabRow"
|
||
- type: "null"
|
||
lagged:
|
||
type: object
|
||
properties:
|
||
discovered:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/CyclePhaseLaggedPair"
|
||
pairsTested:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
fdrQ:
|
||
type: number
|
||
minPairs:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- discovered
|
||
- pairsTested
|
||
- fdrQ
|
||
- minPairs
|
||
additionalProperties: false
|
||
symptomPatterns:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/CycleSymptomPhaseRow"
|
||
contrast:
|
||
type: object
|
||
properties:
|
||
high:
|
||
type: string
|
||
const: LUTEAL
|
||
low:
|
||
type: string
|
||
const: FOLLICULAR
|
||
required:
|
||
- high
|
||
- low
|
||
additionalProperties: false
|
||
windowDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
cyclesObserved:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- rows
|
||
- headline
|
||
- lagged
|
||
- symptomPatterns
|
||
- contrast
|
||
- windowDays
|
||
- cyclesObserved
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CyclePhaseCrosstabRow:
|
||
type: object
|
||
properties:
|
||
metricKey:
|
||
type: string
|
||
enum:
|
||
- restingHeartRate
|
||
- heartRateVariability
|
||
- sleepDuration
|
||
- steps
|
||
- weight
|
||
- basalBodyTemp
|
||
- wristTemperature
|
||
- skinTemperature
|
||
- bloodGlucose
|
||
- mood
|
||
display:
|
||
type: string
|
||
enum:
|
||
- hours
|
||
- steps
|
||
- bpm
|
||
- ms
|
||
- kg
|
||
- celsius
|
||
- glucose
|
||
- mood
|
||
lutealDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
follicularDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
lutealAvg:
|
||
type: number
|
||
follicularAvg:
|
||
type: number
|
||
delta:
|
||
type: number
|
||
pValue:
|
||
type: number
|
||
qValue:
|
||
type: number
|
||
confidence:
|
||
type: string
|
||
enum:
|
||
- low
|
||
- medium
|
||
- high
|
||
required:
|
||
- metricKey
|
||
- display
|
||
- lutealDays
|
||
- follicularDays
|
||
- lutealAvg
|
||
- follicularAvg
|
||
- delta
|
||
- pValue
|
||
- qValue
|
||
- confidence
|
||
additionalProperties: false
|
||
description: One FDR-surviving luteal-vs-follicular contrast for an outcome metric. `delta = lutealAvg − follicularAvg`.
|
||
Observational, never causal.
|
||
CyclePhaseLaggedPair:
|
||
type: object
|
||
properties:
|
||
behaviour:
|
||
type: string
|
||
outcome:
|
||
type: string
|
||
n:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
r:
|
||
type: number
|
||
pValue:
|
||
type: number
|
||
qValue:
|
||
type: number
|
||
interpretation:
|
||
type: string
|
||
lagDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- behaviour
|
||
- outcome
|
||
- n
|
||
- r
|
||
- pValue
|
||
- qValue
|
||
- interpretation
|
||
- lagDays
|
||
additionalProperties: false
|
||
description: One FDR-surviving lagged-Pearson pair from the continuous CYCLE_PHASE ordinal × outcome matrix (mechanism
|
||
B). Descriptive, never causal.
|
||
CycleSymptomPhaseRow:
|
||
type: object
|
||
properties:
|
||
symptomKey:
|
||
type: string
|
||
counts:
|
||
type: object
|
||
properties:
|
||
MENSTRUAL:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
FOLLICULAR:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
OVULATORY:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
LUTEAL:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- MENSTRUAL
|
||
- FOLLICULAR
|
||
- OVULATORY
|
||
- LUTEAL
|
||
additionalProperties: false
|
||
total:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
topPhase:
|
||
type: string
|
||
enum:
|
||
- MENSTRUAL
|
||
- FOLLICULAR
|
||
- OVULATORY
|
||
- LUTEAL
|
||
topShare:
|
||
type: number
|
||
required:
|
||
- symptomKey
|
||
- counts
|
||
- total
|
||
- topPhase
|
||
- topShare
|
||
additionalProperties: false
|
||
description: Where a logged symptom clusters across the cycle phases. Surfaced only once logged on ≥3 phase-labelled
|
||
days. Observational, never causal.
|
||
CyclePurgeEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
purged:
|
||
type: boolean
|
||
dayLogs:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
predictions:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
cycles:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
auditRows:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
pushRows:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- purged
|
||
- dayLogs
|
||
- predictions
|
||
- cycles
|
||
- auditRows
|
||
- pushRows
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CycleProfileEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/CycleProfileDTO"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CycleProfileDTO:
|
||
type: object
|
||
properties:
|
||
goal:
|
||
type: string
|
||
enum:
|
||
- GENERAL_HEALTH
|
||
- AVOID_PREGNANCY
|
||
- TRYING_TO_CONCEIVE
|
||
- PERIMENOPAUSE
|
||
- OFF
|
||
cycleTrackingEnabled:
|
||
type: boolean
|
||
rawChartMode:
|
||
type: boolean
|
||
predictionEnabled:
|
||
type: boolean
|
||
discreetNotifications:
|
||
type: boolean
|
||
sensitiveCategoryEncryption:
|
||
type: boolean
|
||
typicalCycleLength:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
typicalPeriodLength:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
lutealPhaseLength:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- goal
|
||
- cycleTrackingEnabled
|
||
- rawChartMode
|
||
- predictionEnabled
|
||
- discreetNotifications
|
||
- sensitiveCategoryEncryption
|
||
- typicalCycleLength
|
||
- typicalPeriodLength
|
||
- lutealPhaseLength
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: The full per-user cycle settings row.
|
||
CyclePrefsGetEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/CycleProfileDTO"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CyclePrefsPatchEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/CycleProfileDTO"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CapabilitiesEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/CapabilitiesResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CapabilitiesResponse:
|
||
type: object
|
||
properties:
|
||
apiContractVersion:
|
||
type: string
|
||
description: Running build version — mirrors GET /api/version `version`.
|
||
derivedMetricIds:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Closed derived-metric id set (GET /api/insights/derived).
|
||
vitalsBaselineTypes:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: MeasurementTypes the typical-range baseline engine supports.
|
||
layoutTileIds:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Canonical insights layout tile-id set (English ids).
|
||
metricStatusIds:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Closed metric-status / assessment id set.
|
||
ingest:
|
||
type: object
|
||
properties:
|
||
quantityTypes:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
type:
|
||
type: string
|
||
description: HealthLog MeasurementType.
|
||
hk:
|
||
type: string
|
||
description: HealthKit identifier.
|
||
unit:
|
||
type: string
|
||
description: Canonical DB unit.
|
||
required:
|
||
- type
|
||
- hk
|
||
- unit
|
||
additionalProperties: false
|
||
description: Accepted HealthKit quantity-sample mappings.
|
||
eventTypes:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: MeasurementTypes for device-flagged EVENT-class HealthKit samples.
|
||
computedScores:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Server-owned nightly composite score types.
|
||
writeAllowlist:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: MeasurementSources a client may attribute on a write (others are server-owned).
|
||
required:
|
||
- quantityTypes
|
||
- eventTypes
|
||
- computedScores
|
||
- writeAllowlist
|
||
additionalProperties: false
|
||
description: Ingest vocabularies the batch / single-write paths accept.
|
||
fhir:
|
||
type: object
|
||
properties:
|
||
atcSystem:
|
||
type: string
|
||
description: WHO ATC CodeSystem URI.
|
||
snomedRoute:
|
||
type: string
|
||
description: SNOMED CT CodeSystem URI.
|
||
germanAtcDefaultLocales:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: App locales that default the additive BfArM ATC coding on.
|
||
restBaseUrl:
|
||
type: string
|
||
description: Base path of the read-only FHIR R4 REST face.
|
||
readScope:
|
||
type: string
|
||
description: Bearer scope a narrow token needs to read the FHIR face.
|
||
resourceTypes:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: FHIR resource types the REST face serves (read + search).
|
||
operations:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Whole-record operations exposed (e.g. $everything).
|
||
searchParams:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Search parameters honoured uniformly across the search routes.
|
||
required:
|
||
- atcSystem
|
||
- snomedRoute
|
||
- germanAtcDefaultLocales
|
||
- restBaseUrl
|
||
- readScope
|
||
- resourceTypes
|
||
- operations
|
||
- searchParams
|
||
additionalProperties: false
|
||
description: FHIR coding constants + the read-only REST face descriptor (v1.11).
|
||
share:
|
||
type: object
|
||
properties:
|
||
supported:
|
||
type: boolean
|
||
description: Whether clinician share links are served.
|
||
maxDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Maximum lifetime of a share link, in days. No never-expiring share.
|
||
resourceTypes:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: FHIR resource types a share link may be scoped to serve.
|
||
sections:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Scopeable report sections a share link may toggle.
|
||
required:
|
||
- supported
|
||
- maxDays
|
||
- resourceTypes
|
||
- sections
|
||
additionalProperties: false
|
||
description: Clinician share-link surface descriptor (v1.11).
|
||
required:
|
||
- apiContractVersion
|
||
- derivedMetricIds
|
||
- vitalsBaselineTypes
|
||
- layoutTileIds
|
||
- metricStatusIds
|
||
- ingest
|
||
- fhir
|
||
- share
|
||
additionalProperties: false
|
||
description: Live id vocabularies + contract version. Every list is derived server-side from the canonical registry it
|
||
documents, so it cannot drift from the values the routes actually accept/emit.
|
||
ShareLinkCreated:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
label:
|
||
type: string
|
||
rangeStart:
|
||
type: string
|
||
rangeEnd:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
resourceTypes:
|
||
type: array
|
||
items:
|
||
type: string
|
||
allowFhirApi:
|
||
type: boolean
|
||
expiresAt:
|
||
type: string
|
||
createdAt:
|
||
type: string
|
||
revokedAt:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
lastAccessAt:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
accessCount:
|
||
type: number
|
||
active:
|
||
type: boolean
|
||
token:
|
||
type: string
|
||
description: Raw `hls_` token — returned ONCE and unrecoverable thereafter.
|
||
required:
|
||
- id
|
||
- label
|
||
- rangeStart
|
||
- rangeEnd
|
||
- resourceTypes
|
||
- allowFhirApi
|
||
- expiresAt
|
||
- createdAt
|
||
- revokedAt
|
||
- lastAccessAt
|
||
- accessCount
|
||
- active
|
||
- token
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
ShareLinkList:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
shareLinks:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
label:
|
||
type: string
|
||
rangeStart:
|
||
type: string
|
||
rangeEnd:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
resourceTypes:
|
||
type: array
|
||
items:
|
||
type: string
|
||
allowFhirApi:
|
||
type: boolean
|
||
expiresAt:
|
||
type: string
|
||
createdAt:
|
||
type: string
|
||
revokedAt:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
lastAccessAt:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
accessCount:
|
||
type: number
|
||
active:
|
||
type: boolean
|
||
required:
|
||
- id
|
||
- label
|
||
- rangeStart
|
||
- rangeEnd
|
||
- resourceTypes
|
||
- allowFhirApi
|
||
- expiresAt
|
||
- createdAt
|
||
- revokedAt
|
||
- lastAccessAt
|
||
- accessCount
|
||
- active
|
||
additionalProperties: false
|
||
required:
|
||
- shareLinks
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
ShareLinkRevoked:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
revoked:
|
||
type: boolean
|
||
required:
|
||
- id
|
||
- revoked
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
LoginResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/AccessRefreshBundle"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
AccessRefreshBundle:
|
||
type: object
|
||
properties:
|
||
user:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
username:
|
||
type: string
|
||
required:
|
||
- id
|
||
- username
|
||
additionalProperties: false
|
||
token:
|
||
type: string
|
||
description: Access token (`hlk_<64hex>`).
|
||
tokenExpiresAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
refreshToken:
|
||
description: Refresh token (`hlr_<64hex>`); only present for native-policy callers.
|
||
type: string
|
||
refreshTokenExpiresAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- token
|
||
- tokenExpiresAt
|
||
additionalProperties: false
|
||
description: Native-client token bundle returned by login + refresh. Web cookie-only callers see only `user`.
|
||
PasskeyLoginVerifyResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/AccessRefreshBundle"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
RefreshResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/AccessRefreshBundle"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
SyncStateEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/SyncStateResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
SyncStateResponse:
|
||
type: object
|
||
properties:
|
||
userId:
|
||
type: string
|
||
timezone:
|
||
type: string
|
||
lastSyncedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
serverNow:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
measurements:
|
||
type: object
|
||
properties:
|
||
lastUpdatedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
liveCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
tombstonedCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- lastUpdatedAt
|
||
- liveCount
|
||
- tombstonedCount
|
||
additionalProperties: false
|
||
mood:
|
||
type: object
|
||
properties:
|
||
lastUpdatedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
liveCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
tombstonedCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- lastUpdatedAt
|
||
- liveCount
|
||
- tombstonedCount
|
||
additionalProperties: false
|
||
intakes:
|
||
type: object
|
||
properties:
|
||
lastUpdatedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
liveCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
tombstonedCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- lastUpdatedAt
|
||
- liveCount
|
||
- tombstonedCount
|
||
additionalProperties: false
|
||
sync:
|
||
type: object
|
||
properties:
|
||
incrementalWindowDays:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
description: Days an incremental delta stays valid; tracks the native refresh-token lifetime. Beyond it a device
|
||
re-pairs with a full backfill. iOS derives its window from this rather than hardcoding 60.
|
||
tombstoneRetentionDays:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
description: Horizon past which tombstones may be pruned. A cursor older than this gets `cursorExpired` on
|
||
`/api/sync/changes`.
|
||
required:
|
||
- incrementalWindowDays
|
||
- tombstoneRetentionDays
|
||
additionalProperties: false
|
||
description: Sync-window metadata the client reads instead of hardcoding.
|
||
required:
|
||
- userId
|
||
- timezone
|
||
- lastSyncedAt
|
||
- serverNow
|
||
- measurements
|
||
- mood
|
||
- intakes
|
||
- sync
|
||
additionalProperties: false
|
||
description: iOS SyncMode handshake. Each GET also advances the server-side `lastSyncedAt` checkpoint and returns the
|
||
previous value. The cheap 'should I sync?' summary; the durable delta cursor lives on `/api/sync/changes`.
|
||
SyncChangesEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/SyncChangesResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
SyncChangesResponse:
|
||
type: object
|
||
properties:
|
||
serverNow:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
cursor:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Opaque cursor to echo into the next request.
|
||
hasMore:
|
||
type: boolean
|
||
description: False once the client is caught up as of `serverNow`.
|
||
cursorExpired:
|
||
type: boolean
|
||
description: True when the supplied cursor predates tombstone retention — drop the cursor and do a clean initial sync.
|
||
changes:
|
||
type: object
|
||
properties:
|
||
measurements:
|
||
type: object
|
||
properties:
|
||
upserts:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncMeasurementUpsert"
|
||
tombstones:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncMeasurementTombstone"
|
||
required:
|
||
- upserts
|
||
- tombstones
|
||
additionalProperties: false
|
||
mood:
|
||
type: object
|
||
properties:
|
||
upserts:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncMoodUpsert"
|
||
tombstones:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncMoodTombstone"
|
||
required:
|
||
- upserts
|
||
- tombstones
|
||
additionalProperties: false
|
||
intakes:
|
||
type: object
|
||
properties:
|
||
upserts:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncIntakeUpsert"
|
||
tombstones:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/SyncIntakeTombstone"
|
||
required:
|
||
- upserts
|
||
- tombstones
|
||
additionalProperties: false
|
||
required:
|
||
- measurements
|
||
- mood
|
||
- intakes
|
||
additionalProperties: false
|
||
required:
|
||
- serverNow
|
||
- cursor
|
||
- hasMore
|
||
- cursorExpired
|
||
- changes
|
||
additionalProperties: false
|
||
description: "Multi-domain delta page (v1.7.0): measurements + mood + intakes. One opaque multi-domain keyset cursor;
|
||
tombstones apply before upserts within each domain. Tombstone identity: measurements key on externalId, mood +
|
||
intakes on server id. The iOS consumer is measurements-only this cycle; mood + intakes are forward-prep."
|
||
SyncMeasurementUpsert:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
type:
|
||
type: string
|
||
enum:
|
||
- WEIGHT
|
||
- BLOOD_PRESSURE_SYS
|
||
- BLOOD_PRESSURE_DIA
|
||
- PULSE
|
||
- BODY_FAT
|
||
- SLEEP_DURATION
|
||
- ACTIVITY_STEPS
|
||
- BLOOD_GLUCOSE
|
||
- TOTAL_BODY_WATER
|
||
- BONE_MASS
|
||
- OXYGEN_SATURATION
|
||
- HEART_RATE_VARIABILITY
|
||
- RESTING_HEART_RATE
|
||
- ACTIVE_ENERGY_BURNED
|
||
- FLIGHTS_CLIMBED
|
||
- WALKING_RUNNING_DISTANCE
|
||
- VO2_MAX
|
||
- BODY_TEMPERATURE
|
||
- FAT_FREE_MASS
|
||
- FAT_MASS
|
||
- MUSCLE_MASS
|
||
- SKIN_TEMPERATURE
|
||
- PULSE_WAVE_VELOCITY
|
||
- VASCULAR_AGE
|
||
- VISCERAL_FAT
|
||
- AUDIO_EXPOSURE_ENV
|
||
- AUDIO_EXPOSURE_HEADPHONE
|
||
- TIME_IN_DAYLIGHT
|
||
- WALKING_STEADINESS
|
||
- AUDIO_EXPOSURE_EVENT
|
||
- RESPIRATORY_RATE
|
||
- BODY_MASS_INDEX
|
||
- LEAN_BODY_MASS
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
- IRREGULAR_RHYTHM_NOTIFICATION
|
||
- HIGH_HEART_RATE_EVENT
|
||
- LOW_HEART_RATE_EVENT
|
||
- WALKING_STEADINESS_EVENT
|
||
- BREATHING_DISTURBANCE_EVENT
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
- HRV_RMSSD
|
||
- DAY_STRAIN
|
||
- WORKOUT_STRAIN
|
||
- SLEEP_PERFORMANCE
|
||
- SLEEP_EFFICIENCY
|
||
- SLEEP_CONSISTENCY
|
||
- SLEEP_NEED
|
||
- ENERGY_EXPENDITURE_KJ
|
||
- AVERAGE_HEART_RATE
|
||
- MAX_HEART_RATE
|
||
- SLEEP_DISTURBANCE_COUNT
|
||
value:
|
||
type: number
|
||
unit:
|
||
type: string
|
||
measuredAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
source:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
notes:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
externalId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Cross-device dedup key (UUID string or `stats:<id>:<date>`).
|
||
syncVersion:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
description: LWW reconciliation counter; echo to keep the mirror monotonic.
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- id
|
||
- type
|
||
- value
|
||
- unit
|
||
- measuredAt
|
||
- source
|
||
- externalId
|
||
- syncVersion
|
||
- updatedAt
|
||
additionalProperties: false
|
||
SyncMeasurementTombstone:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
externalId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: The identity key the client dedups on for measurements.
|
||
syncVersion:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
deletedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- id
|
||
- externalId
|
||
- syncVersion
|
||
- deletedAt
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: A soft-deleted measurement. Apply tombstones BEFORE upserts within a page to avoid resurrecting a row.
|
||
SyncMoodUpsert:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
date:
|
||
type: string
|
||
description: YYYY-MM-DD anchored to the row's `tz`.
|
||
mood:
|
||
type: string
|
||
score:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
tags:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: JSON array of tag keys, or null.
|
||
note:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
moodLoggedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
source:
|
||
type: string
|
||
syncVersion:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: LWW reconciliation counter; mood is last-writer-wins by it.
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- id
|
||
- date
|
||
- mood
|
||
- score
|
||
- tags
|
||
- note
|
||
- moodLoggedAt
|
||
- source
|
||
- syncVersion
|
||
- updatedAt
|
||
additionalProperties: false
|
||
SyncMoodTombstone:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
description: Server id — the identity key the client dedups on for mood.
|
||
syncVersion:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
deletedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- id
|
||
- syncVersion
|
||
- deletedAt
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: A soft-deleted mood entry, keyed on server `id`. Apply before upserts within the domain page.
|
||
SyncIntakeUpsert:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
medicationId:
|
||
type: string
|
||
scheduledFor:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
takenAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
skipped:
|
||
type: boolean
|
||
source:
|
||
type: string
|
||
syncVersion:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Reconciliation counter. An intake is immutable; a correction is a tombstone + re-insert.
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- id
|
||
- medicationId
|
||
- scheduledFor
|
||
- takenAt
|
||
- skipped
|
||
- source
|
||
- syncVersion
|
||
- updatedAt
|
||
additionalProperties: false
|
||
SyncIntakeTombstone:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
description: Server id — the identity key the client dedups on for intakes.
|
||
syncVersion:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
deletedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- id
|
||
- syncVersion
|
||
- deletedAt
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: A soft-deleted medication intake, keyed on server `id`. Apply before upserts within the domain page.
|
||
ListMeasurementsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
measurements:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MeasurementResource"
|
||
total:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- measurements
|
||
- total
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MeasurementResource:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
type:
|
||
type: string
|
||
enum:
|
||
- WEIGHT
|
||
- BLOOD_PRESSURE_SYS
|
||
- BLOOD_PRESSURE_DIA
|
||
- PULSE
|
||
- BODY_FAT
|
||
- SLEEP_DURATION
|
||
- ACTIVITY_STEPS
|
||
- BLOOD_GLUCOSE
|
||
- TOTAL_BODY_WATER
|
||
- BONE_MASS
|
||
- OXYGEN_SATURATION
|
||
- HEART_RATE_VARIABILITY
|
||
- RESTING_HEART_RATE
|
||
- ACTIVE_ENERGY_BURNED
|
||
- FLIGHTS_CLIMBED
|
||
- WALKING_RUNNING_DISTANCE
|
||
- VO2_MAX
|
||
- BODY_TEMPERATURE
|
||
- FAT_FREE_MASS
|
||
- FAT_MASS
|
||
- MUSCLE_MASS
|
||
- SKIN_TEMPERATURE
|
||
- PULSE_WAVE_VELOCITY
|
||
- VASCULAR_AGE
|
||
- VISCERAL_FAT
|
||
- AUDIO_EXPOSURE_ENV
|
||
- AUDIO_EXPOSURE_HEADPHONE
|
||
- TIME_IN_DAYLIGHT
|
||
- WALKING_STEADINESS
|
||
- AUDIO_EXPOSURE_EVENT
|
||
- RESPIRATORY_RATE
|
||
- BODY_MASS_INDEX
|
||
- LEAN_BODY_MASS
|
||
- WALKING_HEART_RATE_AVERAGE
|
||
- WALKING_ASYMMETRY
|
||
- WALKING_DOUBLE_SUPPORT
|
||
- WALKING_STEP_LENGTH
|
||
- WALKING_SPEED
|
||
- CARDIO_RECOVERY
|
||
- WRIST_TEMPERATURE
|
||
- FALL_COUNT
|
||
- SIX_MINUTE_WALK_DISTANCE
|
||
- STAIR_ASCENT_SPEED
|
||
- STAIR_DESCENT_SPEED
|
||
- BREATHING_DISTURBANCES
|
||
- IRREGULAR_RHYTHM_NOTIFICATION
|
||
- HIGH_HEART_RATE_EVENT
|
||
- LOW_HEART_RATE_EVENT
|
||
- WALKING_STEADINESS_EVENT
|
||
- BREATHING_DISTURBANCE_EVENT
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
- HRV_RMSSD
|
||
- DAY_STRAIN
|
||
- WORKOUT_STRAIN
|
||
- SLEEP_PERFORMANCE
|
||
- SLEEP_EFFICIENCY
|
||
- SLEEP_CONSISTENCY
|
||
- SLEEP_NEED
|
||
- ENERGY_EXPENDITURE_KJ
|
||
- AVERAGE_HEART_RATE
|
||
- MAX_HEART_RATE
|
||
- SLEEP_DISTURBANCE_COUNT
|
||
value:
|
||
type: number
|
||
unit:
|
||
type: string
|
||
measuredAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
source:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
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
|
||
MeasurementsBulkDeleteResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MeasurementsBulkDeleteResult"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MeasurementsBulkDeleteResult:
|
||
type: object
|
||
properties:
|
||
deleted:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- deleted
|
||
additionalProperties: false
|
||
MeasurementsRestoreResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MeasurementsRestoreResult"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MeasurementsRestoreResult:
|
||
type: object
|
||
properties:
|
||
restored:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- restored
|
||
additionalProperties: false
|
||
SleepNightResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/SleepNightResource"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
SleepNightResource:
|
||
type: object
|
||
properties:
|
||
night:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
main:
|
||
anyOf:
|
||
- type: object
|
||
properties:
|
||
night:
|
||
type: string
|
||
description: Wake-day key (YYYY-MM-DD).
|
||
source:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
- type: "null"
|
||
start:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
end:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
asleepMinutes:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
inBedMinutes:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
awakeMinutes:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
awakenings:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
stages:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
enum:
|
||
- IN_BED
|
||
- AWAKE
|
||
- ASLEEP
|
||
- REM
|
||
- CORE
|
||
- DEEP
|
||
additionalProperties:
|
||
type: number
|
||
required:
|
||
- IN_BED
|
||
- AWAKE
|
||
- ASLEEP
|
||
- REM
|
||
- CORE
|
||
- DEEP
|
||
segments:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
stage:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- IN_BED
|
||
- AWAKE
|
||
- ASLEEP
|
||
- REM
|
||
- CORE
|
||
- DEEP
|
||
- type: "null"
|
||
start:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
end:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
minutes:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- stage
|
||
- start
|
||
- end
|
||
- minutes
|
||
additionalProperties: false
|
||
required:
|
||
- night
|
||
- source
|
||
- start
|
||
- end
|
||
- asleepMinutes
|
||
- inBedMinutes
|
||
- awakeMinutes
|
||
- awakenings
|
||
- stages
|
||
- segments
|
||
additionalProperties: false
|
||
- type: "null"
|
||
naps:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
night:
|
||
type: string
|
||
description: Wake-day key (YYYY-MM-DD).
|
||
source:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
- type: "null"
|
||
start:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
end:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
asleepMinutes:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
inBedMinutes:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
awakeMinutes:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
awakenings:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
stages:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
enum:
|
||
- IN_BED
|
||
- AWAKE
|
||
- ASLEEP
|
||
- REM
|
||
- CORE
|
||
- DEEP
|
||
additionalProperties:
|
||
type: number
|
||
required:
|
||
- IN_BED
|
||
- AWAKE
|
||
- ASLEEP
|
||
- REM
|
||
- CORE
|
||
- DEEP
|
||
segments:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
stage:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- IN_BED
|
||
- AWAKE
|
||
- ASLEEP
|
||
- REM
|
||
- CORE
|
||
- DEEP
|
||
- type: "null"
|
||
start:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
end:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
minutes:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- stage
|
||
- start
|
||
- end
|
||
- minutes
|
||
additionalProperties: false
|
||
required:
|
||
- night
|
||
- source
|
||
- start
|
||
- end
|
||
- asleepMinutes
|
||
- inBedMinutes
|
||
- awakeMinutes
|
||
- awakenings
|
||
- stages
|
||
- segments
|
||
additionalProperties: false
|
||
required:
|
||
- night
|
||
- main
|
||
- naps
|
||
additionalProperties: false
|
||
description: "One reconstructed sleep night: the main session's hypnogram segments + breakdown, plus same-wake-day naps
|
||
surfaced separately."
|
||
ListWorkoutsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/WorkoutListResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
WorkoutListResponse:
|
||
type: object
|
||
properties:
|
||
workouts:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/WorkoutListEntry"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
total:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
limit:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
offset:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
droppedDuplicates:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- total
|
||
- limit
|
||
- offset
|
||
- droppedDuplicates
|
||
additionalProperties: false
|
||
required:
|
||
- workouts
|
||
- meta
|
||
additionalProperties: false
|
||
WorkoutListEntry:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
sportType:
|
||
type: string
|
||
startedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
endedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
durationSec:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
distanceM:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
activeEnergyKcal:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
avgHr:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
maxHr:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
source:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
externalId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
required:
|
||
- id
|
||
- sportType
|
||
- startedAt
|
||
- endedAt
|
||
- durationSec
|
||
- distanceM
|
||
- activeEnergyKcal
|
||
- avgHr
|
||
- maxHr
|
||
- source
|
||
- externalId
|
||
additionalProperties: false
|
||
GetWorkoutResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/WorkoutDetailResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
WorkoutDetailResponse:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
sportType:
|
||
type: string
|
||
startedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
endedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
durationSec:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
distanceM:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
activeEnergyKcal:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
avgHr:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
maxHr:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
minHr:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
stepCount:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
elevationM:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
pauseDurationSec:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
source:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
externalId:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
metadata:
|
||
anyOf:
|
||
- {}
|
||
- type: "null"
|
||
route:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/WorkoutRouteGeometry"
|
||
- type: "null"
|
||
samples:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/WorkoutHrSeries"
|
||
- type: "null"
|
||
canonicalId:
|
||
type: string
|
||
required:
|
||
- id
|
||
- sportType
|
||
- startedAt
|
||
- endedAt
|
||
- durationSec
|
||
- distanceM
|
||
- activeEnergyKcal
|
||
- avgHr
|
||
- maxHr
|
||
- minHr
|
||
- stepCount
|
||
- elevationM
|
||
- pauseDurationSec
|
||
- source
|
||
- externalId
|
||
- metadata
|
||
- route
|
||
- samples
|
||
- canonicalId
|
||
additionalProperties: false
|
||
WorkoutRouteGeometry:
|
||
type: object
|
||
properties:
|
||
geometry: {}
|
||
sampleTimestamps:
|
||
anyOf:
|
||
- type: array
|
||
items:
|
||
type: string
|
||
- type: "null"
|
||
required:
|
||
- geometry
|
||
- sampleTimestamps
|
||
additionalProperties: false
|
||
WorkoutHrSeries:
|
||
type: object
|
||
properties:
|
||
sampleCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
samples: {}
|
||
required:
|
||
- sampleCount
|
||
- samples
|
||
additionalProperties: false
|
||
BatchWorkoutsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/WorkoutBatchResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
WorkoutBatchResponse:
|
||
type: object
|
||
properties:
|
||
processed:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
inserted:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
duplicates:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
skipped:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
index:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
reason:
|
||
type: string
|
||
required:
|
||
- index
|
||
- reason
|
||
additionalProperties: false
|
||
entries:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/WorkoutBatchEntryResult"
|
||
required:
|
||
- processed
|
||
- inserted
|
||
- duplicates
|
||
- skipped
|
||
- entries
|
||
additionalProperties: false
|
||
WorkoutBatchEntryResult:
|
||
type: object
|
||
properties:
|
||
index:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
status:
|
||
type: string
|
||
enum:
|
||
- inserted
|
||
- duplicate
|
||
- skipped
|
||
reason:
|
||
type: string
|
||
required:
|
||
- index
|
||
- status
|
||
additionalProperties: false
|
||
DeviceRegisterResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
required:
|
||
- id
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
ListMedicationsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationListEntry"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationListEntry:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
name:
|
||
type: string
|
||
dose:
|
||
type: string
|
||
treatmentClass:
|
||
$ref: "#/components/schemas/MedicationTreatmentClass"
|
||
dosesPerUnit:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Doses per pen / vial for inventory tracking. NULL = inventory tracking off.
|
||
active:
|
||
type: boolean
|
||
notificationsEnabled:
|
||
type: boolean
|
||
liveActivityEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS Live Activity opt-in for this medication's reminders. Default false. The iOS client owns the
|
||
ActivityKit lifecycle; the server only stores + echoes the flag.
|
||
criticalAlarmEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS 26 AlarmKit critical-reminder opt-in. Default false. Critical alarms bypass the device mute
|
||
switch / Focus; the server stores the preference only.
|
||
atcCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional WHO ATC classification code (active-substance class, e.g. `A10BX10`).
|
||
User/clinician-asserted; never machine-guessed. Emitted on the FHIR `medicationCodeableConcept` under
|
||
`http://www.whocc.no/atc`. NULL = no code captured.
|
||
rxNormCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional RxNorm RxCUI (numeric, US identifier, e.g. `2601723`). Secondary FHIR coding under
|
||
`http://www.nlm.nih.gov/research/umls/rxnorm`, alongside any ATC code. NULL = no code captured.
|
||
pausedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
snoozedUntil:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
nextDueAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: "v1.7.0 server-computed next due instant across all the medication's schedules (earliest
|
||
`nextOccurrenceAfter`). Read-only — computed, not stored. NULL when no schedule has an upcoming slot
|
||
(paused, one-shot in the past, `endsOn` crossed, every schedule PRN). v1.16.4 — when an unresolved slot's
|
||
anchor has passed but the catch-up band is still open (`anchor < now <= overdueEnd`, current schedule era),
|
||
this carries THAT slot (a past instant) with `nextDueOverdue: true` instead of jumping to the next future
|
||
slot. The list GET is cached 60 s, so a 60 s staleness is accepted."
|
||
nextDueOverdue:
|
||
type: boolean
|
||
description: "v1.16.4 — true when `nextDueAt` is an OPEN overdue slot: its anchor has passed, `now` is still inside the
|
||
slot's catch-up band, and no taken / skipped / auto-missed row resolves it. False for a regular future
|
||
next-due (and when `nextDueAt` is null). Read-only — computed, not stored."
|
||
startsOn:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: v1.5 course start (ISO date). Anchors RRULE BYDAY / BYMONTHDAY patterns and the rolling-interval
|
||
countdown's first window. NULL means active from creation.
|
||
endsOn:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: v1.5 course end (ISO date). NULL means chronic. Equals `startsOn` when `oneShot` is true.
|
||
oneShot:
|
||
type: boolean
|
||
description: v1.5 single-administration flag. When true the medication has at most one schedule (no `rrule` /
|
||
`rollingIntervalDays`), and `active` auto-flips to false once the dose is logged.
|
||
createdAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
schedules:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleOutput"
|
||
category:
|
||
$ref: "#/components/schemas/MedicationCategory"
|
||
lastTakenAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: Latest non-skipped `MedicationIntakeEvent.takenAt` for the medication. Drives the rolling-cadence countdown
|
||
surface.
|
||
todayEventCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Number of intake events scheduled for today (user-local day window).
|
||
required:
|
||
- id
|
||
- name
|
||
- dose
|
||
- treatmentClass
|
||
- dosesPerUnit
|
||
- active
|
||
- notificationsEnabled
|
||
- liveActivityEnabled
|
||
- criticalAlarmEnabled
|
||
- atcCode
|
||
- rxNormCode
|
||
- pausedAt
|
||
- snoozedUntil
|
||
- nextDueAt
|
||
- nextDueOverdue
|
||
- startsOn
|
||
- endsOn
|
||
- oneShot
|
||
- createdAt
|
||
- updatedAt
|
||
- schedules
|
||
- category
|
||
- lastTakenAt
|
||
- todayEventCount
|
||
additionalProperties: false
|
||
description: List-row variant of the medication resource enriched with the joined `category`, `lastTakenAt`, and
|
||
`todayEventCount` fields the dashboard + iOS client consume. The base medication fields (`id`, `name`, `dose`,
|
||
`treatmentClass`, `dosesPerUnit`, `active`, `notificationsEnabled`, `pausedAt`, `snoozedUntil`, `startsOn`,
|
||
`endsOn`, `oneShot`, `createdAt`, `updatedAt`, `schedules`) are inlined; see the `Medication` component for
|
||
their semantics.
|
||
MedicationCategory:
|
||
type: string
|
||
enum:
|
||
- BLOOD_PRESSURE
|
||
- VITAMIN
|
||
- SUPPLEMENT
|
||
- PAIN_RELIEF
|
||
- ALLERGY
|
||
- DIGESTIVE
|
||
- THYROID
|
||
- HORMONE
|
||
- SKIN
|
||
- SLEEP_AID
|
||
- DIABETES
|
||
- ANTIBIOTIC
|
||
- OTHER
|
||
description: Clinical taxonomy stored in the `medication_categories` side-table. Orthogonal to `MedicationTreatmentClass`.
|
||
CreateMedicationResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationDetail"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationDetail:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
name:
|
||
type: string
|
||
dose:
|
||
type: string
|
||
treatmentClass:
|
||
$ref: "#/components/schemas/MedicationTreatmentClass"
|
||
dosesPerUnit:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Doses per pen / vial for inventory tracking. NULL = inventory tracking off.
|
||
active:
|
||
type: boolean
|
||
notificationsEnabled:
|
||
type: boolean
|
||
liveActivityEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS Live Activity opt-in for this medication's reminders. Default false. The iOS client owns the
|
||
ActivityKit lifecycle; the server only stores + echoes the flag.
|
||
criticalAlarmEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS 26 AlarmKit critical-reminder opt-in. Default false. Critical alarms bypass the device mute
|
||
switch / Focus; the server stores the preference only.
|
||
atcCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional WHO ATC classification code (active-substance class, e.g. `A10BX10`).
|
||
User/clinician-asserted; never machine-guessed. Emitted on the FHIR `medicationCodeableConcept` under
|
||
`http://www.whocc.no/atc`. NULL = no code captured.
|
||
rxNormCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional RxNorm RxCUI (numeric, US identifier, e.g. `2601723`). Secondary FHIR coding under
|
||
`http://www.nlm.nih.gov/research/umls/rxnorm`, alongside any ATC code. NULL = no code captured.
|
||
pausedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
snoozedUntil:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
nextDueAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: "v1.7.0 server-computed next due instant across all the medication's schedules (earliest
|
||
`nextOccurrenceAfter`). Read-only — computed, not stored. NULL when no schedule has an upcoming slot
|
||
(paused, one-shot in the past, `endsOn` crossed, every schedule PRN). v1.16.4 — when an unresolved slot's
|
||
anchor has passed but the catch-up band is still open (`anchor < now <= overdueEnd`, current schedule era),
|
||
this carries THAT slot (a past instant) with `nextDueOverdue: true` instead of jumping to the next future
|
||
slot. The list GET is cached 60 s, so a 60 s staleness is accepted."
|
||
nextDueOverdue:
|
||
type: boolean
|
||
description: "v1.16.4 — true when `nextDueAt` is an OPEN overdue slot: its anchor has passed, `now` is still inside the
|
||
slot's catch-up band, and no taken / skipped / auto-missed row resolves it. False for a regular future
|
||
next-due (and when `nextDueAt` is null). Read-only — computed, not stored."
|
||
startsOn:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: v1.5 course start (ISO date). Anchors RRULE BYDAY / BYMONTHDAY patterns and the rolling-interval
|
||
countdown's first window. NULL means active from creation.
|
||
endsOn:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: v1.5 course end (ISO date). NULL means chronic. Equals `startsOn` when `oneShot` is true.
|
||
oneShot:
|
||
type: boolean
|
||
description: v1.5 single-administration flag. When true the medication has at most one schedule (no `rrule` /
|
||
`rollingIntervalDays`), and `active` auto-flips to false once the dose is logged.
|
||
createdAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
schedules:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleOutput"
|
||
category:
|
||
$ref: "#/components/schemas/MedicationCategory"
|
||
required:
|
||
- id
|
||
- name
|
||
- dose
|
||
- treatmentClass
|
||
- dosesPerUnit
|
||
- active
|
||
- notificationsEnabled
|
||
- liveActivityEnabled
|
||
- criticalAlarmEnabled
|
||
- atcCode
|
||
- rxNormCode
|
||
- pausedAt
|
||
- snoozedUntil
|
||
- nextDueAt
|
||
- nextDueOverdue
|
||
- startsOn
|
||
- endsOn
|
||
- oneShot
|
||
- createdAt
|
||
- updatedAt
|
||
- schedules
|
||
- category
|
||
additionalProperties: false
|
||
description: Detail variant of the medication resource enriched with the joined `category`. The base medication fields
|
||
are inlined; see the `Medication` component for their semantics.
|
||
GetMedicationResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationDetail"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
UpdateMedicationResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationDetail"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
DeleteMedicationResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
deleted:
|
||
type: boolean
|
||
required:
|
||
- deleted
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
ReplayMedicationIntakeResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationIntakeEvent"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationIntakeEvent:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
userId:
|
||
type: string
|
||
medicationId:
|
||
type: string
|
||
scheduledFor:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
takenAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
skipped:
|
||
type: boolean
|
||
source:
|
||
type: string
|
||
enum:
|
||
- WEB
|
||
- API
|
||
- REMINDER
|
||
- IMPORT
|
||
idempotencyKey:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
createdAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- id
|
||
- userId
|
||
- medicationId
|
||
- scheduledFor
|
||
- takenAt
|
||
- skipped
|
||
- source
|
||
- idempotencyKey
|
||
- createdAt
|
||
additionalProperties: false
|
||
description: Single dose log row. `takenAt` is non-null for confirmed intakes; `skipped:true` represents a
|
||
deliberately-missed dose (no inventory consumption).
|
||
CreateMedicationIntakeResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationIntakeEvent"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationExtractResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
name:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 100
|
||
dose:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 50
|
||
doseUnit:
|
||
type: string
|
||
enum:
|
||
- mg
|
||
- ml
|
||
- iu
|
||
- tablets
|
||
- drops
|
||
- puffs
|
||
- sprays
|
||
cadenceKind:
|
||
type: string
|
||
enum:
|
||
- daily
|
||
- weekdays
|
||
- everyNWeeks
|
||
- monthly
|
||
- everyNMonths
|
||
- yearly
|
||
- rolling
|
||
- oneShot
|
||
weekdays:
|
||
maxItems: 7
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MO
|
||
- TU
|
||
- WE
|
||
- TH
|
||
- FR
|
||
- SA
|
||
- SU
|
||
intervalWeeks:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 52
|
||
intervalMonths:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 12
|
||
dayOfMonth:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 31
|
||
rollingIntervalDays:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 365
|
||
timesOfDay:
|
||
maxItems: 12
|
||
type: array
|
||
items:
|
||
type: string
|
||
pattern: ^([01]\d|2[0-3]):[0-5]\d$
|
||
startsOn:
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
endsOn:
|
||
type: string
|
||
pattern: ^\d{4}-\d{2}-\d{2}$
|
||
oneShot:
|
||
type: boolean
|
||
confidence:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties:
|
||
type: number
|
||
minimum: 0
|
||
maximum: 1
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
GetMedicationCadenceResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationCadenceResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationCadenceResponse:
|
||
type: object
|
||
properties:
|
||
windowDays:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
anchorIso:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
next:
|
||
anyOf:
|
||
- type: object
|
||
properties:
|
||
windowStart:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
windowEnd:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
scheduleIndex:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- windowStart
|
||
- windowEnd
|
||
- scheduleIndex
|
||
additionalProperties: false
|
||
- type: "null"
|
||
chips:
|
||
$ref: "#/components/schemas/MedicationCadenceChips"
|
||
timeline:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationCadenceTimelinePoint"
|
||
required:
|
||
- windowDays
|
||
- anchorIso
|
||
- next
|
||
- chips
|
||
- timeline
|
||
additionalProperties: false
|
||
description: Cadence + compliance read for a single medication. `next` is the upcoming-dose envelope (null when the
|
||
course has ended or the rolling clock has no pinning intake yet); `timeline` walks the requested `windowDays`
|
||
worth of slots in ascending time order.
|
||
MedicationCadenceChips:
|
||
type: object
|
||
properties:
|
||
adherenceRate:
|
||
type: number
|
||
streakDays:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
expectedSlots:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
actualDoses:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- adherenceRate
|
||
- streakDays
|
||
- expectedSlots
|
||
- actualDoses
|
||
additionalProperties: false
|
||
description: Four compliance summary values for the medication detail page chip row.
|
||
MedicationCadenceTimelinePoint:
|
||
type: object
|
||
properties:
|
||
day:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
windowStart:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
windowEnd:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
scheduleIndex:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
status:
|
||
type: string
|
||
required:
|
||
- day
|
||
- windowStart
|
||
- windowEnd
|
||
- scheduleIndex
|
||
- status
|
||
additionalProperties: false
|
||
description: One expected-vs-actual dose slot for the cadence timeline chart. `status` is one of `taken | skipped |
|
||
missed | pending | future` and drives the chip colour.
|
||
ListMedicationComplianceResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationComplianceSummaryEntry"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationComplianceSummaryEntry:
|
||
type: object
|
||
properties:
|
||
medicationId:
|
||
type: string
|
||
compliance7:
|
||
$ref: "#/components/schemas/ComplianceResult"
|
||
compliance30:
|
||
$ref: "#/components/schemas/ComplianceResult"
|
||
complianceDisplay:
|
||
$ref: "#/components/schemas/ComplianceDisplay"
|
||
required:
|
||
- medicationId
|
||
- compliance7
|
||
- compliance30
|
||
- complianceDisplay
|
||
additionalProperties: false
|
||
description: "Compact per-medication adherence row for the batched card read: the 7-/30-day summaries plus the
|
||
cadence-scaled display block. The per-day `dailyCompliance` grid is NOT on this shape — read the per-medication
|
||
`/compliance` endpoint for the history glyph track."
|
||
ComplianceResult:
|
||
type: object
|
||
properties:
|
||
totalExpected:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: "Full denominator over the window: `taken + skipped + missed`. Cadence-aware and clamped to the
|
||
medication's `createdAt`."
|
||
taken:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
skipped:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Doses the user explicitly skipped — excluded from the `rate` denominator.
|
||
missed:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
rate:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 100
|
||
description: Adherence percentage `round(taken / (taken + missed) * 100)` — `skipped` is excluded from the denominator.
|
||
streak:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Consecutive days with every due dose taken.
|
||
required:
|
||
- totalExpected
|
||
- taken
|
||
- skipped
|
||
- missed
|
||
- rate
|
||
- streak
|
||
additionalProperties: false
|
||
description: Rolling-window adherence summary. `compliance30` is the authoritative 'last 30 days, taken vs expected'
|
||
read — clients should display `rate` and use `totalExpected` as the denominator rather than re-deriving it from
|
||
the daily map.
|
||
ComplianceDisplay:
|
||
type: object
|
||
properties:
|
||
shortDays:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
longDays:
|
||
type: integer
|
||
exclusiveMinimum: 0
|
||
maximum: 9007199254740991
|
||
expectedShort:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
expectedLong:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
minStableDoses:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
short:
|
||
type: object
|
||
properties:
|
||
rate:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 100
|
||
taken:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
expected:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: v1.15.9 rate denominator over the short window (`taken + missed`); user skips are excluded. Render `taken /
|
||
expected · rate%`.
|
||
missed:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: v1.15.9 doses counted against the rate over the short window — includes forgotten doses the auto-miss cron
|
||
flipped.
|
||
streak:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- rate
|
||
- taken
|
||
- expected
|
||
- missed
|
||
- streak
|
||
additionalProperties: false
|
||
long:
|
||
type: object
|
||
properties:
|
||
rate:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 100
|
||
taken:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
expected:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: v1.15.9 rate denominator over the long window (`taken + missed`).
|
||
missed:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: v1.15.9 doses counted against the rate over the long window.
|
||
required:
|
||
- rate
|
||
- taken
|
||
- expected
|
||
- missed
|
||
additionalProperties: false
|
||
currentCycle:
|
||
type: object
|
||
properties:
|
||
state:
|
||
type: string
|
||
enum:
|
||
- on_track
|
||
- due
|
||
- missed
|
||
- none
|
||
description: "Open-cycle state, decoupled from the percentage rows: `on_track` = next dose not yet due; `due` = due now
|
||
/ in grace; `missed` = past grace with no logged intake (the only red state); `none` = no projected next
|
||
dose (PRN / paused / ended)."
|
||
nextDueAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: The open cycle's due instant. Null when `state` is `none`.
|
||
graceUntil:
|
||
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: End of the due slot's grace window. Null when `state` is `none`.
|
||
hasClosedCycles:
|
||
type: boolean
|
||
description: False for a brand-new sparse med with zero closed dose cycles — the percentage rows are vacuous and the
|
||
card should show a neutral 'not enough data yet' state.
|
||
required:
|
||
- state
|
||
- nextDueAt
|
||
- graceUntil
|
||
- hasClosedCycles
|
||
additionalProperties: false
|
||
description: v1.13.x — the current (open) dose cycle, surfaced so a between-doses sparse med renders a neutral 'next
|
||
dose in N days' line instead of a scary red 0%. The percentage rows above already exclude the open forward
|
||
cycle from their denominator.
|
||
currentDose:
|
||
type: object
|
||
properties:
|
||
status:
|
||
type: string
|
||
enum:
|
||
- upcoming
|
||
- on_time_window
|
||
- overdue
|
||
- missed
|
||
- taken_on_time
|
||
- taken_late
|
||
- skipped
|
||
description: Per-dose state of the open cycle, server-derived from the window model so the card renders the take-window
|
||
(green) / overdue / heavily-overdue escalation from one authority. `upcoming` when no dose is open (PRN
|
||
/ paused / ended).
|
||
targetAt:
|
||
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: Target instant of the open dose. Null when no dose is open.
|
||
required:
|
||
- status
|
||
- targetAt
|
||
additionalProperties: false
|
||
description: v1.15.9 — the open dose's per-dose status + target, so the card highlights the actionable take-window green
|
||
and escalates an overdue dose without re-deriving the window math client-side.
|
||
required:
|
||
- shortDays
|
||
- longDays
|
||
- expectedShort
|
||
- expectedLong
|
||
- minStableDoses
|
||
- short
|
||
- long
|
||
- currentCycle
|
||
- currentDose
|
||
additionalProperties: false
|
||
description: The two-row card block whose windows scale with dosing cadence (dense meds keep 7 / 30 days, sparse meds
|
||
step both windows up). NOT the 30-day denominator — read `compliance30.totalExpected` for that.
|
||
GetMedicationComplianceResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationComplianceResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationComplianceResponse:
|
||
type: object
|
||
properties:
|
||
compliance7:
|
||
$ref: "#/components/schemas/ComplianceResult"
|
||
compliance30:
|
||
$ref: "#/components/schemas/ComplianceResult"
|
||
dailyCompliance:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties:
|
||
$ref: "#/components/schemas/DailyComplianceEntry"
|
||
description: Flat per-day map keyed `YYYY-MM-DD` in the user timezone, one entry per day for up to 90 days back, clamped
|
||
to the medication's `createdAt` (so a recently-created med has fewer entries). No weekly/monthly collapse —
|
||
this is the raw daily grid.
|
||
complianceDisplay:
|
||
$ref: "#/components/schemas/ComplianceDisplay"
|
||
required:
|
||
- compliance7
|
||
- compliance30
|
||
- dailyCompliance
|
||
- complianceDisplay
|
||
additionalProperties: false
|
||
description: Adherence read for a single medication. `compliance30` is the authoritative 30-day taken-vs-expected
|
||
summary; `dailyCompliance` is the per-day grid for the history glyph track. The graded raw→week→month→year
|
||
series used elsewhere for AI prompts does NOT apply here — this response is never downsampled.
|
||
DailyComplianceEntry:
|
||
type: object
|
||
properties:
|
||
expected:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Engine-computed due-slot count for the day. Equals `expectedCount`; kept for existing consumers.
|
||
expectedCount:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: True due-slot count for the day (additive field clients key off so they don't infer due-ness from
|
||
`expected`).
|
||
due:
|
||
type: boolean
|
||
description: "`expectedCount > 0`. Paint a per-day glyph as expected/missed ONLY when `due === true`; off-cadence /
|
||
pre-creation / PRN days are not misses."
|
||
taken:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
skipped:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
onTime:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
description: Doses taken in the on-time band, including the `early` bucket (early counts as compliant).
|
||
late:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
veryLate:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
early:
|
||
description: Doses taken before the on-time band's grace start; already folded into `onTime`, surfaced separately for
|
||
consumers that differentiate.
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 9007199254740991
|
||
required:
|
||
- expected
|
||
- expectedCount
|
||
- due
|
||
- taken
|
||
- skipped
|
||
- onTime
|
||
- late
|
||
- veryLate
|
||
additionalProperties: false
|
||
description: Per-day compliance cell with the timing breakdown that drives the history glyph track.
|
||
ListScheduleRevisionsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
currentSince:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
|
||
description: "Instant the LIVE plan took over: the newest revision's `validUntil`, or the medication's `createdAt` when
|
||
no era has been archived."
|
||
revisions:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleRevision"
|
||
description: Archived eras, newest first.
|
||
required:
|
||
- currentSince
|
||
- revisions
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationScheduleRevision:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
validFrom:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
|
||
description: Inclusive start instant of the era.
|
||
validUntil:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$
|
||
description: Exclusive end instant of the era — the moment the next plan took over.
|
||
source:
|
||
type: string
|
||
enum:
|
||
- ARCHIVED
|
||
- MANUAL
|
||
description: Provenance. ARCHIVED = minted by the schedule-replace write path (immutable). MANUAL = user-entered
|
||
pre-tracking era (deletable).
|
||
entries:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/ScheduleRevisionEntry"
|
||
required:
|
||
- id
|
||
- validFrom
|
||
- validUntil
|
||
- source
|
||
- entries
|
||
additionalProperties: false
|
||
description: One archived schedule era covering `[validFrom, validUntil)`. The dose-history ledger, compliance tallies,
|
||
and cadence chips mint past days against the era that was live then.
|
||
ScheduleRevisionEntry:
|
||
type: object
|
||
properties:
|
||
timesOfDay:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Daily dose times (HH:mm, user local) the era ran at.
|
||
label:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
dose:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
scheduleType:
|
||
type: string
|
||
description: Schedule-type discriminator of the archived row (SCHEDULED / PRN / CYCLIC).
|
||
required:
|
||
- timesOfDay
|
||
- label
|
||
- dose
|
||
- scheduleType
|
||
additionalProperties: false
|
||
description: Display summary of one archived schedule row inside an era. The full snapshot (windows, rrule, doseWindows,
|
||
…) stays server-side; this projection carries what the timeline renders.
|
||
CreateScheduleRevisionResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationScheduleRevision"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
UpdateScheduleRevisionResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationScheduleRevision"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
DeleteScheduleRevisionResponse:
|
||
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
|
||
GetCoachPrefsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
tone:
|
||
default: warm
|
||
type: string
|
||
enum:
|
||
- warm
|
||
- neutral
|
||
- concise
|
||
verbosity:
|
||
default: default
|
||
type: string
|
||
enum:
|
||
- brief
|
||
- default
|
||
- detailed
|
||
excludeMetrics:
|
||
default: []
|
||
maxItems: 11
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- bp
|
||
- weight
|
||
- pulse
|
||
- mood
|
||
- compliance
|
||
- hrv
|
||
- sleep
|
||
- resting_hr
|
||
- steps
|
||
- medications
|
||
- anthropometrics
|
||
showEvidenceByDefault:
|
||
default: false
|
||
type: boolean
|
||
defaultWindow:
|
||
default: allTime
|
||
type: string
|
||
enum:
|
||
- last7days
|
||
- last30days
|
||
- last90days
|
||
- allTime
|
||
dataClusters:
|
||
maxItems: 10
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- cardio
|
||
- body
|
||
- activity
|
||
- workouts
|
||
- sleep
|
||
- mood
|
||
- glucose
|
||
- medication
|
||
- mobility
|
||
- environment
|
||
required:
|
||
- tone
|
||
- verbosity
|
||
- excludeMetrics
|
||
- showEvidenceByDefault
|
||
- defaultWindow
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
PutCoachPrefsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
tone:
|
||
default: warm
|
||
type: string
|
||
enum:
|
||
- warm
|
||
- neutral
|
||
- concise
|
||
verbosity:
|
||
default: default
|
||
type: string
|
||
enum:
|
||
- brief
|
||
- default
|
||
- detailed
|
||
excludeMetrics:
|
||
default: []
|
||
maxItems: 11
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- bp
|
||
- weight
|
||
- pulse
|
||
- mood
|
||
- compliance
|
||
- hrv
|
||
- sleep
|
||
- resting_hr
|
||
- steps
|
||
- medications
|
||
- anthropometrics
|
||
showEvidenceByDefault:
|
||
default: false
|
||
type: boolean
|
||
defaultWindow:
|
||
default: allTime
|
||
type: string
|
||
enum:
|
||
- last7days
|
||
- last30days
|
||
- last90days
|
||
- allTime
|
||
dataClusters:
|
||
maxItems: 10
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- cardio
|
||
- body
|
||
- activity
|
||
- workouts
|
||
- sleep
|
||
- mood
|
||
- glucose
|
||
- medication
|
||
- mobility
|
||
- environment
|
||
required:
|
||
- tone
|
||
- verbosity
|
||
- excludeMetrics
|
||
- showEvidenceByDefault
|
||
- defaultWindow
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
GetDisableCoachResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/DisableCoachFlagOutput"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
PatchDisableCoachResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/DisableCoachFlagOutput"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
GetSourcePriorityResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
skinTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
respiratoryRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
recovery:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
metricPriority:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
skinTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
respiratoryRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
recovery:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
additionalProperties: false
|
||
deviceTypePriority:
|
||
type: object
|
||
properties:
|
||
default:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
additionalProperties:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
PutSourcePriorityResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
skinTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
respiratoryRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
recovery:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
metricPriority:
|
||
type: object
|
||
properties:
|
||
steps:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
activeEnergy:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
walkingRunningDistance:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
flightsClimbed:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
sleep:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
weight:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bloodPressure:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
pulse:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyFat:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
bodyTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
spo2:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
hrv:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
restingHeartRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
vo2Max:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
skinTemperature:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
respiratoryRate:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
recovery:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- MANUAL
|
||
- WITHINGS
|
||
- IMPORT
|
||
- APPLE_HEALTH
|
||
- COMPUTED
|
||
- WHOOP
|
||
- FITBIT
|
||
additionalProperties: false
|
||
deviceTypePriority:
|
||
type: object
|
||
properties:
|
||
default:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
additionalProperties:
|
||
maxItems: 8
|
||
type: array
|
||
items:
|
||
type: string
|
||
enum:
|
||
- watch
|
||
- band
|
||
- ring
|
||
- phone
|
||
- scale
|
||
- other
|
||
- unknown
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
UploadAvatarResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
avatarUrl:
|
||
type: string
|
||
contentType:
|
||
type: string
|
||
enum:
|
||
- image/jpeg
|
||
- image/png
|
||
- image/webp
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
required:
|
||
- avatarUrl
|
||
- contentType
|
||
- updatedAt
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
GetProfileResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/ProfileResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
ProfileResponse:
|
||
type: object
|
||
properties:
|
||
username:
|
||
type: string
|
||
displayName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
email:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
dateOfBirth:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
gender:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- MALE
|
||
- FEMALE
|
||
- type: "null"
|
||
heightCm:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
locale:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
timezone:
|
||
type: string
|
||
timeFormat:
|
||
type: string
|
||
enum:
|
||
- AUTO
|
||
- H12
|
||
- H24
|
||
description: Hour-cycle display preference. AUTO follows the locale convention, H12 forces AM/PM, H24 forces 24-hour.
|
||
moodReminderEnabled:
|
||
type: boolean
|
||
fullName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
insurerName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
insurerIkNumber:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: German insurer institution number (IKNR), 9 digits.
|
||
insuranceNumber:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: German KVNR, decrypted for the form prefill.
|
||
required:
|
||
- username
|
||
- email
|
||
- dateOfBirth
|
||
- gender
|
||
- heightCm
|
||
- locale
|
||
- timezone
|
||
- timeFormat
|
||
- moodReminderEnabled
|
||
- fullName
|
||
- insurerName
|
||
- insurerIkNumber
|
||
- insuranceNumber
|
||
additionalProperties: false
|
||
description: Flattened profile fields for the native client (GET). The KVNR is decrypted server-side; the IKNR (v1.8.6)
|
||
is plaintext at rest.
|
||
PatchProfileResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/ProfileUpdateResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
ProfileUpdateResponse:
|
||
type: object
|
||
properties:
|
||
username:
|
||
type: string
|
||
displayName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
email:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
dateOfBirth:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
gender:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- MALE
|
||
- FEMALE
|
||
- type: "null"
|
||
heightCm:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
locale:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
timezone:
|
||
type: string
|
||
timeFormat:
|
||
type: string
|
||
enum:
|
||
- AUTO
|
||
- H12
|
||
- H24
|
||
description: Hour-cycle display preference. AUTO follows the locale convention, H12 forces AM/PM, H24 forces 24-hour.
|
||
moodReminderEnabled:
|
||
type: boolean
|
||
fullName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
insurerName:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
insurerIkNumber:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: German insurer institution number (IKNR), 9 digits.
|
||
hasInsuranceNumber:
|
||
type: boolean
|
||
description: Whether a KVNR is on file (the value itself is not echoed).
|
||
required:
|
||
- username
|
||
- email
|
||
- dateOfBirth
|
||
- gender
|
||
- heightCm
|
||
- locale
|
||
- timezone
|
||
- timeFormat
|
||
- moodReminderEnabled
|
||
- fullName
|
||
- insurerName
|
||
- insurerIkNumber
|
||
- hasInsuranceNumber
|
||
additionalProperties: false
|
||
description: Profile echo returned by the PATCH/PUT update path. Reports KVNR presence as a boolean rather than
|
||
re-emitting the decrypted value.
|
||
InsightsLayout:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightsLayoutBodyOutput"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
InsightsLayoutSaved:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightsLayoutBodyOutput"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
InsightsLayoutReset:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightsLayoutBodyOutput"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CoachFactsList:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
facts:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
category:
|
||
type: string
|
||
enum:
|
||
- preference
|
||
- condition
|
||
- goal
|
||
- constraint
|
||
- context
|
||
description: "App-side closed category: preference | condition | goal | constraint | context."
|
||
text:
|
||
type: string
|
||
description: Decrypted fact text.
|
||
confidence:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: 0..100 server-assigned extraction confidence.
|
||
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
|
||
- category
|
||
- text
|
||
- confidence
|
||
- createdAt
|
||
additionalProperties: false
|
||
description: The caller's active facts, highest-confidence then newest first. Undecryptable rows are omitted.
|
||
required:
|
||
- facts
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CoachFactsCleared:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
cleared:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Number of active facts soft-deleted by the bulk clear.
|
||
required:
|
||
- cleared
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CoachFactDeleted:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
deleted:
|
||
type: boolean
|
||
description: True when a fact owned by the caller was soft-deleted; false for an unknown / cross-user / already-deleted
|
||
id (idempotent no-op).
|
||
required:
|
||
- deleted
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
GetCoachAboutMeResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
aboutMe:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
conditions:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
allergies:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
coachFocus:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
pendingQuestions:
|
||
type: array
|
||
items:
|
||
type: string
|
||
updatedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
maxChars:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
fieldMaxChars:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- aboutMe
|
||
- conditions
|
||
- allergies
|
||
- coachFocus
|
||
- pendingQuestions
|
||
- updatedAt
|
||
- maxChars
|
||
- fieldMaxChars
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
PutCoachAboutMeResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
aboutMe:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
conditions:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
allergies:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
coachFocus:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
pendingQuestions:
|
||
type: array
|
||
items:
|
||
type: string
|
||
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)))$
|
||
maxChars:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
fieldMaxChars:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- aboutMe
|
||
- conditions
|
||
- allergies
|
||
- coachFocus
|
||
- pendingQuestions
|
||
- updatedAt
|
||
- maxChars
|
||
- fieldMaxChars
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
GetCoachAboutMeQuestionsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
questions:
|
||
type: array
|
||
items:
|
||
type: string
|
||
required:
|
||
- questions
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
DeleteCoachAboutMeQuestionsResponse:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
questions:
|
||
type: array
|
||
items:
|
||
type: string
|
||
required:
|
||
- questions
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
AdminInviteList:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: array
|
||
items:
|
||
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)))$
|
||
expiresAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
usedAt:
|
||
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"
|
||
revokedAt:
|
||
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"
|
||
uses:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
maxUses:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
creator:
|
||
anyOf:
|
||
- type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
username:
|
||
type: string
|
||
required:
|
||
- id
|
||
- username
|
||
additionalProperties: false
|
||
- type: "null"
|
||
consumer:
|
||
anyOf:
|
||
- type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
username:
|
||
type: string
|
||
required:
|
||
- id
|
||
- username
|
||
additionalProperties: false
|
||
- type: "null"
|
||
redemptions:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
redeemedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
user:
|
||
anyOf:
|
||
- type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
username:
|
||
type: string
|
||
email:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
required:
|
||
- id
|
||
- username
|
||
- email
|
||
additionalProperties: false
|
||
- type: "null"
|
||
required:
|
||
- id
|
||
- redeemedAt
|
||
- user
|
||
additionalProperties: false
|
||
required:
|
||
- id
|
||
- createdAt
|
||
- expiresAt
|
||
- usedAt
|
||
- revokedAt
|
||
- uses
|
||
- maxUses
|
||
- creator
|
||
- consumer
|
||
- redemptions
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
AdminInviteCreated:
|
||
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)))$
|
||
expiresAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
uses:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
maxUses:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
token:
|
||
type: string
|
||
description: Raw invite token (`hlv_<64hex>`). Shown exactly once — never persisted in plaintext.
|
||
url:
|
||
type: string
|
||
description: Composed registration deep link (`/auth/register?invite=…`).
|
||
required:
|
||
- id
|
||
- createdAt
|
||
- expiresAt
|
||
- uses
|
||
- maxUses
|
||
- token
|
||
- url
|
||
additionalProperties: false
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
AdminInviteRevoked:
|
||
type: object
|
||
properties:
|
||
data:
|
||
type: object
|
||
properties:
|
||
revoked:
|
||
type: boolean
|
||
required:
|
||
- revoked
|
||
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.
|
||
DashboardSnapshotResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/DashboardSnapshotResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
DashboardSnapshotResponse:
|
||
type: object
|
||
properties:
|
||
user:
|
||
type: object
|
||
properties:
|
||
username:
|
||
type: string
|
||
timezone:
|
||
type: string
|
||
heightCm:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
dateOfBirth:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
gender:
|
||
anyOf:
|
||
- type: string
|
||
enum:
|
||
- MALE
|
||
- FEMALE
|
||
- type: "null"
|
||
glucoseUnit:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
onboardingTourCompleted:
|
||
type: boolean
|
||
greetingHour:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- username
|
||
- timezone
|
||
- heightCm
|
||
- dateOfBirth
|
||
- gender
|
||
- glucoseUnit
|
||
- onboardingTourCompleted
|
||
- greetingHour
|
||
additionalProperties: false
|
||
layout:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
layoutCatalogue:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
visible:
|
||
type: boolean
|
||
order:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- id
|
||
- visible
|
||
- order
|
||
additionalProperties: false
|
||
description: Full 27-id widget catalogue (server-known + iOS-only) with per-widget visibility + order. iOS-only ids are
|
||
appended default-invisible. The web dashboard reads `layout`; this block is the cold-launch seed for the
|
||
native client.
|
||
metricStates:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties:
|
||
type: object
|
||
properties:
|
||
value:
|
||
type: number
|
||
measuredAt:
|
||
type: string
|
||
unit:
|
||
type: string
|
||
required:
|
||
- value
|
||
- measuredAt
|
||
- unit
|
||
additionalProperties: false
|
||
description: "Latest reading per chartable metric, keyed by the iOS `MetricKind` raw value (the non-obvious raws:
|
||
`oxygenSaturation`, `totalBodyWater`, `heartRateVariability`, `bodyMassIndex`, `walkingAsymmetryPercentage`,
|
||
`walkingDoubleSupportPercentage`, `environmentalAudioExposure`, `headphoneAudioExposure`,
|
||
`activeEnergyBurned`). Each entry carries `value`, `measuredAt` (ISO8601), and the canonical `unit`. Types
|
||
the user has never logged are omitted."
|
||
tiles:
|
||
type: object
|
||
properties:
|
||
summaries:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
lastSeenByType:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
mood:
|
||
type: object
|
||
properties:
|
||
summary:
|
||
anyOf:
|
||
- type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
- type: "null"
|
||
entries:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
date:
|
||
type: string
|
||
score:
|
||
type: number
|
||
samples:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
required:
|
||
- date
|
||
- score
|
||
- samples
|
||
additionalProperties: false
|
||
required:
|
||
- summary
|
||
- entries
|
||
additionalProperties: false
|
||
required:
|
||
- summaries
|
||
- lastSeenByType
|
||
- mood
|
||
additionalProperties: false
|
||
extras:
|
||
anyOf:
|
||
- type: object
|
||
properties:
|
||
bpInTargetPct:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
bpInTargetPct7d:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
bpInTargetPct30d:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
bpInTargetPctAllTime:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
bpInTargetPctPriorMonth:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
bpInTargetPctPriorYear:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
glucoseByContext:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
required:
|
||
- bpInTargetPct
|
||
- bpInTargetPct7d
|
||
- bpInTargetPct30d
|
||
- bpInTargetPctAllTime
|
||
- bpInTargetPctPriorMonth
|
||
- bpInTargetPctPriorYear
|
||
- glucoseByContext
|
||
additionalProperties: false
|
||
- type: "null"
|
||
briefing:
|
||
anyOf:
|
||
- type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
- type: "null"
|
||
briefingState:
|
||
type: string
|
||
enum:
|
||
- ready
|
||
- preparing
|
||
- disabled
|
||
- no-provider
|
||
briefingUpdatedAt:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
briefingStale:
|
||
type: boolean
|
||
description: True when `briefing` carries the last good (expired-TTL) briefing while a refresh is pending (`preparing`)
|
||
or impossible (`no-provider`). Render the stale content with its `briefingUpdatedAt` timestamp instead of a
|
||
blank tile.
|
||
generatedAt:
|
||
type: string
|
||
required:
|
||
- user
|
||
- layout
|
||
- layoutCatalogue
|
||
- metricStates
|
||
- tiles
|
||
- extras
|
||
- briefing
|
||
- briefingState
|
||
- briefingUpdatedAt
|
||
- briefingStale
|
||
- generatedAt
|
||
additionalProperties: false
|
||
description: "Unified above-the-fold dashboard payload. `tiles` always arrives (slim summaries + mood + resolved widget
|
||
layout); `extras` (BD-in-target + per-context glucose) is null on a rollup-coverage miss so the strip never
|
||
waits on the slowest read. `briefing` is lifted read-only from the pre-generated insight cache — never generated
|
||
synchronously — and reports `ready` / `preparing` / `disabled` / `no-provider` via `briefingState`
|
||
(`no-provider` = stale-or-missing cache with no AI provider configured anywhere, so no warm pass will fill it;
|
||
stop polling and surface a connect-provider hint). A stale-but-parseable briefing is still delivered with
|
||
`briefingStale: true`. `layoutCatalogue` (full 27-id widget catalogue) and `metricStates` (latest reading per
|
||
metric, keyed by iOS `MetricKind` raw value) are additive cold-launch seeds for the native client; both derive
|
||
in-process from data already fetched, adding no DB round-trip."
|
||
InsightsComprehensiveResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightsComprehensiveResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
InsightsComprehensiveResponse:
|
||
type: object
|
||
properties:
|
||
summary:
|
||
type: string
|
||
recommendations:
|
||
type: array
|
||
items:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
citations:
|
||
type: array
|
||
items:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
warnings:
|
||
type: array
|
||
items:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
dailyBriefing:
|
||
anyOf:
|
||
- type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
- type: "null"
|
||
trendAnnotations:
|
||
anyOf:
|
||
- type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
- type: "null"
|
||
storyboardAnnotations:
|
||
type: array
|
||
items:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
metricSource:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
revalidating:
|
||
description: True when the body is served from last-good cache (stale-while-revalidate) while a fresh aggregation runs
|
||
in the background. The client keeps polling on `revalidating` (bounded) so the open page converges on the
|
||
fresh body.
|
||
type: boolean
|
||
required:
|
||
- summary
|
||
- recommendations
|
||
- citations
|
||
- warnings
|
||
additionalProperties: false
|
||
description: AI-generated insights bundle. Strict-schema validated server-side; Coach-routed when the insight surface
|
||
needs day-level grounding.
|
||
InsightsPregenerateResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightsPregenerateResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
InsightsPregenerateResponse:
|
||
type: object
|
||
properties:
|
||
queued:
|
||
type: boolean
|
||
description: True when the full warm was accepted and enqueued.
|
||
locale:
|
||
type: string
|
||
enum:
|
||
- de
|
||
- en
|
||
description: The locale the assessments are being warmed in.
|
||
required:
|
||
- queued
|
||
- locale
|
||
additionalProperties: false
|
||
description: Acknowledgement that a full assessment warm was enqueued for the calling user. The generation runs out of
|
||
band; the text lands in the read-only status routes.
|
||
AnalyticsRangeResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/AnalyticsRangeResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
AnalyticsRangeResponse:
|
||
type: object
|
||
properties:
|
||
range:
|
||
type: string
|
||
enum:
|
||
- 7d
|
||
- 30d
|
||
- 90d
|
||
- 1y
|
||
description: The range that was read (echoes the request).
|
||
windowDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Trailing-window length in days for the chosen range.
|
||
granularity:
|
||
type: string
|
||
description: Rollup granularity the read resolved against (`DAY` / `WEEK` / `MONTH` / `YEAR`, or `none` on a coverage
|
||
miss).
|
||
current:
|
||
description: Aggregate over the current window `[now-N, now)`.
|
||
$ref: "#/components/schemas/AnalyticsWindowAggregate"
|
||
previous:
|
||
description: Aggregate over the previous comparable window `[now-2N, now-N)`.
|
||
$ref: "#/components/schemas/AnalyticsWindowAggregate"
|
||
delta:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: "`current.mean - previous.mean`; null when either window has no data (never a misleading 0)."
|
||
deltaPct:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: "`delta / previous.mean` as a fraction (0.03 = +3 %); null when the prior window has no / zero mean (no
|
||
divide-by-zero). The client shows 'no prior-period data' in that case."
|
||
required:
|
||
- range
|
||
- windowDays
|
||
- granularity
|
||
- current
|
||
- previous
|
||
- delta
|
||
- deltaPct
|
||
additionalProperties: false
|
||
description: Single-metric period-over-period aggregate. Reads the current and previous comparable windows from the WMY
|
||
rollup tier and composes a count-weighted-mean delta. `count/min/max/mean/sum` are linearly composable across
|
||
buckets; SD/slope/r² are intentionally excluded (not composable).
|
||
AnalyticsWindowAggregate:
|
||
type: object
|
||
properties:
|
||
count:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Reading count composed across buckets.
|
||
min:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: Window minimum; null when empty.
|
||
max:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: Window maximum; null when empty.
|
||
mean:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: Count-weighted mean across buckets; null when empty.
|
||
sum:
|
||
anyOf:
|
||
- type: number
|
||
- type: "null"
|
||
description: Cumulative total for cumulative metrics (steps, energy, distance); null when no bucket carries a sum.
|
||
required:
|
||
- count
|
||
- min
|
||
- max
|
||
- mean
|
||
- sum
|
||
additionalProperties: false
|
||
BloodPressureStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
InsightStatusResponse:
|
||
type: object
|
||
properties:
|
||
hasProvider:
|
||
type: boolean
|
||
description: False when the user has no usable AI provider — `text` then carries the generic no-key guidance.
|
||
text:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: The assessment narrative (plain text, rendered as React text children). Null while a first generation is
|
||
preparing.
|
||
cached:
|
||
type: boolean
|
||
description: True when `text` is served from cache (incl. last-good).
|
||
updatedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: When the served assessment was generated; null when none.
|
||
preparing:
|
||
description: True when a first assessment is being generated out of band and no prior text exists yet — the client polls
|
||
until it lands.
|
||
type: boolean
|
||
revalidating:
|
||
description: True when `text` is served from last-good cache (stale-while-revalidate) while a fresh generation is in
|
||
flight. The client keeps polling on `preparing || revalidating` (bounded) so the open card upgrades to the
|
||
warmed assessment without a remount.
|
||
type: boolean
|
||
required:
|
||
- hasProvider
|
||
- text
|
||
- cached
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: "Specialised per-metric assessment envelope (blood-pressure, pulse, weight, bmi, mood). Identical shape to
|
||
the generic metric-status card so the `InsightStatusCard` consumes it unchanged. Read-only +
|
||
stale-while-revalidate: a cache miss warms a generation out of band and serves the last-good text meanwhile."
|
||
PulseStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
WeightStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
BmiStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MoodStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/InsightStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationComplianceStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MedicationComplianceStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MedicationComplianceStatusResponse:
|
||
type: object
|
||
properties:
|
||
hasProvider:
|
||
type: boolean
|
||
description: False when the user has no usable AI provider — `summary` then carries the generic no-key guidance.
|
||
summary:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: The overall compliance narrative (plain text). Null while a first generation is preparing.
|
||
medications:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationComplianceStatusItem"
|
||
description: Per-medication compliance notes. Empty while preparing or when no medication qualifies.
|
||
cached:
|
||
type: boolean
|
||
description: True when the envelope is served from cache (incl. last-good).
|
||
updatedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: When the served assessment was generated; null when none.
|
||
preparing:
|
||
description: True when a first assessment is being generated out of band and no prior summary exists yet — the client
|
||
polls until it lands.
|
||
type: boolean
|
||
revalidating:
|
||
description: True when the envelope is served from last-good cache (stale-while-revalidate) while a fresh generation is
|
||
in flight. The client keeps polling on `preparing || revalidating` (bounded).
|
||
type: boolean
|
||
required:
|
||
- hasProvider
|
||
- summary
|
||
- medications
|
||
- cached
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: Medication-compliance assessment envelope. Unlike the other six specialised cards it carries a `summary`
|
||
plus a per-medication `text` array rather than a single `text` field. Read-only + stale-while-revalidate.
|
||
MedicationComplianceStatusItem:
|
||
type: object
|
||
properties:
|
||
medicationId:
|
||
type: string
|
||
description: The medication this note belongs to.
|
||
text:
|
||
type: string
|
||
description: Per-medication compliance note (plain text).
|
||
required:
|
||
- medicationId
|
||
- text
|
||
additionalProperties: false
|
||
MetricStatusResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/MetricStatusResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
MetricStatusResponse:
|
||
type: object
|
||
properties:
|
||
hasProvider:
|
||
type: boolean
|
||
description: False when the user has no usable AI provider — `text` then carries the generic no-key guidance.
|
||
text:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: The assessment narrative (plain text, rendered as React text children). Null while a first generation is
|
||
preparing, or when the metric has insufficient data.
|
||
cached:
|
||
type: boolean
|
||
description: True when `text` is served from cache (incl. last-good).
|
||
updatedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: When the served assessment was generated; null when none.
|
||
preparing:
|
||
description: True when a first assessment is being generated out of band and no prior text exists yet — the client polls
|
||
until it lands.
|
||
type: boolean
|
||
revalidating:
|
||
description: True when `text` is served from last-good cache (stale-while-revalidate) while a fresh generation is in
|
||
flight. The payload is otherwise terminal; the client keeps polling on `preparing || revalidating` (bounded)
|
||
so the open card upgrades to the warmed assessment without a remount.
|
||
type: boolean
|
||
insufficient:
|
||
description: True when the metric has no readings; no assessment is generated (no LLM call). The card shows its
|
||
insufficient-data state.
|
||
type: boolean
|
||
required:
|
||
- hasProvider
|
||
- text
|
||
- cached
|
||
- updatedAt
|
||
additionalProperties: false
|
||
description: "Generic per-metric assessment envelope. Identical shape to the seven specialised `*-status` cards so the
|
||
`InsightStatusCard` consumes it unchanged. Read-only + stale-while-revalidate: a cache miss warms a generation
|
||
out of band and serves the last-good text meanwhile."
|
||
DerivedMetricResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/DerivedMetricResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
DerivedMetricResponse:
|
||
type: object
|
||
properties:
|
||
metric:
|
||
type: string
|
||
enum:
|
||
- VITALS_BASELINE
|
||
- FITNESS_AGE
|
||
- VASCULAR_AGE_DELTA
|
||
- SLEEP_SCORE
|
||
- READINESS
|
||
- COINCIDENT_DEVIATION
|
||
- HRV_BALANCE
|
||
- BMI
|
||
- RECOVERY_SCORE
|
||
- STRESS_SCORE
|
||
- STRAIN_SCORE
|
||
- WRIST_TEMPERATURE_BASELINE
|
||
- STAIR_ASCENT_SPEED_BASELINE
|
||
- STAIR_DESCENT_SPEED_BASELINE
|
||
- SIX_MINUTE_WALK_BAND
|
||
- TRAJECTORY
|
||
description: Echoes the requested derived-metric id (tags the union).
|
||
status:
|
||
type: string
|
||
enum:
|
||
- ok
|
||
- insufficient
|
||
description: "'ok' carries `value` + `confidence`; 'insufficient' carries `reason` and no value, but still carries
|
||
`coverage` + `provenance` so the surface renders the same gating UI."
|
||
value:
|
||
anyOf:
|
||
- type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties: {}
|
||
- type: "null"
|
||
description: Metric-specific value object when status is 'ok' (e.g. { type, center, low, high, spread, sampleDays, k,
|
||
series } for VITALS_BASELINE, where `series` is the trailing per-day mean values for the inline sparkline);
|
||
null when 'insufficient'.
|
||
coverage:
|
||
$ref: "#/components/schemas/DerivedCoverage"
|
||
confidence:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/DerivedConfidence"
|
||
- type: "null"
|
||
description: Present when status is 'ok'; null when 'insufficient'.
|
||
provenance:
|
||
$ref: "#/components/schemas/DerivedProvenance"
|
||
reason:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Why the value could not be produced; null when status is 'ok'.
|
||
assessment:
|
||
anyOf:
|
||
- $ref: "#/components/schemas/DerivedAssessment"
|
||
- type: "null"
|
||
description: "v1.13.2 — short 'why is this score what it is' explanation, keyed to the SAME requested id (only for the
|
||
per-score ids READINESS, SLEEP_SCORE, RECOVERY_SCORE, STRAIN_SCORE, STRESS_SCORE). Null for any other metric
|
||
and whenever status !== 'ok'. Always non-empty when present: a deterministic text fills it (so provider-less
|
||
accounts + the demo always get one) and warmer AI prose overrides it once cached."
|
||
required:
|
||
- metric
|
||
- status
|
||
- value
|
||
- coverage
|
||
- confidence
|
||
- provenance
|
||
- reason
|
||
- assessment
|
||
additionalProperties: false
|
||
description: Flat `Derived<T>` envelope for one derived wellness metric. Pure compute over the rollup tier (no LLM, no
|
||
narrative). iOS decodes one stable shape and combines values across metrics; coverage/confidence/provenance let
|
||
it render the same honesty chips.
|
||
DerivedCoverage:
|
||
type: object
|
||
properties:
|
||
requiredInputs:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Inputs the metric wants (its full input set).
|
||
presentInputs:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Inputs actually present in the user's data.
|
||
historyDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Distinct days of history backing the value (the gating floor).
|
||
missing:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Named inputs still missing — drives the 'track N more' nudge.
|
||
required:
|
||
- requiredInputs
|
||
- presentInputs
|
||
- historyDays
|
||
- missing
|
||
additionalProperties: false
|
||
DerivedConfidence:
|
||
type: object
|
||
properties:
|
||
score:
|
||
type: number
|
||
description: 0..100 confidence; feeds the shared coverage meter unchanged.
|
||
band:
|
||
type: string
|
||
enum:
|
||
- high
|
||
- medium
|
||
- low
|
||
- draft
|
||
description: Confidence band the meter renders.
|
||
required:
|
||
- score
|
||
- band
|
||
additionalProperties: false
|
||
DerivedProvenance:
|
||
type: object
|
||
properties:
|
||
inputs:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: Named inputs that actually backed the value.
|
||
source:
|
||
type: string
|
||
enum:
|
||
- DAY
|
||
- WEEK
|
||
- MONTH
|
||
- YEAR
|
||
- live
|
||
- none
|
||
description: Granularity the dominant read resolved against. 'live' = a coverage-miss live-SQL fallback; 'none' = no
|
||
data backed the value.
|
||
windowDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Trailing window the value summarises, in days.
|
||
computedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
description: Compute time (for cache-staleness + the 'as of' chip).
|
||
required:
|
||
- inputs
|
||
- source
|
||
- windowDays
|
||
- computedAt
|
||
additionalProperties: false
|
||
DerivedAssessment:
|
||
type: object
|
||
properties:
|
||
text:
|
||
type: string
|
||
description: Short, non-empty explanation of why the score sits where it does, referencing the score's contributors.
|
||
source:
|
||
type: string
|
||
description: "'deterministic' for the always-on template text, or 'ai' when warmer provider prose has been cached."
|
||
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)))$
|
||
description: When the text was produced / last warmed.
|
||
required:
|
||
- text
|
||
- source
|
||
- updatedAt
|
||
additionalProperties: false
|
||
DerivedBatchResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/DerivedBatchResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
DerivedBatchResponse:
|
||
type: object
|
||
properties:
|
||
metrics:
|
||
type: object
|
||
propertyNames:
|
||
type: string
|
||
additionalProperties:
|
||
$ref: "#/components/schemas/DerivedMetricResponse"
|
||
description: Map keyed by the per-request token (`<metric>` or `<metric>:<type>`). Each value is the same flat
|
||
`Derived<T>` envelope the single-metric route returns, so a client decodes one shape and reads back exactly
|
||
the tokens it asked for.
|
||
required:
|
||
- metrics
|
||
additionalProperties: false
|
||
description: Batched derived-metric values. One request resolves the whole dashboard grid (the wellness scores + the
|
||
derived re-frames + one baseline per vital) instead of N concurrent single-metric requests sharing the Prisma
|
||
pool. Pure compute over the rollup tier — no LLM, no narrative, no cache table.
|
||
CorrelationDiscoveryResponseEnvelope:
|
||
type: object
|
||
properties:
|
||
data:
|
||
$ref: "#/components/schemas/CorrelationDiscoveryResponse"
|
||
error:
|
||
type: "null"
|
||
meta:
|
||
type: object
|
||
properties:
|
||
requestId:
|
||
type: string
|
||
additionalProperties: false
|
||
required:
|
||
- data
|
||
- error
|
||
additionalProperties: false
|
||
CorrelationDiscoveryResponse:
|
||
type: object
|
||
properties:
|
||
discovered:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/DiscoveredCorrelation"
|
||
description: Pairs surviving n ≥ 20, p < 0.05, AND the BH-FDR control.
|
||
pairsTested:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Behaviour × outcome pairs assessed (for the honest footer).
|
||
fdrQ:
|
||
type: number
|
||
description: The FDR target the surface used.
|
||
minPairs:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Minimum paired-day count enforced per pair.
|
||
required:
|
||
- discovered
|
||
- pairsTested
|
||
- fdrQ
|
||
- minPairs
|
||
additionalProperties: false
|
||
description: v1.10.0 — FDR-controlled correlation discovery over a curated behaviour × outcome matrix, lagged behaviour
|
||
→ next-day outcome. Only statistically-defensible pairs surface; descriptive, never causal.
|
||
DiscoveredCorrelation:
|
||
type: object
|
||
properties:
|
||
behaviour:
|
||
type: string
|
||
description: Behaviour channel (lag source), e.g. TIME_IN_DAYLIGHT, MOOD.
|
||
outcome:
|
||
type: string
|
||
description: Outcome channel (lag target), e.g. SLEEP_DURATION, HEART_RATE_VARIABILITY.
|
||
n:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Paired-day count after the day+1 lag join (≥ 20).
|
||
r:
|
||
type: number
|
||
description: Pearson r over the lag-joined daily series.
|
||
pValue:
|
||
type: number
|
||
description: Two-sided exact Student-t p-value (< 0.05).
|
||
qValue:
|
||
type: number
|
||
description: Benjamini-Hochberg FDR-adjusted q-value (≤ the surface threshold).
|
||
interpretation:
|
||
type: string
|
||
description: Conservative, descriptive interpretation — never causal.
|
||
lagDays:
|
||
type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
description: Lag in days applied (1).
|
||
required:
|
||
- behaviour
|
||
- outcome
|
||
- n
|
||
- r
|
||
- pValue
|
||
- qValue
|
||
- interpretation
|
||
- lagDays
|
||
additionalProperties: false
|
||
MedicationScheduleOutput:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
medicationId:
|
||
type: string
|
||
windowStart:
|
||
type: string
|
||
description: Legacy single-time-of-intake (HH:mm, user local). Preserved for backwards compatibility; the new
|
||
`timesOfDay` array supersedes it.
|
||
windowEnd:
|
||
type: string
|
||
description: Legacy reminder-window upper bound (HH:mm). Used to derive the late-classification grace span when
|
||
`reminderGraceMinutes` is null.
|
||
label:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
dose:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Per-schedule dose override. NULL means the schedule inherits `Medication.dose`.
|
||
daysOfWeek:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: Legacy persisted recurrence encoding (`null` | `1,3,5` | `i2;1,3,5`). v1.5 readers consult `rrule` first;
|
||
the field is kept for pre-v1.5 rows. v1.6.0 drops the column.
|
||
timesOfDay:
|
||
type: array
|
||
items:
|
||
type: string
|
||
description: v1.5 first-class points-in-time the dose is taken (HH:mm, user local). Backfilled to `[windowStart]` for
|
||
every pre-v1.5 row.
|
||
reminderGraceMinutes:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Reminder grace window in minutes. NULL falls back to the legacy `windowEnd - windowStart` span.
|
||
rrule:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: RFC 5545 RRULE string (subset). Used for calendar-anchored cadences. **Mutually exclusive with
|
||
`rollingIntervalDays`** — exactly one of the two is non-null on any v1.5+ schedule (or both are null on
|
||
legacy rows that haven't been touched since the migration).
|
||
rollingIntervalDays:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Flexible-rolling interval in days, counted forward from the latest `MedicationIntakeEvent.takenAt`.
|
||
**Mutually exclusive with `rrule`.**
|
||
scheduleType:
|
||
type: string
|
||
enum:
|
||
- SCHEDULED
|
||
- PRN
|
||
- CYCLIC
|
||
description: v1.7.0 schedule-type discriminator. SCHEDULED = rrule / rolling / legacy cadence. PRN = as-needed (never
|
||
projected, reminded, or counted in compliance expected; still loggable via the intake route). CYCLIC = N
|
||
weeks on / M weeks off, gating whichever inner cadence the rrule / legacy fields describe.
|
||
cyclicOnWeeks:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: v1.7.0 cyclic "on" weeks. Only meaningful when `scheduleType` is CYCLIC; null otherwise.
|
||
cyclicOffWeeks:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: v1.7.0 cyclic "off" weeks. Only meaningful when `scheduleType` is CYCLIC; null otherwise.
|
||
required:
|
||
- id
|
||
- medicationId
|
||
- windowStart
|
||
- windowEnd
|
||
- label
|
||
- dose
|
||
- daysOfWeek
|
||
- timesOfDay
|
||
- reminderGraceMinutes
|
||
- rrule
|
||
- rollingIntervalDays
|
||
- scheduleType
|
||
- cyclicOnWeeks
|
||
- cyclicOffWeeks
|
||
additionalProperties: false
|
||
description: Schedule entry attached to a medication. v1.5 promotes `timesOfDay` to first-class and introduces `rrule`
|
||
(calendar-anchored cadences) and `rollingIntervalDays` (flexible-rolling cadences). The two recurrence
|
||
primitives are mutually exclusive — enforced by the Zod refine on writes, the route layer, and a DB CHECK
|
||
constraint (`medication_schedules_rrule_xor_rolling`). v1.7.0 adds `scheduleType` (SCHEDULED / PRN / CYCLIC) and
|
||
the cyclic on/off-week fields.
|
||
DisableCoachFlagOutput:
|
||
type: object
|
||
properties:
|
||
disableCoach:
|
||
type: boolean
|
||
required:
|
||
- disableCoach
|
||
additionalProperties: false
|
||
description: Per-account Coach opt-out toggle (v1.4.47 W3). `true` hides the Coach FAB and short-circuits its API gates.
|
||
InsightsLayoutBodyOutput:
|
||
type: object
|
||
properties:
|
||
version:
|
||
anyOf:
|
||
- type: number
|
||
const: 1
|
||
- type: number
|
||
const: 2
|
||
sections:
|
||
maxItems: 50
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
enum:
|
||
- wellness-scores
|
||
- daily-briefing
|
||
- vitals
|
||
- trends
|
||
- period-review
|
||
- cycle-summary
|
||
- signals
|
||
- rhythm-events
|
||
visible:
|
||
type: boolean
|
||
order:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 99
|
||
required:
|
||
- id
|
||
- visible
|
||
- order
|
||
additionalProperties: false
|
||
tiles:
|
||
minItems: 1
|
||
maxItems: 50
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
enum:
|
||
- overview
|
||
- blood-pressure
|
||
- pulse
|
||
- oxygen
|
||
- body-temperature
|
||
- respiratory-rate
|
||
- weight
|
||
- bmi
|
||
- body-water
|
||
- bone-mass
|
||
- fat-free-mass
|
||
- fat-mass
|
||
- muscle-mass
|
||
- visceral-fat
|
||
- lean-body-mass
|
||
- steps
|
||
- active-energy
|
||
- workouts
|
||
- flights-climbed
|
||
- walking-distance
|
||
- walking-steadiness
|
||
- walking-heart-rate
|
||
- walking-asymmetry
|
||
- double-support-time
|
||
- step-length
|
||
- walking-speed
|
||
- cardio-fitness
|
||
- falls
|
||
- six-minute-walk
|
||
- stair-ascent-speed
|
||
- stair-descent-speed
|
||
- sleep
|
||
- breathing-disturbances
|
||
- resting-pulse
|
||
- hrv
|
||
- pulse-wave-velocity
|
||
- vascular-age
|
||
- cardio-recovery
|
||
- environmental-audio
|
||
- headphone-audio
|
||
- audio-events
|
||
- daylight
|
||
- blood-glucose
|
||
- skin-temperature
|
||
- wrist-temperature
|
||
- mood
|
||
- medications
|
||
- blutdruck
|
||
- puls
|
||
- sauerstoff
|
||
- koerpertemperatur
|
||
- gewicht
|
||
- aktive-energie
|
||
- schlaf
|
||
- ruhepuls
|
||
- stimmung
|
||
- medikamente
|
||
visible:
|
||
type: boolean
|
||
order:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 99
|
||
required:
|
||
- id
|
||
- visible
|
||
- order
|
||
additionalProperties: false
|
||
required:
|
||
- version
|
||
additionalProperties: false
|
||
description: "Per-user Insights layout (v2). `tiles` is the per-metric pill list (ordered, with a visibility flag);
|
||
`sections` is the additive v2 list of the overview's big semantic blocks (wellness-scores, daily-briefing,
|
||
vitals, trends, period-review, cycle-summary, signals, rhythm-events), each with order + visibility. `version`
|
||
is the layout schema version: BOTH 1 and 2 are accepted on input (a pre-v2 iOS client still PUTs `version: 1`);
|
||
the server always normalises to the canonical v2 blob before persisting, and GET responses always carry
|
||
`version: 2`. Both arrays are optional on input — a client sending only `tiles` (the pre-v2 contract) still
|
||
validates, and the server fills missing defaults; a v1 blob with no `sections` resolves forward with all
|
||
sections default-visible. Tile ids are a closed enum: the canonical ids are English (matching the routed
|
||
`/insights/<slug>` sub-pages). The legacy German tile ids (blutdruck, puls, sauerstoff, koerpertemperatur,
|
||
gewicht, aktive-energie, schlaf, ruhepuls, stimmung, medikamente) remain accepted on input for backward
|
||
compatibility and are normalised to their English equivalents before persisting; GET responses always carry the
|
||
canonical English ids. Section ids are English-only. The legacy tile ids are deprecated and will be removed in a
|
||
future major version."
|
||
Medication:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
name:
|
||
type: string
|
||
dose:
|
||
type: string
|
||
treatmentClass:
|
||
$ref: "#/components/schemas/MedicationTreatmentClass"
|
||
dosesPerUnit:
|
||
anyOf:
|
||
- type: integer
|
||
minimum: -9007199254740991
|
||
maximum: 9007199254740991
|
||
- type: "null"
|
||
description: Doses per pen / vial for inventory tracking. NULL = inventory tracking off.
|
||
active:
|
||
type: boolean
|
||
notificationsEnabled:
|
||
type: boolean
|
||
liveActivityEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS Live Activity opt-in for this medication's reminders. Default false. The iOS client owns the
|
||
ActivityKit lifecycle; the server only stores + echoes the flag.
|
||
criticalAlarmEnabled:
|
||
type: boolean
|
||
description: v1.7.0 iOS 26 AlarmKit critical-reminder opt-in. Default false. Critical alarms bypass the device mute
|
||
switch / Focus; the server stores the preference only.
|
||
atcCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional WHO ATC classification code (active-substance class, e.g. `A10BX10`).
|
||
User/clinician-asserted; never machine-guessed. Emitted on the FHIR `medicationCodeableConcept` under
|
||
`http://www.whocc.no/atc`. NULL = no code captured.
|
||
rxNormCode:
|
||
anyOf:
|
||
- type: string
|
||
- type: "null"
|
||
description: v1.9.0 optional RxNorm RxCUI (numeric, US identifier, e.g. `2601723`). Secondary FHIR coding under
|
||
`http://www.nlm.nih.gov/research/umls/rxnorm`, alongside any ATC code. NULL = no code captured.
|
||
pausedAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
snoozedUntil:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
nextDueAt:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: "v1.7.0 server-computed next due instant across all the medication's schedules (earliest
|
||
`nextOccurrenceAfter`). Read-only — computed, not stored. NULL when no schedule has an upcoming slot
|
||
(paused, one-shot in the past, `endsOn` crossed, every schedule PRN). v1.16.4 — when an unresolved slot's
|
||
anchor has passed but the catch-up band is still open (`anchor < now <= overdueEnd`, current schedule era),
|
||
this carries THAT slot (a past instant) with `nextDueOverdue: true` instead of jumping to the next future
|
||
slot. The list GET is cached 60 s, so a 60 s staleness is accepted."
|
||
nextDueOverdue:
|
||
type: boolean
|
||
description: "v1.16.4 — true when `nextDueAt` is an OPEN overdue slot: its anchor has passed, `now` is still inside the
|
||
slot's catch-up band, and no taken / skipped / auto-missed row resolves it. False for a regular future
|
||
next-due (and when `nextDueAt` is null). Read-only — computed, not stored."
|
||
startsOn:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: v1.5 course start (ISO date). Anchors RRULE BYDAY / BYMONTHDAY patterns and the rolling-interval
|
||
countdown's first window. NULL means active from creation.
|
||
endsOn:
|
||
anyOf:
|
||
- type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
- type: "null"
|
||
description: v1.5 course end (ISO date). NULL means chronic. Equals `startsOn` when `oneShot` is true.
|
||
oneShot:
|
||
type: boolean
|
||
description: v1.5 single-administration flag. When true the medication has at most one schedule (no `rrule` /
|
||
`rollingIntervalDays`), and `active` auto-flips to false once the dose is logged.
|
||
createdAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
|
||
schedules:
|
||
type: array
|
||
items:
|
||
$ref: "#/components/schemas/MedicationScheduleOutput"
|
||
required:
|
||
- id
|
||
- name
|
||
- dose
|
||
- treatmentClass
|
||
- dosesPerUnit
|
||
- active
|
||
- notificationsEnabled
|
||
- liveActivityEnabled
|
||
- criticalAlarmEnabled
|
||
- atcCode
|
||
- rxNormCode
|
||
- pausedAt
|
||
- snoozedUntil
|
||
- nextDueAt
|
||
- nextDueOverdue
|
||
- 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.
|