mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
Mostly table column normalisation under docs/audit/ + planning, object- literal expansion in src/lib/ai/coach/snapshot.ts after the W4 F6 Apple Health metric block landed, and small wrap/unwrap line adjustments in the device + APNs routes / tests touched across W3/W4/W5 sessions. No behavioural change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1158 lines
34 KiB
YAML
1158 lines
34 KiB
YAML
components:
|
|
schemas:
|
|
AccessRefreshBundle:
|
|
additionalProperties: false
|
|
description: Native-client token bundle returned by login + refresh. Web cookie-only callers see only `user`.
|
|
properties:
|
|
refreshToken:
|
|
description: Refresh token (`hlr_<64hex>`); only present for native-policy callers.
|
|
type: string
|
|
refreshTokenExpiresAt:
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: string
|
|
token:
|
|
description: Access token (`hlk_<64hex>`).
|
|
type: string
|
|
tokenExpiresAt:
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: string
|
|
user:
|
|
additionalProperties: false
|
|
properties:
|
|
id:
|
|
type: string
|
|
username:
|
|
type: string
|
|
required:
|
|
- id
|
|
- username
|
|
type: object
|
|
required:
|
|
- token
|
|
- tokenExpiresAt
|
|
type: object
|
|
AppleHealthBatchEntry:
|
|
properties:
|
|
endDate:
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: string
|
|
externalId:
|
|
description: HKSample.uuid string — the dedup key.
|
|
maxLength: 120
|
|
minLength: 1
|
|
type: string
|
|
externalSourceVersion:
|
|
maxLength: 120
|
|
minLength: 1
|
|
type: string
|
|
hkIdentifier:
|
|
description: HealthKit identifier (e.g. `HKQuantityTypeIdentifierBodyMass`).
|
|
maxLength: 120
|
|
minLength: 1
|
|
type: string
|
|
sleepStage:
|
|
description: HKCategoryValueSleepAnalysis codepoint; only for sleep samples.
|
|
maximum: 20
|
|
minimum: 0
|
|
type: integer
|
|
startDate:
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: string
|
|
unit:
|
|
maxLength: 60
|
|
minLength: 1
|
|
type: string
|
|
value:
|
|
type: number
|
|
required:
|
|
- hkIdentifier
|
|
- value
|
|
- unit
|
|
- startDate
|
|
- endDate
|
|
- externalId
|
|
type: object
|
|
AppleHealthBatchEntryResult:
|
|
additionalProperties: false
|
|
properties:
|
|
index:
|
|
maximum: 9007199254740991
|
|
minimum: 0
|
|
type: integer
|
|
reason:
|
|
type: string
|
|
status:
|
|
enum:
|
|
- inserted
|
|
- duplicate
|
|
- skipped
|
|
type: string
|
|
required:
|
|
- index
|
|
- status
|
|
type: object
|
|
AppleHealthBatchRequest:
|
|
description: Apple Health batch ingest. ≤500 entries per call; idempotent via `Idempotency-Key`.
|
|
properties:
|
|
entries:
|
|
items:
|
|
$ref: "#/components/schemas/AppleHealthBatchEntry"
|
|
maxItems: 500
|
|
minItems: 1
|
|
type: array
|
|
required:
|
|
- entries
|
|
type: object
|
|
AppleHealthBatchResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
duplicate:
|
|
maximum: 9007199254740991
|
|
minimum: 0
|
|
type: integer
|
|
entries:
|
|
items:
|
|
$ref: "#/components/schemas/AppleHealthBatchEntryResult"
|
|
type: array
|
|
inserted:
|
|
maximum: 9007199254740991
|
|
minimum: 0
|
|
type: integer
|
|
skipped:
|
|
maximum: 9007199254740991
|
|
minimum: 0
|
|
type: integer
|
|
required:
|
|
- inserted
|
|
- duplicate
|
|
- skipped
|
|
- entries
|
|
type: object
|
|
BatchMeasurementsResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/AppleHealthBatchResponse"
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
CoachMessageFeedbackRequest:
|
|
description: Per-message helpful/unhelpful feedback (v1.4.23 H7). Optional `reason` is free-form prose, capped at 200 chars.
|
|
properties:
|
|
rating:
|
|
enum:
|
|
- helpful
|
|
- unhelpful
|
|
type: string
|
|
reason:
|
|
maxLength: 200
|
|
minLength: 1
|
|
type: string
|
|
required:
|
|
- rating
|
|
type: object
|
|
CoachMessageFeedbackResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
additionalProperties: false
|
|
properties:
|
|
createdAt:
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: string
|
|
id:
|
|
type: string
|
|
required:
|
|
- id
|
|
- createdAt
|
|
type: object
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
CreateMeasurementResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/MeasurementResource"
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
DeviceRegisterRequest:
|
|
description:
|
|
Native device registration. Re-registering an APNs token belonging to another user returns 409
|
|
(cross-user-hijack guard).
|
|
properties:
|
|
apnsEnvironment:
|
|
description: Gateway the iOS client received `apnsToken` from. Server never auto-detects.
|
|
enum:
|
|
- sandbox
|
|
- production
|
|
type: string
|
|
apnsToken:
|
|
description: Hex-encoded APNs device token. Must be paired with `apnsEnvironment`.
|
|
maxLength: 256
|
|
minLength: 8
|
|
pattern: ^[A-Fa-f0-9]+$
|
|
type: string
|
|
appVersion:
|
|
maxLength: 32
|
|
minLength: 1
|
|
type: string
|
|
bundleId:
|
|
maxLength: 128
|
|
minLength: 1
|
|
type: string
|
|
locale:
|
|
maxLength: 16
|
|
minLength: 2
|
|
type: string
|
|
model:
|
|
maxLength: 64
|
|
minLength: 1
|
|
type: string
|
|
token:
|
|
description: Generic device identifier (legacy; APNs token below).
|
|
maxLength: 512
|
|
minLength: 8
|
|
pattern: ^[A-Za-z0-9+/=._:-]+$
|
|
type: string
|
|
required:
|
|
- token
|
|
- bundleId
|
|
type: object
|
|
DeviceRegisterResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
additionalProperties: false
|
|
properties:
|
|
id:
|
|
type: string
|
|
required:
|
|
- id
|
|
type: object
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
ErrorEnvelope:
|
|
additionalProperties: false
|
|
description: "Standard error response: data is null, error is human prose."
|
|
properties:
|
|
data:
|
|
type: "null"
|
|
error:
|
|
type: string
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
errorCode:
|
|
type: string
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
GetCoachPrefsResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
additionalProperties: false
|
|
properties:
|
|
excludeMetrics:
|
|
default: []
|
|
items:
|
|
enum:
|
|
- bp
|
|
- weight
|
|
- pulse
|
|
- mood
|
|
- compliance
|
|
- hrv
|
|
- sleep
|
|
- resting_hr
|
|
- steps
|
|
type: string
|
|
maxItems: 9
|
|
type: array
|
|
showEvidenceByDefault:
|
|
default: false
|
|
type: boolean
|
|
tone:
|
|
default: warm
|
|
enum:
|
|
- warm
|
|
- neutral
|
|
- concise
|
|
type: string
|
|
verbosity:
|
|
default: default
|
|
enum:
|
|
- brief
|
|
- default
|
|
- detailed
|
|
type: string
|
|
required:
|
|
- tone
|
|
- verbosity
|
|
- excludeMetrics
|
|
- showEvidenceByDefault
|
|
type: object
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
InsightsComprehensiveResponse:
|
|
additionalProperties: false
|
|
description:
|
|
AI-generated insights bundle. Strict-schema validated server-side; Coach-routed when the insight surface
|
|
needs day-level grounding.
|
|
properties:
|
|
citations:
|
|
items:
|
|
additionalProperties: {}
|
|
propertyNames:
|
|
type: string
|
|
type: object
|
|
type: array
|
|
dailyBriefing:
|
|
anyOf:
|
|
- additionalProperties: {}
|
|
propertyNames:
|
|
type: string
|
|
type: object
|
|
- type: "null"
|
|
metricSource:
|
|
additionalProperties: {}
|
|
propertyNames:
|
|
type: string
|
|
type: object
|
|
recommendations:
|
|
items:
|
|
additionalProperties: {}
|
|
propertyNames:
|
|
type: string
|
|
type: object
|
|
type: array
|
|
storyboardAnnotations:
|
|
items:
|
|
additionalProperties: {}
|
|
propertyNames:
|
|
type: string
|
|
type: object
|
|
type: array
|
|
summary:
|
|
type: string
|
|
trendAnnotations:
|
|
anyOf:
|
|
- additionalProperties: {}
|
|
propertyNames:
|
|
type: string
|
|
type: object
|
|
- type: "null"
|
|
warnings:
|
|
items:
|
|
additionalProperties: {}
|
|
propertyNames:
|
|
type: string
|
|
type: object
|
|
type: array
|
|
weeklyReport:
|
|
anyOf:
|
|
- additionalProperties: {}
|
|
propertyNames:
|
|
type: string
|
|
type: object
|
|
- type: "null"
|
|
required:
|
|
- summary
|
|
- recommendations
|
|
- citations
|
|
- warnings
|
|
type: object
|
|
InsightsComprehensiveResponseEnvelope:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/InsightsComprehensiveResponse"
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
ListMeasurementsResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
additionalProperties: false
|
|
properties:
|
|
measurements:
|
|
items:
|
|
$ref: "#/components/schemas/MeasurementResource"
|
|
type: array
|
|
total:
|
|
maximum: 9007199254740991
|
|
minimum: 0
|
|
type: integer
|
|
required:
|
|
- measurements
|
|
- total
|
|
type: object
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
LoginResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/AccessRefreshBundle"
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
MeasurementResource:
|
|
additionalProperties: false
|
|
description: Server-shaped measurement row returned by GET endpoints.
|
|
properties:
|
|
id:
|
|
type: string
|
|
measuredAt:
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: string
|
|
notes:
|
|
anyOf:
|
|
- type: string
|
|
- type: "null"
|
|
source:
|
|
enum:
|
|
- MANUAL
|
|
- WITHINGS
|
|
- IMPORT
|
|
- APPLE_HEALTH
|
|
type: string
|
|
type:
|
|
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
|
|
type: string
|
|
unit:
|
|
type: string
|
|
value:
|
|
type: number
|
|
required:
|
|
- id
|
|
- type
|
|
- value
|
|
- unit
|
|
- measuredAt
|
|
- source
|
|
type: object
|
|
PasskeyLoginVerifyRequest:
|
|
description: Passkey assertion verification. Same native-client token issuance as the password path.
|
|
properties:
|
|
challengeId:
|
|
description: Server-issued WebAuthn challenge id.
|
|
minLength: 1
|
|
type: string
|
|
credential:
|
|
description: SimpleWebAuthn-style assertion response payload.
|
|
properties:
|
|
authenticatorAttachment:
|
|
enum:
|
|
- platform
|
|
- cross-platform
|
|
type: string
|
|
clientExtensionResults:
|
|
additionalProperties: {}
|
|
propertyNames:
|
|
type: string
|
|
type: object
|
|
id:
|
|
type: string
|
|
rawId:
|
|
type: string
|
|
response:
|
|
additionalProperties: {}
|
|
propertyNames:
|
|
type: string
|
|
type: object
|
|
type:
|
|
const: public-key
|
|
type: string
|
|
required:
|
|
- id
|
|
- rawId
|
|
- type
|
|
- response
|
|
type: object
|
|
required:
|
|
- challengeId
|
|
- credential
|
|
type: object
|
|
PasskeyLoginVerifyResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/AccessRefreshBundle"
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
PutCoachPrefsResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
additionalProperties: false
|
|
properties:
|
|
excludeMetrics:
|
|
default: []
|
|
items:
|
|
enum:
|
|
- bp
|
|
- weight
|
|
- pulse
|
|
- mood
|
|
- compliance
|
|
- hrv
|
|
- sleep
|
|
- resting_hr
|
|
- steps
|
|
type: string
|
|
maxItems: 9
|
|
type: array
|
|
showEvidenceByDefault:
|
|
default: false
|
|
type: boolean
|
|
tone:
|
|
default: warm
|
|
enum:
|
|
- warm
|
|
- neutral
|
|
- concise
|
|
type: string
|
|
verbosity:
|
|
default: default
|
|
enum:
|
|
- brief
|
|
- default
|
|
- detailed
|
|
type: string
|
|
required:
|
|
- tone
|
|
- verbosity
|
|
- excludeMetrics
|
|
- showEvidenceByDefault
|
|
type: object
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
RefreshResponse:
|
|
additionalProperties: false
|
|
properties:
|
|
data:
|
|
$ref: "#/components/schemas/AccessRefreshBundle"
|
|
error:
|
|
type: "null"
|
|
meta:
|
|
additionalProperties: false
|
|
properties:
|
|
requestId:
|
|
type: string
|
|
type: object
|
|
required:
|
|
- data
|
|
- error
|
|
type: object
|
|
RefreshTokenRequest:
|
|
description: Exchange a one-time-use refresh token for a fresh access + refresh pair.
|
|
properties:
|
|
refreshToken:
|
|
description: Caller-presented refresh token (`hlr_<64hex>`).
|
|
minLength: 1
|
|
type: string
|
|
revoke:
|
|
description: When true, revoke the supplied token instead of rotating.
|
|
type: boolean
|
|
required:
|
|
- refreshToken
|
|
type: object
|
|
securitySchemes:
|
|
bearerAuth:
|
|
bearerFormat: hlk_<64hex>
|
|
description: Native-client API token. Use the `token` field returned by /api/auth/login on a native client.
|
|
scheme: bearer
|
|
type: http
|
|
cookieAuth:
|
|
description: Browser session cookie — set by /api/auth/login.
|
|
in: cookie
|
|
name: healthlog_session
|
|
type: apiKey
|
|
info:
|
|
contact:
|
|
name: HealthLog
|
|
url: https://healthlog.dev
|
|
description: Self-hosted personal-health-tracking PWA — public API surface for the iOS native client and external ingest.
|
|
license:
|
|
name: AGPL-3.0
|
|
url: https://github.com/MBombeck/HealthLog/blob/main/LICENSE
|
|
title: HealthLog API
|
|
version: 1.4.23
|
|
openapi: 3.1.0
|
|
paths:
|
|
/api/auth/login:
|
|
post:
|
|
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:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
properties:
|
|
email:
|
|
minLength: 1
|
|
type: string
|
|
password:
|
|
minLength: 1
|
|
type: string
|
|
required:
|
|
- email
|
|
- password
|
|
type: object
|
|
required: true
|
|
responses:
|
|
"200":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/LoginResponse"
|
|
description: Login succeeded.
|
|
"401": &a1
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
description: Authentication required or invalid credentials.
|
|
"422": &a2
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
description: Request validation failed.
|
|
"429": &a3
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
description: Rate limit exceeded.
|
|
summary: Email-or-username login (password)
|
|
tags:
|
|
- Auth
|
|
/api/auth/me/coach-prefs:
|
|
get:
|
|
description:
|
|
Returns the persisted preferences with defaults filled in. Null persisted state resolves to the legacy
|
|
v1.4.22 defaults.
|
|
responses:
|
|
"200":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/GetCoachPrefsResponse"
|
|
description: Resolved preferences.
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
summary: Read per-user Coach prompt-tuning preferences
|
|
tags:
|
|
- Auth
|
|
put:
|
|
description:
|
|
Persists the supplied prefs. Body is validated against `CoachPrefs`; missing keys fall back to the
|
|
documented defaults.
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
properties:
|
|
excludeMetrics:
|
|
default: []
|
|
items:
|
|
enum:
|
|
- bp
|
|
- weight
|
|
- pulse
|
|
- mood
|
|
- compliance
|
|
- hrv
|
|
- sleep
|
|
- resting_hr
|
|
- steps
|
|
type: string
|
|
maxItems: 9
|
|
type: array
|
|
showEvidenceByDefault:
|
|
default: false
|
|
type: boolean
|
|
tone:
|
|
default: warm
|
|
enum:
|
|
- warm
|
|
- neutral
|
|
- concise
|
|
type: string
|
|
verbosity:
|
|
default: default
|
|
enum:
|
|
- brief
|
|
- default
|
|
- detailed
|
|
type: string
|
|
type: object
|
|
required: true
|
|
responses:
|
|
"200":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PutCoachPrefsResponse"
|
|
description: Saved preferences echoed back.
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
summary: Replace per-user Coach prompt-tuning preferences
|
|
tags:
|
|
- Auth
|
|
/api/auth/passkey/login-verify:
|
|
post:
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PasskeyLoginVerifyRequest"
|
|
required: true
|
|
responses:
|
|
"200":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/PasskeyLoginVerifyResponse"
|
|
description: Assertion verified — session + optional bearer issued.
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
summary: Passkey assertion verification
|
|
tags:
|
|
- Auth
|
|
/api/auth/refresh:
|
|
post:
|
|
description:
|
|
Reuse of a consumed refresh token revokes every refresh token still active for the originating device
|
|
(per-device blast radius from v1.4.23). Legacy tokens issued before v1.4.23 with a null deviceId fall back to
|
|
revoke-all-for-user.
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RefreshTokenRequest"
|
|
required: true
|
|
responses:
|
|
"200":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/RefreshResponse"
|
|
description: Rotation succeeded — new pair issued.
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
summary: Rotate refresh token (one-time use)
|
|
tags:
|
|
- Auth
|
|
/api/devices:
|
|
post:
|
|
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:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DeviceRegisterRequest"
|
|
required: true
|
|
responses:
|
|
"201":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/DeviceRegisterResponse"
|
|
description: Device registered or refreshed.
|
|
"401": *a1
|
|
"409":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
description: Device or APNs token already registered to another user.
|
|
"422": *a2
|
|
"429": *a3
|
|
summary: Register native device + APNs token
|
|
tags:
|
|
- Devices
|
|
/api/insights/chat/messages/{id}/feedback:
|
|
post:
|
|
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:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CoachMessageFeedbackRequest"
|
|
required: true
|
|
responses:
|
|
"201":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CoachMessageFeedbackResponse"
|
|
description: Feedback saved.
|
|
"401": *a1
|
|
"404":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
description: Message not found or not owned by the caller.
|
|
"409":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ErrorEnvelope"
|
|
description: Caller has already rated this message text.
|
|
"422": *a2
|
|
"429": *a3
|
|
summary: Rate a Coach assistant message
|
|
tags:
|
|
- Insights
|
|
/api/insights/comprehensive:
|
|
get:
|
|
description:
|
|
Full Insights surface — daily briefing, recommendations with rationale, optional weekly report + storyboard
|
|
annotations. Strict-schema validated server-side.
|
|
responses:
|
|
"200":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/InsightsComprehensiveResponseEnvelope"
|
|
description: Insights bundle.
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
summary: Comprehensive AI insights bundle
|
|
tags:
|
|
- Insights
|
|
/api/measurements:
|
|
get:
|
|
description: Filter by type + date range. Response is paged via offset/limit; default limit 100, hard cap 500.
|
|
parameters:
|
|
- in: query
|
|
name: type
|
|
schema:
|
|
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
|
|
type: string
|
|
- in: query
|
|
name: from
|
|
schema:
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: string
|
|
- in: query
|
|
name: to
|
|
schema:
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: string
|
|
- in: query
|
|
name: limit
|
|
schema:
|
|
default: 100
|
|
maximum: 500
|
|
minimum: 1
|
|
type: integer
|
|
- in: query
|
|
name: offset
|
|
schema:
|
|
default: 0
|
|
maximum: 9007199254740991
|
|
minimum: 0
|
|
type: integer
|
|
- in: query
|
|
name: sortBy
|
|
schema:
|
|
default: measuredAt
|
|
enum:
|
|
- type
|
|
- value
|
|
- measuredAt
|
|
- source
|
|
type: string
|
|
- in: query
|
|
name: sortDir
|
|
schema:
|
|
default: desc
|
|
enum:
|
|
- asc
|
|
- desc
|
|
type: string
|
|
responses:
|
|
"200":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/ListMeasurementsResponse"
|
|
description: Measurement page.
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
summary: List measurements
|
|
tags:
|
|
- Measurements
|
|
post:
|
|
description: Single ingest. Use `/api/measurements/batch` for Apple Health upload streams.
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
properties:
|
|
glucoseContext:
|
|
enum:
|
|
- FASTING
|
|
- POSTPRANDIAL
|
|
- RANDOM
|
|
- BEDTIME
|
|
type: string
|
|
measuredAt:
|
|
format: date-time
|
|
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: string
|
|
notes:
|
|
maxLength: 25
|
|
type: string
|
|
source:
|
|
default: MANUAL
|
|
enum:
|
|
- MANUAL
|
|
- WITHINGS
|
|
- IMPORT
|
|
- APPLE_HEALTH
|
|
type: string
|
|
type:
|
|
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
|
|
type: string
|
|
value:
|
|
type: number
|
|
required:
|
|
- type
|
|
- value
|
|
- measuredAt
|
|
type: object
|
|
required: true
|
|
responses:
|
|
"201":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/CreateMeasurementResponse"
|
|
description: Created.
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
summary: Create one measurement
|
|
tags:
|
|
- Measurements
|
|
/api/measurements/batch:
|
|
post:
|
|
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.
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/AppleHealthBatchRequest"
|
|
required: true
|
|
responses:
|
|
"200":
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: "#/components/schemas/BatchMeasurementsResponse"
|
|
description: Batch processed.
|
|
"401": *a1
|
|
"422": *a2
|
|
"429": *a3
|
|
summary: Apple Health batch ingest
|
|
tags:
|
|
- Measurements
|
|
servers:
|
|
- description: Production
|
|
url: https://healthlog.bombeck.io
|
|
- description: Local dev
|
|
url: http://localhost:3000
|
|
tags:
|
|
- name: Auth
|
|
- name: Measurements
|
|
- name: Medications
|
|
- name: Mood
|
|
- name: Insights
|
|
- name: Notifications
|
|
- name: Devices
|
|
- name: Admin
|