diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index a6cc5abb1..8d97fef51 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -312,123 +312,7 @@ paths: 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 - temperatureExcluded: - type: boolean - ovulationTest: - type: string - enum: - - NEGATIVE - - POSITIVE_LH_SURGE - - ESTROGEN_SURGE - - INDETERMINATE - cervicalMucus: - type: string - enum: - - DRY - - STICKY - - CREAMY - - WATERY - - EGG_WHITE - cervixPosition: - type: string - enum: - - LOW - - HIGH - cervixFirmness: - type: string - enum: - - FIRM - - SOFT - cervixOpening: - type: string - enum: - - CLOSED - - OPEN - 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 + $ref: "#/components/schemas/CycleDayLogInput" responses: "200": description: Existing day-log updated. @@ -482,129 +366,7 @@ paths: 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" - temperatureExcluded: - type: boolean - 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" - cervixPosition: - anyOf: - - type: string - enum: - - LOW - - HIGH - - type: "null" - cervixFirmness: - anyOf: - - type: string - enum: - - FIRM - - SOFT - - type: "null" - cervixOpening: - anyOf: - - type: string - enum: - - CLOSED - - OPEN - - 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)))$ + $ref: "#/components/schemas/CycleDayLogPatchRequest" responses: "200": description: Day-log updated. @@ -661,132 +423,7 @@ paths: 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 - temperatureExcluded: - type: boolean - ovulationTest: - type: string - enum: - - NEGATIVE - - POSITIVE_LH_SURGE - - ESTROGEN_SURGE - - INDETERMINATE - cervicalMucus: - type: string - enum: - - DRY - - STICKY - - CREAMY - - WATERY - - EGG_WHITE - cervixPosition: - type: string - enum: - - LOW - - HIGH - cervixFirmness: - type: string - enum: - - FIRM - - SOFT - cervixOpening: - type: string - enum: - - CLOSED - - OPEN - 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 + $ref: "#/components/schemas/CycleDayLogBulkRequest" responses: "200": description: Batch processed (per-entry results). @@ -813,28 +450,7 @@ paths: 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 + $ref: "#/components/schemas/CyclePeriodRequest" responses: "200": description: Cycle + boundary day-log. @@ -1008,49 +624,7 @@ paths: content: application/json: schema: - type: object - properties: - enabled: - type: boolean - goal: - type: string - enum: - - GENERAL_HEALTH - - AVOID_PREGNANCY - - TRYING_TO_CONCEIVE - - PERIMENOPAUSE - - OFF - secondarySymptom: - type: string - enum: - - MUCUS - - CERVIX - 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" + $ref: "#/components/schemas/CyclePrefsRequest" responses: "200": description: Merged cycle preferences. @@ -1211,67 +785,7 @@ paths: 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 - selection: - type: object - properties: - v: - type: number - const: 2 - leaves: - maxItems: 94 - type: array - items: - type: string - minLength: 1 - maxLength: 64 - required: - - v - - leaves - additionalProperties: false - locale: - type: string - enum: - - de - - en - - fr - - es - - it - - pl - practiceName: - type: string - maxLength: 120 - includeCharts: - type: boolean - germanAtc: - type: boolean - required: - - format - - selection - additionalProperties: false + $ref: "#/components/schemas/HealthRecordExportRequest" responses: "200": description: "Export generated. Content-Type varies by `format`: application/pdf, application/fhir+json, or @@ -1324,57 +838,7 @@ paths: 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" - selection: - type: object - properties: - v: - type: number - const: 2 - leaves: - maxItems: 94 - type: array - items: - type: string - minLength: 1 - maxLength: 64 - required: - - v - - leaves - additionalProperties: false - documentIds: - maxItems: 50 - type: array - items: - type: string - minLength: 1 - maxLength: 64 - documentOnly: - 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 + $ref: "#/components/schemas/CreateShareLinkRequest" responses: "201": description: Share link created. `token` carries the raw `hls_` value and is unrecoverable after this response. @@ -2222,17 +1686,7 @@ paths: content: application/json: schema: - type: object - properties: - email: - type: string - minLength: 1 - password: - type: string - minLength: 1 - required: - - email - - password + $ref: "#/components/schemas/LoginPasswordRequest" responses: "200": description: Login succeeded (token bundle / cookie) — or a second factor is required (`meta.mfaRequired`). @@ -4011,240 +3465,10 @@ paths: application/json: schema: anyOf: - - 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE - 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" - symptomsPresent: - type: boolean - required: - - type - - value - - measuredAt + - $ref: "#/components/schemas/CreateMeasurementRequest" - type: array items: - 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE - 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" - symptomsPresent: - type: boolean - required: - - type - - value - - measuredAt + $ref: "#/components/schemas/CreateMeasurementRequest" responses: "201": description: Created. A single resource for the object body; an array of resources, in request order, for the array body. @@ -4731,179 +3955,7 @@ paths: 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 - - badminton - - 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 - - APPLE_HEALTH - externalId: - type: string - maxLength: 128 - externalSourceVersion: - type: string - maxLength: 64 - metadata: - type: object - propertyNames: - type: string - additionalProperties: {} - route: - type: object - properties: - geometry: - type: object - properties: - type: - type: string - const: LineString - coordinates: - minItems: 2 - maxItems: 20000 - type: array - items: - type: array - prefixItems: - - type: number - minimum: -180 - maximum: 180 - - type: number - minimum: -90 - maximum: 90 - items: - type: number - required: - - type - - coordinates - sampleTimestamps: - type: array - items: - type: object - properties: - t: - type: string - format: date-time - pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ - speedMs: - type: number - minimum: 0 - maximum: 50 - hr: - type: integer - minimum: 20 - maximum: 300 - required: - - t - required: - - geometry - 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 + $ref: "#/components/schemas/CreateBatchWorkoutRequest" responses: "200": description: Batch processed. @@ -9026,90 +8078,7 @@ paths: content: application/json: schema: - type: object - properties: - conversationId: - type: string - minLength: 1 - maxLength: 64 - message: - type: string - minLength: 1 - maxLength: 4000 - prefill: - type: string - maxLength: 2000 - locale: - type: string - enum: - - en - - de - scope: - type: object - properties: - sources: - maxItems: 40 - type: array - items: - type: string - enum: - - bp - - weight - - pulse - - mood - - compliance - - hrv - - sleep - - resting_hr - - steps - - active_energy - - flights - - distance - - vo2_max - - body_temp - - walking_hr - - respiratory_rate - - spo2 - - pulse_wave_velocity - - vascular_age - - body_fat - - fat_mass - - fat_free_mass - - muscle_mass - - lean_body_mass - - bone_mass - - total_body_water - - bmi - - visceral_fat - - glucose - - walking_steadiness - - walking_asymmetry - - walking_double_support - - walking_step_length - - walking_speed - - audio_env - - audio_headphone - - audio_event - - daylight - - skin_temp - - workouts - window: - type: string - enum: - - last7days - - last30days - - last90days - - lastYear - - allTime - guidedQuestion: - type: string - minLength: 1 - maxLength: 500 - workoutId: - type: string - maxLength: 64 - required: - - message + $ref: "#/components/schemas/CoachChatRequest" responses: "200": description: Server-Sent Events stream of `token` / `provenance` / `done` / `error` frames. @@ -9814,54 +8783,7 @@ paths: content: application/json: schema: - type: object - properties: - bloodType: - anyOf: - - type: string - enum: - - A_POS - - A_NEG - - B_POS - - B_NEG - - AB_POS - - AB_NEG - - O_POS - - O_NEG - - UNKNOWN - - type: "null" - organDonor: - anyOf: - - type: string - enum: - - YES - - NO - - UNKNOWN - - type: "null" - advanceDirective: - anyOf: - - type: string - enum: - - EXISTS - - NONE - - UNKNOWN - - type: "null" - contacts: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - implants: - anyOf: - - type: string - maxLength: 1000 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - additionalProperties: false + $ref: "#/components/schemas/UpdateEmergencyProfileRequest" responses: "200": description: The updated emergency profile. @@ -10068,24 +8990,7 @@ paths: content: application/json: schema: - type: object - properties: - status: - type: string - enum: - - proposed - - active - - met - - abandoned - - review_due - - reviewed - reviewDate: - 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" - additionalProperties: false + $ref: "#/components/schemas/CoachPlanPatchRequest" responses: "200": description: The plan after the lifecycle update. @@ -10154,21 +9059,7 @@ paths: content: application/json: schema: - type: object - properties: - cadenceId: - type: string - minLength: 1 - maxLength: 64 - action: - type: string - enum: - - accept - - dismiss - - stop - required: - - cadenceId - - action + $ref: "#/components/schemas/CoachReminderSuggestionAction" responses: "200": description: "A dismiss/stop, or an accept that matched an existing reminder (`duplicate: true`)." @@ -10897,86 +9788,8 @@ paths: - in: query name: metric 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE description: "The metric the strip is about. Closed enum: an unknown value 422s." + $ref: "#/components/schemas/MeasurementType" required: true description: "The metric the strip is about. Closed enum: an unknown value 422s." responses: @@ -11218,86 +10031,8 @@ paths: - 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE description: "The measurement type to read (single metric — no fan-out). Closed enum: an unknown type 422s." + $ref: "#/components/schemas/MeasurementType" required: true description: "The measurement type to read (single metric — no fan-out). Closed enum: an unknown type 422s." - in: query @@ -13255,25 +11990,7 @@ paths: content: application/json: schema: - type: object - properties: - kind: - type: string - enum: - - ai_full - - ai_insights_only - - ai_coach - artefact: - type: string - minLength: 1 - signedAt: - type: string - format: date-time - pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: - - kind - - artefact - - signedAt + $ref: "#/components/schemas/ConsentPostBody" responses: "200": description: The minted receipt. @@ -13305,14 +12022,7 @@ paths: content: application/json: schema: - type: object - properties: - intent: - default: heal - type: string - enum: - - heal - - affirmative + $ref: "#/components/schemas/WebConsentGrantBody" responses: "200": description: Grant outcome. @@ -14193,63 +12903,7 @@ paths: content: application/json: schema: - type: object - properties: - rows: - minItems: 1 - maxItems: 100 - type: array - items: - type: object - properties: - analyte: - type: string - minLength: 1 - maxLength: 120 - panel: - anyOf: - - type: string - minLength: 1 - maxLength: 120 - - type: string - const: "" - value: - type: number - valueText: - type: string - minLength: 1 - maxLength: 120 - unit: - type: string - minLength: 1 - maxLength: 40 - referenceLow: - type: number - referenceHigh: - type: number - referenceText: - anyOf: - - type: string - maxLength: 120 - - type: string - const: "" - takenAt: - type: string - format: date-time - pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$ - required: - - analyte - - takenAt - documentId: - type: string - minLength: 1 - maxLength: 64 - encounterId: - type: string - minLength: 1 - maxLength: 40 - required: - - rows + $ref: "#/components/schemas/OcrCommitRequest" responses: "200": description: Inserted + skipped rows. @@ -14292,33 +12946,7 @@ paths: content: application/json: schema: - type: object - properties: - name: - type: string - minLength: 1 - maxLength: 120 - unit: - type: string - minLength: 1 - maxLength: 40 - lowerBound: - type: number - upperBound: - type: number - context: - type: string - maxLength: 2000 - panel: - anyOf: - - type: string - minLength: 1 - maxLength: 120 - - type: string - const: "" - required: - - name - - unit + $ref: "#/components/schemas/CreateBiomarkerRequest" responses: "201": description: Created biomarker. @@ -14382,40 +13010,7 @@ paths: content: application/json: schema: - type: object - properties: - name: - type: string - minLength: 1 - maxLength: 120 - unit: - type: string - minLength: 1 - maxLength: 40 - lowerBound: - anyOf: - - type: number - - type: "null" - upperBound: - anyOf: - - type: number - - type: "null" - context: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - panel: - anyOf: - - anyOf: - - type: string - minLength: 1 - maxLength: 120 - - type: string - const: "" - - type: "null" - hidden: - type: boolean + $ref: "#/components/schemas/UpdateBiomarkerRequest" responses: "200": description: Updated biomarker. @@ -14524,78 +13119,7 @@ paths: content: application/json: schema: - type: object - properties: - occurredAt: - type: string - format: date-time - pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ - status: - default: DONE - type: string - enum: - - PLANNED - - DONE - - CANCELLED - - NO_SHOW - kind: - default: OTHER - type: string - enum: - - ROUTINE - - ACUTE - - SPECIALIST - - PREVENTIVE - - EMERGENCY - - HOSPITAL - - THERAPY - - OTHER - practitionerId: - anyOf: - - type: string - minLength: 1 - maxLength: 40 - - type: "null" - reason: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - outcome: - anyOf: - - type: string - maxLength: 4000 - - type: "null" - reminderId: - anyOf: - - type: string - minLength: 1 - maxLength: 40 - - type: "null" - documentIds: - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - labResultIds: - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - episodeIds: - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - required: - - occurredAt - additionalProperties: false + $ref: "#/components/schemas/CreateEncounterRequest" responses: "201": description: Visit filed. @@ -14683,74 +13207,7 @@ paths: content: application/json: schema: - type: object - properties: - occurredAt: - type: string - format: date-time - pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ - status: - type: string - enum: - - PLANNED - - DONE - - CANCELLED - - NO_SHOW - kind: - type: string - enum: - - ROUTINE - - ACUTE - - SPECIALIST - - PREVENTIVE - - EMERGENCY - - HOSPITAL - - THERAPY - - OTHER - practitionerId: - anyOf: - - type: string - minLength: 1 - maxLength: 40 - - type: "null" - reason: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - outcome: - anyOf: - - type: string - maxLength: 4000 - - type: "null" - reminderId: - anyOf: - - type: string - minLength: 1 - maxLength: 40 - - type: "null" - documentIds: - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - labResultIds: - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - episodeIds: - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - additionalProperties: false + $ref: "#/components/schemas/UpdateEncounterRequest" responses: "200": description: Visit updated. @@ -14832,26 +13289,7 @@ paths: content: application/json: schema: - type: object - properties: - targetKind: - type: string - enum: - - document - - labResult - - conditionEpisode - targetIds: - minItems: 1 - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - required: - - targetKind - - targetIds - additionalProperties: false + $ref: "#/components/schemas/EncounterLinkRequest" responses: "200": description: The visit's links after the change. @@ -14880,26 +13318,7 @@ paths: content: application/json: schema: - type: object - properties: - targetKind: - type: string - enum: - - document - - labResult - - conditionEpisode - targetIds: - minItems: 1 - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - required: - - targetKind - - targetIds - additionalProperties: false + $ref: "#/components/schemas/EncounterLinkRequest" responses: "200": description: The visit's links after the change. @@ -14953,40 +13372,7 @@ paths: content: application/json: schema: - type: object - properties: - name: - type: string - minLength: 1 - maxLength: 160 - specialty: - anyOf: - - type: string - maxLength: 120 - - type: "null" - practice: - anyOf: - - type: string - maxLength: 160 - - type: "null" - location: - anyOf: - - type: string - maxLength: 300 - - type: "null" - phone: - anyOf: - - type: string - maxLength: 60 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - required: - - name - additionalProperties: false + $ref: "#/components/schemas/CreatePractitionerRequest" responses: "201": description: Practitioner added. @@ -15044,38 +13430,7 @@ paths: content: application/json: schema: - type: object - properties: - name: - type: string - minLength: 1 - maxLength: 160 - specialty: - anyOf: - - type: string - maxLength: 120 - - type: "null" - practice: - anyOf: - - type: string - maxLength: 160 - - type: "null" - location: - anyOf: - - type: string - maxLength: 300 - - type: "null" - phone: - anyOf: - - type: string - maxLength: 60 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - additionalProperties: false + $ref: "#/components/schemas/UpdatePractitionerRequest" responses: "200": description: Practitioner updated. @@ -15937,60 +14292,7 @@ paths: content: application/json: schema: - type: object - properties: - substance: - type: string - minLength: 1 - maxLength: 160 - category: - default: OTHER - type: string - enum: - - FOOD - - MEDICATION - - ENVIRONMENT - - BIOLOGIC - - OTHER - type: - default: ALLERGY - type: string - enum: - - ALLERGY - - INTOLERANCE - severity: - anyOf: - - type: string - enum: - - MILD - - MODERATE - - SEVERE - - type: "null" - status: - default: ACTIVE - type: string - enum: - - ACTIVE - - INACTIVE - - RESOLVED - onsetAt: - 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" - reaction: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - required: - - substance + $ref: "#/components/schemas/CreateAllergyRequest" responses: "201": description: Allergy created. @@ -16048,56 +14350,7 @@ paths: content: application/json: schema: - type: object - properties: - substance: - type: string - minLength: 1 - maxLength: 160 - category: - type: string - enum: - - FOOD - - MEDICATION - - ENVIRONMENT - - BIOLOGIC - - OTHER - type: - type: string - enum: - - ALLERGY - - INTOLERANCE - severity: - anyOf: - - type: string - enum: - - MILD - - MODERATE - - SEVERE - - type: "null" - status: - type: string - enum: - - ACTIVE - - INACTIVE - - RESOLVED - onsetAt: - 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" - reaction: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - additionalProperties: false + $ref: "#/components/schemas/UpdateAllergyRequest" responses: "200": description: Allergy updated. @@ -21132,6 +19385,578 @@ components: description: "Partial custom-symptom edit; at least one of `label` / `icon` / `isActive` is required. `isActive: false` hides the symptom while every day log that references it stays intact; `isActive: true` brings it back, and the per-record cap is re-checked on the way in so hiding and re-enabling cannot be used to exceed it." + CycleDayLogInput: + 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 + temperatureExcluded: + type: boolean + ovulationTest: + type: string + enum: + - NEGATIVE + - POSITIVE_LH_SURGE + - ESTROGEN_SURGE + - INDETERMINATE + cervicalMucus: + type: string + enum: + - DRY + - STICKY + - CREAMY + - WATERY + - EGG_WHITE + cervixPosition: + type: string + enum: + - LOW + - HIGH + cervixFirmness: + type: string + enum: + - FIRM + - SOFT + cervixOpening: + type: string + enum: + - CLOSED + - OPEN + 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 + description: "One day's cycle capture. `note` is encrypted at rest; every other field is queryable plaintext. UPSERT + key: `(userId, source, externalId)` when externalId present, else `(userId, date)`. Shared by the single POST, + the bulk drain, and the period shortcut." + CycleDayLogPatchRequest: + 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" + temperatureExcluded: + type: boolean + 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" + cervixPosition: + anyOf: + - type: string + enum: + - LOW + - HIGH + - type: "null" + cervixFirmness: + anyOf: + - type: string + enum: + - FIRM + - SOFT + - type: "null" + cervixOpening: + anyOf: + - type: string + enum: + - CLOSED + - OPEN + - 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)))$ + description: Partial day-log edit. Every field optional; `note` re-encrypts (explicit null clears it). `date` / `source` + / `externalId` are immutable on update. + CycleDayLogBulkRequest: + 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 + temperatureExcluded: + type: boolean + ovulationTest: + type: string + enum: + - NEGATIVE + - POSITIVE_LH_SURGE + - ESTROGEN_SURGE + - INDETERMINATE + cervicalMucus: + type: string + enum: + - DRY + - STICKY + - CREAMY + - WATERY + - EGG_WHITE + cervixPosition: + type: string + enum: + - LOW + - HIGH + cervixFirmness: + type: string + enum: + - FIRM + - SOFT + cervixOpening: + type: string + enum: + - CLOSED + - OPEN + 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 + description: Outbox / HealthKit drain. Up to 500 entries per call; wrapped in `withIdempotency`; rate-limited 60/min. + Each entry upserts per the day-log key. + CyclePeriodRequest: + 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 + description: One-tap period boundary. `start` opens a new cycle (closing the prior), `end` stamps the current cycle's + periodEndDate; both write a boundary day-log. + CyclePrefsRequest: + type: object + properties: + enabled: + type: boolean + goal: + type: string + enum: + - GENERAL_HEALTH + - AVOID_PREGNANCY + - TRYING_TO_CONCEIVE + - PERIMENOPAUSE + - OFF + secondarySymptom: + type: string + enum: + - MUCUS + - CERVIX + 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" + description: Partial cycle-preferences deep-merge. `enabled` flips the feature gate (`cycleTrackingEnabled`). Omitted + fields are left untouched. + HealthRecordExportRequest: + 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 + selection: + type: object + properties: + v: + type: number + const: 2 + leaves: + maxItems: 94 + type: array + items: + type: string + minLength: 1 + maxLength: 64 + required: + - v + - leaves + additionalProperties: false + locale: + type: string + enum: + - de + - en + - fr + - es + - it + - pl + practiceName: + type: string + maxLength: 120 + includeCharts: + type: boolean + germanAtc: + type: boolean + required: + - format + - selection + additionalProperties: false + description: "v1.7.0 — health-record / doctor-handover export selection. `format` picks PDF, FHIR R4 document Bundle, or + a combined zip package. `selection` is REQUIRED and carries the leaf inclusion list — membership is inclusion, + absence is exclusion, and there is no server-side default; the retired grouped `sections` shape is a 422. No + `userId` field — the user is always narrowed from the session/Bearer. The route is strict: unknown keys 422." + CreateShareLinkRequest: + 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" + selection: + type: object + properties: + v: + type: number + const: 2 + leaves: + maxItems: 94 + type: array + items: + type: string + minLength: 1 + maxLength: 64 + required: + - v + - leaves + additionalProperties: false + documentIds: + maxItems: 50 + type: array + items: + type: string + minLength: 1 + maxLength: 64 + documentOnly: + 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 + description: "v1.11.0 — owner request to mint a clinician share link to their own health record. `expiresAt` is required + (absolute ISO instant) and capped at 90 days. `rangeStart`/`rangeEnd` freeze the reporting window (rangeEnd null + = rolling). `selection` freezes which record leaves the link may serve, and omitting it means an empty scope + rather than a default one. `documentIds` freezes the documents the link carries; `documentOnly` mints a share + that serves those documents and no record scope at all. A share link serves the rendered page and the documents + frozen onto it — there is no FHIR or other machine-readable face behind a share token. Strict: unknown keys + 422." EncryptedExportRequest: type: object properties: @@ -21154,6 +19979,20 @@ components: required: - identifier description: "Discovery lookup for the iOS onboarding flow: given a typed email or username, resolve the next sign-in step." + LoginPasswordRequest: + type: object + properties: + email: + type: string + minLength: 1 + password: + type: string + minLength: 1 + required: + - email + - password + description: "Email-or-username login. The native-client flow returns a paired access + refresh token when + X-Client-Type: native or the iOS UA prefix is present." MfaVerifyRequest: type: object properties: @@ -21690,6 +20529,127 @@ components: - credential description: Finish a passkey enrollment. The handler reads these two fields directly rather than through a Zod schema; a missing one is a 422. + CreateMeasurementRequest: + 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 + - ANS_CHARGE + - CARDIO_LOAD + - SLEEP_SCORE + - BODY_TEMPERATURE_DEVIATION + - RESILIENCE + - PHQ9_SCORE + - GAD7_SCORE + - GRIP_STRENGTH + - PAIN_NRS + - WAIST_CIRCUMFERENCE + - WAIST_TO_HEIGHT + - WHO5_SCORE + - SCI_SCORE + 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" + symptomsPresent: + type: boolean + required: + - type + - value + - measuredAt + description: "Single-measurement ingest body. Plausibility-range guard runs server-side; out-of-range values fail 422. + `glucoseContext` stays REQUIRED on a `BLOOD_GLUCOSE` row here: this is the hand-entry surface, where the person + taking the reading knows whether it was fasting or after a meal. The bulk ingest paths (CSV import, JSON import, + device sync) accept a contextless reading, because a sensor export classifies nothing per sample." AppleHealthBatchRequest: type: object properties: @@ -21863,6 +20823,185 @@ components: - ids description: 1..200 measurement ids, scoped to the caller. Forged / foreign / not-deleted ids are silently skipped (no existence leak). + CreateBatchWorkoutRequest: + 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 + - badminton + - 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 + - APPLE_HEALTH + externalId: + type: string + maxLength: 128 + externalSourceVersion: + type: string + maxLength: 64 + metadata: + type: object + propertyNames: + type: string + additionalProperties: {} + route: + type: object + properties: + geometry: + type: object + properties: + type: + type: string + const: LineString + coordinates: + minItems: 2 + maxItems: 20000 + type: array + items: + type: array + prefixItems: + - type: number + minimum: -180 + maximum: 180 + - type: number + minimum: -90 + maximum: 90 + items: + type: number + required: + - type + - coordinates + sampleTimestamps: + type: array + items: + type: object + properties: + t: + type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ + speedMs: + type: number + minimum: 0 + maximum: 50 + hr: + type: integer + minimum: 20 + maximum: 300 + required: + - t + required: + - geometry + 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 + description: "Typed workout batch ingest. Each entry is an HKWorkout-aligned record with an optional nested GeoJSON + LineString route AND an optional route-independent per-workout heart-rate series (`samples`: `[{ t, hr?, + speedMs?, power?, cadence? }]`, up to 30 000 points). The `samples` series is the strain-engine input for indoor + workouts that have no GPS route. Up to 100 workouts per call; nested route geometry capped at 20 000 points. + Withings server-to-server callers pass source: WITHINGS and ship no route (Withings reports aggregates only)." DeviceRegisterRequest: type: object properties: @@ -23732,6 +22871,96 @@ components: required: - timezone description: The account's timezone. The handler reads and trims the field directly rather than through a Zod schema. + CoachChatRequest: + type: object + properties: + conversationId: + type: string + minLength: 1 + maxLength: 64 + message: + type: string + minLength: 1 + maxLength: 4000 + prefill: + type: string + maxLength: 2000 + locale: + type: string + enum: + - en + - de + scope: + type: object + properties: + sources: + maxItems: 40 + type: array + items: + type: string + enum: + - bp + - weight + - pulse + - mood + - compliance + - hrv + - sleep + - resting_hr + - steps + - active_energy + - flights + - distance + - vo2_max + - body_temp + - walking_hr + - respiratory_rate + - spo2 + - pulse_wave_velocity + - vascular_age + - body_fat + - fat_mass + - fat_free_mass + - muscle_mass + - lean_body_mass + - bone_mass + - total_body_water + - bmi + - visceral_fat + - glucose + - walking_steadiness + - walking_asymmetry + - walking_double_support + - walking_step_length + - walking_speed + - audio_env + - audio_headphone + - audio_event + - daylight + - skin_temp + - workouts + window: + type: string + enum: + - last7days + - last30days + - last90days + - lastYear + - allTime + guidedQuestion: + type: string + minLength: 1 + maxLength: 500 + workoutId: + type: string + maxLength: 64 + required: + - message + description: Inbound Coach turn. `message` is the user's turn (1–4 000 chars). `conversationId` is omitted to start a + new conversation (the server mints a title from the first message) and supplied to continue one. `scope` narrows + which metrics the snapshot ships and which window the timeline covers; omitted fields fall back to server + defaults. `locale` picks the reply language. `guidedQuestion` carries the clarifying question a message answers + (client-side bubble, never persisted). No `userId` field — the owner is narrowed from the session / Bearer. RenameCoachConversationRequest: type: object properties: @@ -23917,6 +23146,58 @@ components: - version description: PUT body for the Insights layout — the layout fields plus the optional optimistic-concurrency base token (`baseUpdatedAt`). Omit the token for the legacy unconditional write. + UpdateEmergencyProfileRequest: + type: object + properties: + bloodType: + anyOf: + - type: string + enum: + - A_POS + - A_NEG + - B_POS + - B_NEG + - AB_POS + - AB_NEG + - O_POS + - O_NEG + - UNKNOWN + - type: "null" + organDonor: + anyOf: + - type: string + enum: + - YES + - NO + - UNKNOWN + - type: "null" + advanceDirective: + anyOf: + - type: string + enum: + - EXISTS + - NONE + - UNKNOWN + - type: "null" + contacts: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + implants: + anyOf: + - type: string + maxLength: 1000 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + additionalProperties: false + description: Partial edit of the emergency (Notfalldaten) profile. An omitted key leaves the column untouched; a `null` + enum or an emptied free-text field clears it. `bloodType`, `organDonor` and `advanceDirective` are closed enums; + `contacts`, `implants` and `note` are encrypted at rest. Rejects unknown keys. CoachAboutMeAdoptRequest: type: object properties: @@ -23933,6 +23214,49 @@ components: description: An answer to fold into the stored self-context. `question` is the clarifying question it belongs to and is what the server matches the target field from; omit it for the remember-a-message path and the field is matched from `answer` instead. `answer` is capped at 500 characters — the same cap as a structured self-context field. + CoachPlanPatchRequest: + type: object + properties: + status: + type: string + enum: + - proposed + - active + - met + - abandoned + - review_due + - reviewed + reviewDate: + 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" + additionalProperties: false + description: "v1.21.3 — confirm or update a Coach plan's lifecycle. `status` moves a `proposed` plan to `active` + (confirm), or to `met` / `abandoned`. `reviewDate` pins (ISO instant) or clears (null) a check-in checkpoint. At + least one field is required. Strict: unknown keys 422 — the body can never carry the metric, the encrypted text, + or a userId." + CoachReminderSuggestionAction: + type: object + properties: + cadenceId: + type: string + minLength: 1 + maxLength: 64 + action: + type: string + enum: + - accept + - dismiss + - stop + required: + - cadenceId + - action + description: "v1.18.1 — act on a Coach cadence suggestion. `cadenceId` names a closed-catalog preset (e.g. + `weight_daily`, `bp_7_2_2`); the client never sends a schedule. `action`: `accept` creates a + `MeasurementReminder` (origin: COACH) through the same engine the Vorsorge surface uses; `dismiss` records + dismissal memory; `stop` suppresses all future cadence suggestions. Strict: unknown keys 422." CoachReminderCreate: type: object properties: @@ -24138,6 +23462,88 @@ components: genuinely different rating, which is the signal the aggregator wants. `providerType` and `promptVersion` are NOT accepted — the server fills both from the account's own attribution, so a client cannot tamper with the slice the quality dashboard reports on." + MeasurementType: + 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 + - ANS_CHARGE + - CARDIO_LOAD + - SLEEP_SCORE + - BODY_TEMPERATURE_DEVIATION + - RESILIENCE + - PHQ9_SCORE + - GAD7_SCORE + - GRIP_STRENGTH + - PAIN_NRS + - WAIST_CIRCUMFERENCE + - WAIST_TO_HEIGHT + - WHO5_SCORE + - SCI_SCORE + description: DB-stored measurement category. v1.4.23 added 7 Apple Health values (HRV, resting HR, active energy, + flights, walking/running distance, VO2 max, body temperature). InsightsSettingsPutRequest: type: object properties: @@ -25289,6 +24695,41 @@ components: required: - confirm description: Typed confirmation for erasing the record. No undo. + ConsentPostBody: + type: object + properties: + kind: + type: string + enum: + - ai_full + - ai_insights_only + - ai_coach + artefact: + type: string + minLength: 1 + signedAt: + type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: + - kind + - artefact + - signedAt + description: Explicit AI-consent grant. `artefact` is an opaque signed receipt (base64 PDF or JWT, ≤ 64 KB UTF-8 bytes); + `signedAt` is an ISO-8601 instant. Always appends a fresh row — re-granting after a revoke mints a new receipt. + WebConsentGrantBody: + type: object + properties: + intent: + default: heal + type: string + enum: + - heal + - affirmative + description: "Optional. Omitting the body (or sending `intent: \"heal\"`) is the AI-settings mount heal: it mints only + for an account with no `ai_full` consent history at all, so a standing revocation is never resurrected. `intent: + \"affirmative\"` is the user's own grant action and may supersede an earlier revocation, exactly as a first + grant would." JsonImportRequest: type: object properties: @@ -25308,85 +24749,7 @@ components: 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE + $ref: "#/components/schemas/MeasurementType" value: type: number description: Checked against the type's server-side plausibility range; an out-of-range value fails the whole request @@ -25756,6 +25119,387 @@ components: required: - mode - text + OcrCommitRequest: + type: object + properties: + rows: + minItems: 1 + maxItems: 100 + type: array + items: + type: object + properties: + analyte: + type: string + minLength: 1 + maxLength: 120 + panel: + anyOf: + - type: string + minLength: 1 + maxLength: 120 + - type: string + const: "" + value: + type: number + valueText: + type: string + minLength: 1 + maxLength: 120 + unit: + type: string + minLength: 1 + maxLength: 40 + referenceLow: + type: number + referenceHigh: + type: number + referenceText: + anyOf: + - type: string + maxLength: 120 + - type: string + const: "" + takenAt: + type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z))$ + required: + - analyte + - takenAt + documentId: + type: string + minLength: 1 + maxLength: 64 + encounterId: + type: string + minLength: 1 + maxLength: 40 + required: + - rows + description: The rows a human confirmed on the Lab-OCR review screen. Each row is EITHER numeric (`value` + `unit`, + optional reference bounds) OR qualitative (`valueText`) — exactly one. `analyte` drives a resolve-or-mint of the + user-scoped biomarker; `takenAt` is a backdatable ISO instant. No `userId` field — it is narrowed from the + session. 1..100 rows. The route skips a row that duplicates a live reading (same analyte + day + value). The + optional `encounterId` files the panel against a visit the caller owns — ONE link per written result row, so a + marker re-run on a different day belongs to its own visit; always optional, and a link that could not be made + never fails the commit. + CreateBiomarkerRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 120 + unit: + type: string + minLength: 1 + maxLength: 40 + lowerBound: + type: number + upperBound: + type: number + context: + type: string + maxLength: 2000 + panel: + anyOf: + - type: string + minLength: 1 + maxLength: 120 + - type: string + const: "" + required: + - name + - unit + description: "Define a user-scoped biomarker ONCE: canonical `name`, `unit`, optional reference bounds (`lowerBound` / + `upperBound`; when both present `lowerBound` must not exceed `upperBound`), an optional encrypted `context` + note, and an optional `panel` grouping. The name is unique per user (no second 'LDL'). Recording a value later + just picks this marker — its unit + range are never re-entered." + UpdateBiomarkerRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 120 + unit: + type: string + minLength: 1 + maxLength: 40 + lowerBound: + anyOf: + - type: number + - type: "null" + upperBound: + anyOf: + - type: number + - type: "null" + context: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + panel: + anyOf: + - anyOf: + - type: string + minLength: 1 + maxLength: 120 + - type: string + const: "" + - type: "null" + hidden: + type: boolean + description: Partial edit of a biomarker. An omitted key leaves the column untouched; an explicit `null` on `context` / + `panel` / a bound clears it. Set `hidden` to drop / restore the marker in the active list + lab-entry pickers. A + rename that collides with another of the caller's markers is rejected 409. + CreateEncounterRequest: + type: object + properties: + occurredAt: + type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ + status: + default: DONE + type: string + enum: + - PLANNED + - DONE + - CANCELLED + - NO_SHOW + kind: + default: OTHER + type: string + enum: + - ROUTINE + - ACUTE + - SPECIALIST + - PREVENTIVE + - EMERGENCY + - HOSPITAL + - THERAPY + - OTHER + practitionerId: + anyOf: + - type: string + minLength: 1 + maxLength: 40 + - type: "null" + reason: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + outcome: + anyOf: + - type: string + maxLength: 4000 + - type: "null" + reminderId: + anyOf: + - type: string + minLength: 1 + maxLength: 40 + - type: "null" + documentIds: + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + labResultIds: + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + episodeIds: + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + required: + - occurredAt + additionalProperties: false + description: "File a visit, or book one. `occurredAt` is the only required field — a visit saves with a date and nothing + else. A future instant with `status: PLANNED` books an appointment and mints exactly one reminder for it. + `reason` and `outcome` are encrypted at rest. The three id arrays pre-link documents, lab results and condition + episodes; an id naming nothing the caller owns is dropped rather than refused, so a link never blocks a save." + UpdateEncounterRequest: + type: object + properties: + occurredAt: + type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ + status: + type: string + enum: + - PLANNED + - DONE + - CANCELLED + - NO_SHOW + kind: + type: string + enum: + - ROUTINE + - ACUTE + - SPECIALIST + - PREVENTIVE + - EMERGENCY + - HOSPITAL + - THERAPY + - OTHER + practitionerId: + anyOf: + - type: string + minLength: 1 + maxLength: 40 + - type: "null" + reason: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + outcome: + anyOf: + - type: string + maxLength: 4000 + - type: "null" + reminderId: + anyOf: + - type: string + minLength: 1 + maxLength: 40 + - type: "null" + documentIds: + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + labResultIds: + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + episodeIds: + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + additionalProperties: false + description: Partial edit of a visit; an omitted key leaves the column untouched, and a body naming nothing is a 422. + Moving `occurredAt` re-anchors the appointment reminder the visit already owns and never mints a second. Moving + `status` to DONE closes the checkup named by `reminderId`, on the transition only. CANCELLED and NO_SHOW stop + the reminder without deleting the row. A present id array replaces that link family, empty array included. + EncounterLinkRequest: + type: object + properties: + targetKind: + type: string + enum: + - document + - labResult + - conditionEpisode + targetIds: + minItems: 1 + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + required: + - targetKind + - targetIds + additionalProperties: false + description: Targets to file against, or unfile from, a visit. Idempotent in both directions and capped at 100 ids. Ids + naming nothing the caller owns come back in `unknown` rather than failing the request. + CreatePractitionerRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 160 + specialty: + anyOf: + - type: string + maxLength: 120 + - type: "null" + practice: + anyOf: + - type: string + maxLength: 160 + - type: "null" + location: + anyOf: + - type: string + maxLength: 300 + - type: "null" + phone: + anyOf: + - type: string + maxLength: 60 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + required: + - name + additionalProperties: false + description: Add a doctor or practice to the caller's own address book. `name` is the only required field and is stored + as queryable plaintext so the picker can search and sort it; `note` is encrypted at rest. `specialty` is free + text rather than an enum. Nothing is unique across accounts. + UpdatePractitionerRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 160 + specialty: + anyOf: + - type: string + maxLength: 120 + - type: "null" + practice: + anyOf: + - type: string + maxLength: 160 + - type: "null" + location: + anyOf: + - type: string + maxLength: 300 + - type: "null" + phone: + anyOf: + - type: string + maxLength: 60 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + additionalProperties: false + description: Partial edit; an omitted key leaves the column untouched, an explicit null clears it, and a body naming + nothing is a 422. CreateVaccinationRequest: type: object properties: @@ -26169,6 +25913,117 @@ components: are encrypted at rest and never returned. The server computes the total (WHO-5 reports raw-sum × 4 as 0–100) + severity band + item-9 safety flag. Opt-in, beside mood tracking — this is a screening surface, not a diagnosis." + CreateAllergyRequest: + type: object + properties: + substance: + type: string + minLength: 1 + maxLength: 160 + category: + default: OTHER + type: string + enum: + - FOOD + - MEDICATION + - ENVIRONMENT + - BIOLOGIC + - OTHER + type: + default: ALLERGY + type: string + enum: + - ALLERGY + - INTOLERANCE + severity: + anyOf: + - type: string + enum: + - MILD + - MODERATE + - SEVERE + - type: "null" + status: + default: ACTIVE + type: string + enum: + - ACTIVE + - INACTIVE + - RESOLVED + onsetAt: + 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" + reaction: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + required: + - substance + description: Record an allergy/intolerance. `substance` is the user-facing allergen name (the FHIR AllergyIntolerance + `code.text` anchor — never a machine-guessed code). The free-text `reaction` + `note` are encrypted at rest. + `onsetAt` is optional (unknown when omitted). Patient-reported. + UpdateAllergyRequest: + type: object + properties: + substance: + type: string + minLength: 1 + maxLength: 160 + category: + type: string + enum: + - FOOD + - MEDICATION + - ENVIRONMENT + - BIOLOGIC + - OTHER + type: + type: string + enum: + - ALLERGY + - INTOLERANCE + severity: + anyOf: + - type: string + enum: + - MILD + - MODERATE + - SEVERE + - type: "null" + status: + type: string + enum: + - ACTIVE + - INACTIVE + - RESOLVED + onsetAt: + 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" + reaction: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + additionalProperties: false + description: Partial edit of an allergy; an omitted key leaves the column untouched. A `null` + `severity`/`onsetAt`/`reaction`/`note` clears that field. Rejects unknown keys. CreateFamilyHistoryRequest: type: object properties: @@ -28620,13 +28475,7 @@ components: - type: "null" flow: anyOf: - - type: string - enum: - - NONE - - SPOTTING - - LIGHT - - MEDIUM - - HEAVY + - $ref: "#/components/schemas/FlowLevel" - type: "null" intermenstrualBleeding: type: boolean @@ -28638,22 +28487,11 @@ components: type: boolean ovulationTest: anyOf: - - type: string - enum: - - NEGATIVE - - POSITIVE_LH_SURGE - - ESTROGEN_SURGE - - INDETERMINATE + - $ref: "#/components/schemas/OvulationTest" - type: "null" cervicalMucus: anyOf: - - type: string - enum: - - DRY - - STICKY - - CREAMY - - WATERY - - EGG_WHITE + - $ref: "#/components/schemas/CervicalMucus" - type: "null" cervixPosition: anyOf: @@ -28684,19 +28522,11 @@ components: - type: "null" pregnancyTest: anyOf: - - type: string - enum: - - NEGATIVE - - POSITIVE - - INDETERMINATE + - $ref: "#/components/schemas/HomeTestResult" - type: "null" progesteroneTest: anyOf: - - type: string - enum: - - NEGATIVE - - POSITIVE - - INDETERMINATE + - $ref: "#/components/schemas/HomeTestResult" - type: "null" contraceptive: anyOf: @@ -28771,6 +28601,39 @@ components: additionalProperties: false description: The canonical day-log row iOS mirrors. `note` is decrypted on read. Soft-deleted rows ride `/api/sync/changes` as tombstones. + FlowLevel: + type: string + enum: + - NONE + - SPOTTING + - LIGHT + - MEDIUM + - HEAVY + description: Menstrual-flow intensity. + OvulationTest: + type: string + enum: + - NEGATIVE + - POSITIVE_LH_SURGE + - ESTROGEN_SURGE + - INDETERMINATE + description: Ovulation predictor-kit (OPK) reading. + CervicalMucus: + type: string + enum: + - DRY + - STICKY + - CREAMY + - WATERY + - EGG_WHITE + description: Cervical-mucus quality. + HomeTestResult: + type: string + enum: + - NEGATIVE + - POSITIVE + - INDETERMINATE + description: At-home test result (pregnancy / progesterone). CycleDayLogEnvelope: type: object properties: @@ -28984,13 +28847,7 @@ components: type: object properties: goal: - type: string - enum: - - GENERAL_HEALTH - - AVOID_PREGNANCY - - TRYING_TO_CONCEIVE - - PERIMENOPAUSE - - OFF + $ref: "#/components/schemas/CycleTrackingGoal" rawChartMode: type: boolean predictionEnabled: @@ -29037,13 +28894,7 @@ components: description: Whether a logged cycle opens on this day. Deleting this day's log removes that cycle start with it. flow: anyOf: - - type: string - enum: - - NONE - - SPOTTING - - LIGHT - - MEDIUM - - HEAVY + - $ref: "#/components/schemas/FlowLevel" - type: "null" hasSymptoms: type: boolean @@ -29057,22 +28908,11 @@ components: type: boolean ovulationTest: anyOf: - - type: string - enum: - - NEGATIVE - - POSITIVE_LH_SURGE - - ESTROGEN_SURGE - - INDETERMINATE + - $ref: "#/components/schemas/OvulationTest" - type: "null" cervicalMucus: anyOf: - - type: string - enum: - - DRY - - STICKY - - CREAMY - - WATERY - - EGG_WHITE + - $ref: "#/components/schemas/CervicalMucus" - type: "null" cervixPosition: anyOf: @@ -29144,6 +28984,15 @@ components: - data - error additionalProperties: false + CycleTrackingGoal: + type: string + enum: + - GENERAL_HEALTH + - AVOID_PREGNANCY + - TRYING_TO_CONCEIVE + - PERIMENOPAUSE + - OFF + description: Drives cycle copy + fertile-window gating. CyclePredictionDTO: type: object properties: @@ -29664,13 +29513,7 @@ components: type: object properties: goal: - type: string - enum: - - GENERAL_HEALTH - - AVOID_PREGNANCY - - TRYING_TO_CONCEIVE - - PERIMENOPAUSE - - OFF + $ref: "#/components/schemas/CycleTrackingGoal" cycleTrackingEnabled: type: boolean secondarySymptom: @@ -36713,85 +36556,7 @@ components: description: Stored row id, or a synthetic key (`day::`, `sleep-seg::`) on the collapsed list modes. 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE + $ref: "#/components/schemas/MeasurementType" value: type: number unit: @@ -36801,22 +36566,7 @@ components: format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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 - - NIGHTSCOUT - - POLAR - - OURA - - TELEGRAM - - MCP - - GOOGLE_HEALTH - - STRAVA + $ref: "#/components/schemas/MeasurementSource" notes: anyOf: - type: string @@ -36903,6 +36653,24 @@ components: - syncVersion - updatedAt additionalProperties: false + MeasurementSource: + type: string + enum: + - MANUAL + - WITHINGS + - IMPORT + - APPLE_HEALTH + - COMPUTED + - WHOOP + - FITBIT + - NIGHTSCOUT + - POLAR + - OURA + - TELEGRAM + - MCP + - GOOGLE_HEALTH + - STRAVA + description: Origin of the measurement. v1.4.23 added APPLE_HEALTH for the iOS HealthKit batch ingest path. SyncMeasurementTombstone: type: object properties: @@ -37144,170 +36912,14 @@ components: series: type: object propertyNames: - 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE + $ref: "#/components/schemas/MeasurementType" additionalProperties: type: array items: 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE + $ref: "#/components/schemas/MeasurementType" value: type: number description: Daily mean (spot metrics) or SUM (cumulative). @@ -37578,85 +37190,7 @@ components: description: Stored row id, or a synthetic key (`day::`, `sleep-seg::`) on the collapsed list modes. 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE + $ref: "#/components/schemas/MeasurementType" value: type: number unit: @@ -37666,22 +37200,7 @@ components: format: date-time pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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 - - NIGHTSCOUT - - POLAR - - OURA - - TELEGRAM - - MCP - - GOOGLE_HEALTH - - STRAVA + $ref: "#/components/schemas/MeasurementSource" notes: anyOf: - type: string @@ -37772,85 +37291,7 @@ components: 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE + $ref: "#/components/schemas/MeasurementType" value: type: number description: The bucket's aggregated value. @@ -38193,22 +37634,7 @@ components: description: Wake-day key (YYYY-MM-DD). source: anyOf: - - type: string - enum: - - MANUAL - - WITHINGS - - IMPORT - - APPLE_HEALTH - - COMPUTED - - WHOOP - - FITBIT - - NIGHTSCOUT - - POLAR - - OURA - - TELEGRAM - - MCP - - GOOGLE_HEALTH - - STRAVA + - $ref: "#/components/schemas/MeasurementSource" - type: "null" start: type: string @@ -38362,22 +37788,7 @@ components: description: Wake-day key (YYYY-MM-DD). source: anyOf: - - type: string - enum: - - MANUAL - - WITHINGS - - IMPORT - - APPLE_HEALTH - - COMPUTED - - WHOOP - - FITBIT - - NIGHTSCOUT - - POLAR - - OURA - - TELEGRAM - - MCP - - GOOGLE_HEALTH - - STRAVA + - $ref: "#/components/schemas/MeasurementSource" - type: "null" start: type: string @@ -38583,25 +37994,10 @@ components: maximum: 9007199254740991 description: Nights still needed before the debt is asserted (0 when ready). source: - type: string - enum: - - MANUAL - - WITHINGS - - IMPORT - - APPLE_HEALTH - - COMPUTED - - WHOOP - - FITBIT - - NIGHTSCOUT - - POLAR - - OURA - - TELEGRAM - - MCP - - GOOGLE_HEALTH - - STRAVA description: v1.25.0 — the active source the debt is resolved FROM, picked off the user's `sleepDebt` source ladder. `COMPUTED` is HealthLog's own rolling-balance estimate (the only producer today); a provider value would be a device-native debt. Clients explain the figure when it is `COMPUTED`. + $ref: "#/components/schemas/MeasurementSource" required: - state - debtMinutes @@ -38734,85 +38130,7 @@ components: type: string description: The RECORD's user id — under a delegated read this is the owner, not the caller. metricType: - 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE + $ref: "#/components/schemas/MeasurementType" metricSlot: anyOf: - type: string @@ -38841,22 +38159,7 @@ components: description: The measurement that achieved the record. Nulled rather than cascaded when that measurement is later deleted, so the historical fact survives the reading behind it. source: - type: string - enum: - - MANUAL - - WITHINGS - - IMPORT - - APPLE_HEALTH - - COMPUTED - - WHOOP - - FITBIT - - NIGHTSCOUT - - POLAR - - OURA - - TELEGRAM - - MCP - - GOOGLE_HEALTH - - STRAVA + $ref: "#/components/schemas/MeasurementSource" externalId: anyOf: - type: string @@ -38982,22 +38285,7 @@ components: maximum: 9007199254740991 - type: "null" source: - type: string - enum: - - MANUAL - - WITHINGS - - IMPORT - - APPLE_HEALTH - - COMPUTED - - WHOOP - - FITBIT - - NIGHTSCOUT - - POLAR - - OURA - - TELEGRAM - - MCP - - GOOGLE_HEALTH - - STRAVA + $ref: "#/components/schemas/MeasurementSource" externalId: anyOf: - type: string @@ -39102,22 +38390,7 @@ components: maximum: 9007199254740991 - type: "null" source: - type: string - enum: - - MANUAL - - WITHINGS - - IMPORT - - APPLE_HEALTH - - COMPUTED - - WHOOP - - FITBIT - - NIGHTSCOUT - - POLAR - - OURA - - TELEGRAM - - MCP - - GOOGLE_HEALTH - - STRAVA + $ref: "#/components/schemas/MeasurementSource" externalId: anyOf: - type: string @@ -47726,67 +46999,7 @@ components: type: object properties: data: - type: object - properties: - bloodType: - anyOf: - - type: string - enum: - - A_POS - - A_NEG - - B_POS - - B_NEG - - AB_POS - - AB_NEG - - O_POS - - O_NEG - - UNKNOWN - - type: "null" - organDonor: - anyOf: - - type: string - enum: - - YES - - NO - - UNKNOWN - - type: "null" - advanceDirective: - anyOf: - - type: string - enum: - - EXISTS - - NONE - - UNKNOWN - - type: "null" - contacts: - anyOf: - - type: string - - type: "null" - contactsUnreadable: - type: boolean - implants: - anyOf: - - type: string - - type: "null" - implantsUnreadable: - type: boolean - note: - anyOf: - - type: string - - type: "null" - noteUnreadable: - type: boolean - required: - - bloodType - - organDonor - - advanceDirective - - contacts - - contactsUnreadable - - implants - - implantsUnreadable - - note - - noteUnreadable - additionalProperties: false + $ref: "#/components/schemas/EmergencyProfile" error: type: "null" meta: @@ -47799,71 +47012,76 @@ components: - data - error additionalProperties: false + EmergencyProfile: + type: object + properties: + bloodType: + anyOf: + - type: string + enum: + - A_POS + - A_NEG + - B_POS + - B_NEG + - AB_POS + - AB_NEG + - O_POS + - O_NEG + - UNKNOWN + - type: "null" + organDonor: + anyOf: + - type: string + enum: + - YES + - NO + - UNKNOWN + - type: "null" + advanceDirective: + anyOf: + - type: string + enum: + - EXISTS + - NONE + - UNKNOWN + - type: "null" + contacts: + anyOf: + - type: string + - type: "null" + contactsUnreadable: + type: boolean + implants: + anyOf: + - type: string + - type: "null" + implantsUnreadable: + type: boolean + note: + anyOf: + - type: string + - type: "null" + noteUnreadable: + type: boolean + required: + - bloodType + - organDonor + - advanceDirective + - contacts + - contactsUnreadable + - implants + - implantsUnreadable + - note + - noteUnreadable + additionalProperties: false + description: "The caller's emergency profile: three closed-enum facts plus three decrypted free-text fields. A free-text + field is null when unset; its `*Unreadable` flag is true when ciphertext was present but could not be decrypted + (a key-rotation gap, fail-soft rather than 500)." UpdateEmergencyProfileResponse: type: object properties: data: - type: object - properties: - bloodType: - anyOf: - - type: string - enum: - - A_POS - - A_NEG - - B_POS - - B_NEG - - AB_POS - - AB_NEG - - O_POS - - O_NEG - - UNKNOWN - - type: "null" - organDonor: - anyOf: - - type: string - enum: - - YES - - NO - - UNKNOWN - - type: "null" - advanceDirective: - anyOf: - - type: string - enum: - - EXISTS - - NONE - - UNKNOWN - - type: "null" - contacts: - anyOf: - - type: string - - type: "null" - contactsUnreadable: - type: boolean - implants: - anyOf: - - type: string - - type: "null" - implantsUnreadable: - type: boolean - note: - anyOf: - - type: string - - type: "null" - noteUnreadable: - type: boolean - required: - - bloodType - - organDonor - - advanceDirective - - contacts - - contactsUnreadable - - implants - - implantsUnreadable - - note - - noteUnreadable - additionalProperties: false + $ref: "#/components/schemas/EmergencyProfile" error: type: "null" meta: @@ -49374,85 +48592,7 @@ components: - type: object properties: metricType: - 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 - - ANS_CHARGE - - CARDIO_LOAD - - SLEEP_SCORE - - BODY_TEMPERATURE_DEVIATION - - RESILIENCE - - PHQ9_SCORE - - GAD7_SCORE - - GRIP_STRENGTH - - PAIN_NRS - - WAIST_CIRCUMFERENCE - - WAIST_TO_HEIGHT - - WHO5_SCORE - - SCI_SCORE + $ref: "#/components/schemas/MeasurementType" daysInside: type: integer minimum: -9007199254740991 @@ -53052,22 +52192,7 @@ components: pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: Device-reported event time (`measuredAt`). source: - type: string - enum: - - MANUAL - - WITHINGS - - IMPORT - - APPLE_HEALTH - - COMPUTED - - WHOOP - - FITBIT - - NIGHTSCOUT - - POLAR - - OURA - - TELEGRAM - - MCP - - GOOGLE_HEALTH - - STRAVA + $ref: "#/components/schemas/MeasurementSource" deviceType: anyOf: - type: string @@ -68573,6 +67698,8 @@ components: - showEvidenceByDefault - defaultWindow additionalProperties: false + description: Per-user Coach prompt-tuning preferences (v1.4.23 H4). All fields default to the legacy v1.4.22 behaviour + when omitted. MoodTagLayout: type: object properties: diff --git a/src/lib/openapi/routes/allergies.ts b/src/lib/openapi/routes/allergies.ts index 2be4d0a16..3f1629054 100644 --- a/src/lib/openapi/routes/allergies.ts +++ b/src/lib/openapi/routes/allergies.ts @@ -33,19 +33,23 @@ import { stdResponses, } from "./shared"; -allergyCreateSchema.meta({ +// `.meta()` CLONES in Zod 4 rather than annotating in place, so the returned +// schema has to be captured and referenced. A bare `schema.meta({...})` +// statement registers nothing and the component id it names never reaches the +// emitted document. +const createAllergyRequest = allergyCreateSchema.meta({ id: "CreateAllergyRequest", description: "Record an allergy/intolerance. `substance` is the user-facing allergen name (the FHIR AllergyIntolerance `code.text` anchor — never a machine-guessed code). The free-text `reaction` + `note` are encrypted at rest. `onsetAt` is optional (unknown when omitted). Patient-reported.", }); -allergyUpdateSchema.meta({ +const updateAllergyRequest = allergyUpdateSchema.meta({ id: "UpdateAllergyRequest", description: "Partial edit of an allergy; an omitted key leaves the column untouched. A `null` `severity`/`onsetAt`/`reaction`/`note` clears that field. Rejects unknown keys.", }); -allergyListQuerySchema.meta({ +const listAllergiesQuery = allergyListQuerySchema.meta({ id: "ListAllergiesQuery", description: "Query params for the allergy list: optional `limit` (1–200, default 100) and `includeInactive` ('true' | 'false'); 'false' returns only ACTIVE records. Newest-first.", @@ -85,7 +89,7 @@ export const allergyPaths: NonNullable = { summary: "List allergies (v1.25)", description: "Returns the caller's live (non-deleted) allergy/intolerance records, newest-first. `includeInactive=false` returns only ACTIVE records.", - requestParams: { query: allergyListQuerySchema }, + requestParams: { query: listAllergiesQuery }, responses: { ...recordRefusal(), "200": { @@ -107,7 +111,7 @@ export const allergyPaths: NonNullable = { parameters: [idempotencyKeyParameter], requestBody: { required: true, - content: { "application/json": { schema: allergyCreateSchema } }, + content: { "application/json": { schema: createAllergyRequest } }, }, responses: { ...idempotentWrite(), @@ -153,7 +157,7 @@ export const allergyPaths: NonNullable = { requestParams: { path: z.object({ id: z.string() }) }, requestBody: { required: true, - content: { "application/json": { schema: allergyUpdateSchema } }, + content: { "application/json": { schema: updateAllergyRequest } }, }, responses: { ...recordRefusal(), diff --git a/src/lib/openapi/routes/auth.ts b/src/lib/openapi/routes/auth.ts index ef464a1b1..3d2694ab7 100644 --- a/src/lib/openapi/routes/auth.ts +++ b/src/lib/openapi/routes/auth.ts @@ -14,7 +14,6 @@ import { z } from "zod/v4"; import type { ZodOpenApiObject } from "zod-openapi"; import { changePasswordSchema, - loginPasswordSchema, passkeyRenameSchema, registerSchema, } from "@/lib/validations/auth"; @@ -36,7 +35,12 @@ import { stepUpMintSchema, stepUpOptionsSchema, } from "@/lib/validations/step-up"; -import { dataEnvelope, stdResponses, errorEnvelope } from "./shared"; +import { + dataEnvelope, + stdResponses, + errorEnvelope, + loginPasswordSchema, +} from "./shared"; // ── Sub-schemas owned here (route-specific shapes) ─────────────────── diff --git a/src/lib/openapi/routes/awards.ts b/src/lib/openapi/routes/awards.ts index a442b6076..d81221375 100644 --- a/src/lib/openapi/routes/awards.ts +++ b/src/lib/openapi/routes/awards.ts @@ -12,14 +12,12 @@ import { z } from "zod/v4"; import type { ZodOpenApiObject } from "zod-openapi"; -import { - measurementSourceEnum, - measurementTypeEnum, -} from "@/lib/validations/measurement"; import { MODULE_DISABLED_DESCRIPTION, dataEnvelope, errorEnvelope, + measurementSourceEnum, + measurementTypeEnum, recordRefusal, stdResponses, } from "./shared"; diff --git a/src/lib/openapi/routes/biomarkers.ts b/src/lib/openapi/routes/biomarkers.ts index ff9a04202..230e0e47e 100644 --- a/src/lib/openapi/routes/biomarkers.ts +++ b/src/lib/openapi/routes/biomarkers.ts @@ -28,13 +28,17 @@ import { stdResponses, } from "./shared"; -createBiomarkerSchema.meta({ +// `.meta()` CLONES in Zod 4 rather than annotating in place, so the returned +// schema has to be captured and referenced. A bare `schema.meta({...})` +// statement registers nothing and the component id it names never reaches the +// emitted document. +const createBiomarkerRequest = createBiomarkerSchema.meta({ id: "CreateBiomarkerRequest", description: "Define a user-scoped biomarker ONCE: canonical `name`, `unit`, optional reference bounds (`lowerBound` / `upperBound`; when both present `lowerBound` must not exceed `upperBound`), an optional encrypted `context` note, and an optional `panel` grouping. The name is unique per user (no second 'LDL'). Recording a value later just picks this marker — its unit + range are never re-entered.", }); -updateBiomarkerSchema.meta({ +const updateBiomarkerRequest = updateBiomarkerSchema.meta({ id: "UpdateBiomarkerRequest", description: "Partial edit of a biomarker. An omitted key leaves the column untouched; an explicit `null` on `context` / `panel` / a bound clears it. Set `hidden` to drop / restore the marker in the active list + lab-entry pickers. A rename that collides with another of the caller's markers is rejected 409.", @@ -106,7 +110,7 @@ export const biomarkerPaths: NonNullable = { parameters: [idempotencyKeyParameter], requestBody: { required: true, - content: { "application/json": { schema: createBiomarkerSchema } }, + content: { "application/json": { schema: createBiomarkerRequest } }, }, responses: { ...idempotentWrite(), @@ -153,7 +157,7 @@ export const biomarkerPaths: NonNullable = { requestParams: { path: z.object({ id: z.string() }) }, requestBody: { required: true, - content: { "application/json": { schema: updateBiomarkerSchema } }, + content: { "application/json": { schema: updateBiomarkerRequest } }, }, responses: { ...recordRefusal(), diff --git a/src/lib/openapi/routes/coach.ts b/src/lib/openapi/routes/coach.ts index 6ed940018..3a986fb7d 100644 --- a/src/lib/openapi/routes/coach.ts +++ b/src/lib/openapi/routes/coach.ts @@ -33,15 +33,12 @@ import { COACH_PLAN_STATUSES } from "@/lib/ai/coach/plans"; import { COACH_PLAN_SCOPES, coachPlanPatchSchema, - coachPlansListQuerySchema, } from "@/lib/validations/coach-plan"; import { coachChatRequestSchema } from "@/lib/ai/coach/types"; import { coachAttachmentCreateSchema, fencedChatRequestSchema, } from "@/lib/validations/inbound-documents"; -import { exportSelectionSchema } from "@/lib/validations/health-record-export"; -import { createShareLinkSchema } from "@/lib/validations/clinician-share-link"; import { coachReminderSuggestionActionSchema } from "@/lib/validations/coach-reminder-suggestion"; import { COACH_REMINDER_STATUSES } from "@/lib/ai/coach/reminders"; import { @@ -71,13 +68,13 @@ const aboutMeAdoptRequest = aboutMeAdoptSchema.meta({ "An answer to fold into the stored self-context. `question` is the clarifying question it belongs to and is what the server matches the target field from; omit it for the remember-a-message path and the field is matched from `answer` instead. `answer` is capped at 500 characters — the same cap as a structured self-context field.", }); -emergencyProfileUpdateSchema.meta({ +const updateEmergencyProfileRequest = emergencyProfileUpdateSchema.meta({ id: "UpdateEmergencyProfileRequest", description: "Partial edit of the emergency (Notfalldaten) profile. An omitted key leaves the column untouched; a `null` enum or an emptied free-text field clears it. `bloodType`, `organDonor` and `advanceDirective` are closed enums; `contacts`, `implants` and `note` are encrypted at rest. Rejects unknown keys.", }); -emergencyProfileDtoSchema.meta({ +const emergencyProfile = emergencyProfileDtoSchema.meta({ id: "EmergencyProfile", description: "The caller's emergency profile: three closed-enum facts plus three decrypted free-text fields. A free-text field is null when unset; its `*Unreadable` flag is true when ciphertext was present but could not be decrypted (a key-rotation gap, fail-soft rather than 500).", @@ -88,7 +85,7 @@ emergencyProfileDtoSchema.meta({ // client sends ONLY the cadence id + the action; the server resolves the // metric + schedule + course window from the closed catalog and (for // `accept`) mints a `MeasurementReminder` with `origin: COACH`. -coachReminderSuggestionActionSchema.meta({ +const coachReminderSuggestionAction = coachReminderSuggestionActionSchema.meta({ id: "CoachReminderSuggestionAction", description: "v1.18.1 — act on a Coach cadence suggestion. `cadenceId` names a closed-catalog preset (e.g. `weight_daily`, `bp_7_2_2`); the client never sends a schedule. `action`: `accept` creates a `MeasurementReminder` (origin: COACH) through the same engine the Vorsorge surface uses; `dismiss` records dismissal memory; `stop` suppresses all future cadence suggestions. Strict: unknown keys 422.", @@ -130,7 +127,7 @@ export const coachReminderSuggestionPaths: NonNullable< requestBody: { required: true, content: { - "application/json": { schema: coachReminderSuggestionActionSchema }, + "application/json": { schema: coachReminderSuggestionAction }, }, }, responses: { @@ -264,24 +261,6 @@ const insightsLayoutResult = insightsLayoutSchema "Resolved Insights layout plus the optimistic-concurrency `updatedAt` token.", }); -// v1.7.0 — health-record export selection. Strict shape: unknown keys -// (including any attempt to smuggle a userId) 422 via returnAllZodIssues. -// v1.11.0 — clinician share-link create payload. Strict; no `userId` field -// (the owner is always narrowed from the session/Bearer). `expiresAt` is -// required and capped at SHARE_LINK_MAX_DAYS; the scope columns are frozen -// write-once at creation. -createShareLinkSchema.meta({ - id: "CreateShareLinkRequest", - description: - "v1.11.0 — owner request to mint a clinician share link to their own health record. `expiresAt` is required (absolute ISO instant) and capped at 90 days. `rangeStart`/`rangeEnd` freeze the reporting window (rangeEnd null = rolling). `sections` freezes which record domains the link may serve. A share link serves the rendered page and the documents frozen onto it — there is no FHIR or other machine-readable face behind a share token. Strict: unknown keys 422.", -}); - -exportSelectionSchema.meta({ - id: "HealthRecordExportRequest", - description: - "v1.7.0 — health-record / doctor-handover export selection. `format` picks PDF, FHIR R4 document Bundle, or a combined zip package. Grouped `sections` toggles drive which domains are read (mood is opt-in, off by default). No `userId` field — the user is always narrowed from the session/Bearer. The route is strict: unknown keys 422.", -}); - // ── Coach facts (v1.11.1) ──────────────────────────────────────────── // Read + delete surface for the durable facts the Coach extracts. Facts // are server-extracted, not user-authored, so there is no create/update @@ -396,17 +375,21 @@ const coachPlanDeletedResponse = z.object({ ), }); -coachPlanPatchSchema.meta({ +const coachPlanPatchRequest = coachPlanPatchSchema.meta({ id: "CoachPlanPatchRequest", description: "v1.21.3 — confirm or update a Coach plan's lifecycle. `status` moves a `proposed` plan to `active` (confirm), or to `met` / `abandoned`. `reviewDate` pins (ISO instant) or clears (null) a check-in checkpoint. At least one field is required. Strict: unknown keys 422 — the body can never carry the metric, the encrypted text, or a userId.", }); -coachPlansListQuerySchema.meta({ - id: "CoachPlansListQuery", - description: - "Optional `?status=` filter for the plans list (a single lifecycle status), or `?scope=` for a named group (open = proposed + active + review_due, past = met + abandoned + reviewed, all = every non-deleted plan). Mutually exclusive. Both omitted returns the non-terminal set (proposed + active).", -}); +// `GET /api/coach/plans` publishes its two query parameters by hand below, +// not through `requestParams.query`: the runtime schema carries a cross-field +// `.refine()` (status and scope are mutually exclusive) that the expansion +// cannot express, and the hand-written pair carries per-parameter prose it +// would drop. A `CoachPlansListQuery` annotation sat here claiming a component +// id that nothing referenced and that a query object could never reach anyway +// — the emitter inlines query objects into `parameters`. Every clause of it is +// already published on the operation and on both parameters, so it is gone +// rather than left registering nothing. // ── Coach episodic reminders (v1.22 B2/B6) ────────────────────────────── const coachReminderItem = z @@ -727,7 +710,7 @@ const coachAttachmentsResponseSchema = z }) .meta({ id: "CoachAttachments" }); -coachChatRequestSchema.meta({ +const coachChatRequest = coachChatRequestSchema.meta({ id: "CoachChatRequest", description: "Inbound Coach turn. `message` is the user's turn (1–4 000 chars). `conversationId` is omitted to start a new conversation (the server mints a title from the first message) and supplied to continue one. `scope` narrows which metrics the snapshot ships and which window the timeline covers; omitted fields fall back to server defaults. `locale` picks the reply language. `guidedQuestion` carries the clarifying question a message answers (client-side bubble, never persisted). No `userId` field — the owner is narrowed from the session / Bearer.", @@ -804,7 +787,7 @@ export const coachPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: coachChatRequestSchema }, + "application/json": { schema: coachChatRequest }, }, }, responses: { @@ -1468,7 +1451,7 @@ export const coachPaths: NonNullable = { content: { "application/json": { schema: dataEnvelope( - emergencyProfileDtoSchema, + emergencyProfile, "GetEmergencyProfileResponse", ), }, @@ -1485,7 +1468,7 @@ export const coachPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: emergencyProfileUpdateSchema }, + "application/json": { schema: updateEmergencyProfileRequest }, }, }, responses: { @@ -1494,7 +1477,7 @@ export const coachPaths: NonNullable = { content: { "application/json": { schema: dataEnvelope( - emergencyProfileDtoSchema, + emergencyProfile, "UpdateEmergencyProfileResponse", ), }, @@ -1675,7 +1658,7 @@ export const coachPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: coachPlanPatchSchema }, + "application/json": { schema: coachPlanPatchRequest }, }, }, responses: { diff --git a/src/lib/openapi/routes/consent.ts b/src/lib/openapi/routes/consent.ts index ccaf55697..e98167411 100644 --- a/src/lib/openapi/routes/consent.ts +++ b/src/lib/openapi/routes/consent.ts @@ -26,18 +26,23 @@ import { z } from "zod/v4"; import { consentKindEnum, - consentPostBody, - webConsentGrantBody, + consentPostBody as consentPostBodyBase, + webConsentGrantBody as webConsentGrantBodyBase, } from "@/lib/validations/consent"; import { dataEnvelope, errorEnvelope, stdResponses } from "./shared"; -consentPostBody.meta({ +// `.meta()` CLONES in Zod 4 rather than annotating in place, so the returned +// schema has to be captured and referenced. A bare `schema.meta({...})` +// statement registers nothing and the component id it names never reaches the +// emitted document. The imports are aliased so the annotated clone can keep the +// name every use site below already spells. +const consentPostBody = consentPostBodyBase.meta({ id: "ConsentPostBody", description: "Explicit AI-consent grant. `artefact` is an opaque signed receipt (base64 PDF or JWT, ≤ 64 KB UTF-8 bytes); `signedAt` is an ISO-8601 instant. Always appends a fresh row — re-granting after a revoke mints a new receipt.", }); -webConsentGrantBody.meta({ +const webConsentGrantBody = webConsentGrantBodyBase.meta({ id: "WebConsentGrantBody", description: 'Optional. Omitting the body (or sending `intent: "heal"`) is the AI-settings mount heal: it mints only for an account with no `ai_full` consent history at all, so a standing revocation is never resurrected. `intent: "affirmative"` is the user\'s own grant action and may supersede an earlier revocation, exactly as a first grant would.', diff --git a/src/lib/openapi/routes/cycle.ts b/src/lib/openapi/routes/cycle.ts index e4dacb5f5..903a8cd93 100644 --- a/src/lib/openapi/routes/cycle.ts +++ b/src/lib/openapi/routes/cycle.ts @@ -52,52 +52,56 @@ const cyclePhaseEnumOpenapi = z .enum(["MENSTRUAL", "FOLLICULAR", "OVULATORY", "LUTEAL"]) .meta({ id: "CyclePhase" }); -flowLevelEnum.meta({ +// `.meta()` CLONES in Zod 4 rather than annotating in place, so the returned +// schema has to be captured and referenced. A bare `schema.meta({...})` +// statement registers nothing and the component id it names never reaches the +// emitted document. +const flowLevelEnumOpenapi = flowLevelEnum.meta({ id: "FlowLevel", description: "Menstrual-flow intensity.", }); -ovulationTestEnum.meta({ +const ovulationTestEnumOpenapi = ovulationTestEnum.meta({ id: "OvulationTest", description: "Ovulation predictor-kit (OPK) reading.", }); -cervicalMucusEnum.meta({ +const cervicalMucusEnumOpenapi = cervicalMucusEnum.meta({ id: "CervicalMucus", description: "Cervical-mucus quality.", }); -homeTestResultEnum.meta({ +const homeTestResultEnumOpenapi = homeTestResultEnum.meta({ id: "HomeTestResult", description: "At-home test result (pregnancy / progesterone).", }); -cycleTrackingGoalEnum.meta({ +const cycleTrackingGoalEnumOpenapi = cycleTrackingGoalEnum.meta({ id: "CycleTrackingGoal", description: "Drives cycle copy + fertile-window gating.", }); -cycleDayLogInputSchema.meta({ +const cycleDayLogInput = cycleDayLogInputSchema.meta({ id: "CycleDayLogInput", description: "One day's cycle capture. `note` is encrypted at rest; every other field is queryable plaintext. UPSERT key: `(userId, source, externalId)` when externalId present, else `(userId, date)`. Shared by the single POST, the bulk drain, and the period shortcut.", }); -cycleBulkSchema.meta({ +const cycleDayLogBulkRequest = cycleBulkSchema.meta({ id: "CycleDayLogBulkRequest", description: "Outbox / HealthKit drain. Up to 500 entries per call; wrapped in `withIdempotency`; rate-limited 60/min. Each entry upserts per the day-log key.", }); -cyclePeriodSchema.meta({ +const cyclePeriodRequest = cyclePeriodSchema.meta({ id: "CyclePeriodRequest", description: "One-tap period boundary. `start` opens a new cycle (closing the prior), `end` stamps the current cycle's periodEndDate; both write a boundary day-log.", }); -cyclePrefsSchema.meta({ +const cyclePrefsRequest = cyclePrefsSchema.meta({ id: "CyclePrefsRequest", description: "Partial cycle-preferences deep-merge. `enabled` flips the feature gate (`cycleTrackingEnabled`). Omitted fields are left untouched.", }); -cycleDayLogPatchSchema.meta({ +const cycleDayLogPatchRequest = cycleDayLogPatchSchema.meta({ id: "CycleDayLogPatchRequest", description: "Partial day-log edit. Every field optional; `note` re-encrypts (explicit null clears it). `date` / `source` / `externalId` are immutable on update.", @@ -113,19 +117,19 @@ const cycleDayLogDto = z id: z.string(), date: z.string(), cycleId: z.string().nullable(), - flow: flowLevelEnum.nullable(), + flow: flowLevelEnumOpenapi.nullable(), intermenstrualBleeding: z.boolean(), basalBodyTempC: z.number().nullable(), temperatureExcluded: z.boolean(), - ovulationTest: ovulationTestEnum.nullable(), - cervicalMucus: cervicalMucusEnum.nullable(), + ovulationTest: ovulationTestEnumOpenapi.nullable(), + cervicalMucus: cervicalMucusEnumOpenapi.nullable(), cervixPosition: cervixPositionEnum.nullable(), cervixFirmness: cervixFirmnessEnum.nullable(), cervixOpening: cervixOpeningEnum.nullable(), sexualActivity: z.boolean(), protectedSex: z.boolean().nullable(), - pregnancyTest: homeTestResultEnum.nullable(), - progesteroneTest: homeTestResultEnum.nullable(), + pregnancyTest: homeTestResultEnumOpenapi.nullable(), + progesteroneTest: homeTestResultEnumOpenapi.nullable(), contraceptive: z.string().nullable(), symptoms: z.array(cycleSymptomDto), note: z.string().nullable(), @@ -191,7 +195,7 @@ const cycleCalendarDayDto = z.object({ description: "Whether a logged cycle opens on this day. Deleting this day's log removes that cycle start with it.", }), - flow: flowLevelEnum.nullable(), + flow: flowLevelEnumOpenapi.nullable(), hasSymptoms: z.boolean(), confidence: z.number(), // v1.15.0 — logged basal-body-temperature + fertility-sign markers, surfaced @@ -199,8 +203,8 @@ const cycleCalendarDayDto = z.object({ // loaded server-side for the symptothermal layer; no extra query). basalBodyTempC: z.number().nullable(), temperatureExcluded: z.boolean(), - ovulationTest: ovulationTestEnum.nullable(), - cervicalMucus: cervicalMucusEnum.nullable(), + ovulationTest: ovulationTestEnumOpenapi.nullable(), + cervicalMucus: cervicalMucusEnumOpenapi.nullable(), cervixPosition: cervixPositionEnum.nullable(), cervixFirmness: cervixFirmnessEnum.nullable(), cervixOpening: cervixOpeningEnum.nullable(), @@ -247,7 +251,7 @@ const cycleVerdictDto = z const cycleProfileDto = z .object({ - goal: cycleTrackingGoalEnum, + goal: cycleTrackingGoalEnumOpenapi, cycleTrackingEnabled: z.boolean(), secondarySymptom: secondarySymptomEnum, rawChartMode: z.boolean(), @@ -266,7 +270,7 @@ const cycleProfileDto = z const cycleCalendarResponse = z.object({ profile: z.object({ - goal: cycleTrackingGoalEnum, + goal: cycleTrackingGoalEnumOpenapi, rawChartMode: z.boolean(), predictionEnabled: z.boolean(), cyclesObserved: z.number().int(), @@ -444,10 +448,6 @@ const cycleDisabledOnADelegableRoute = recordRefusal( // rest and never reaches a wide event or an audit excerpt — only the icon and // the minted key do. -// `.meta()` RETURNS a new schema in Zod 4 rather than annotating in place, so -// the result has to be captured and referenced. A bare `schema.meta({...})` -// statement registers nothing, and the component id it names never reaches -// the document. const createCycleCustomSymptomRequest = createCustomSymptomSchema.meta({ id: "CreateCycleCustomSymptomRequest", description: @@ -682,7 +682,7 @@ export const cyclePaths: NonNullable = { parameters: [idempotencyKeyParameter], requestBody: { required: true, - content: { "application/json": { schema: cycleDayLogInputSchema } }, + content: { "application/json": { schema: cycleDayLogInput } }, }, responses: { ...idempotentWrite(), @@ -719,7 +719,7 @@ export const cyclePaths: NonNullable = { requestParams: { path: z.object({ id: z.string() }) }, requestBody: { required: true, - content: { "application/json": { schema: cycleDayLogPatchSchema } }, + content: { "application/json": { schema: cycleDayLogPatchRequest } }, }, responses: { "200": { @@ -764,7 +764,7 @@ export const cyclePaths: NonNullable = { parameters: [idempotencyKeyParameter], requestBody: { required: true, - content: { "application/json": { schema: cycleBulkSchema } }, + content: { "application/json": { schema: cycleDayLogBulkRequest } }, }, responses: { ...idempotentWrite(), @@ -790,7 +790,7 @@ export const cyclePaths: NonNullable = { parameters: [idempotencyKeyParameter], requestBody: { required: true, - content: { "application/json": { schema: cyclePeriodSchema } }, + content: { "application/json": { schema: cyclePeriodRequest } }, }, responses: { ...idempotentWrite(), @@ -979,7 +979,7 @@ export const cyclePaths: NonNullable = { "Deep-merges the supplied fields. `enabled` flips `cycleTrackingEnabled`. Returns the merged CycleProfileDTO. NOT gated.", requestBody: { required: true, - content: { "application/json": { schema: cyclePrefsSchema } }, + content: { "application/json": { schema: cyclePrefsRequest } }, }, responses: { "200": { diff --git a/src/lib/openapi/routes/encounters.ts b/src/lib/openapi/routes/encounters.ts index 07df2104f..f1e3f4a64 100644 --- a/src/lib/openapi/routes/encounters.ts +++ b/src/lib/openapi/routes/encounters.ts @@ -41,49 +41,53 @@ import { stdResponses, } from "./shared"; -encounterCreateSchema.meta({ +// `.meta()` CLONES in Zod 4 rather than annotating in place, so the returned +// schema has to be captured and referenced. A bare `schema.meta({...})` +// statement registers nothing and the component id it names never reaches the +// emitted document. +const createEncounterRequest = encounterCreateSchema.meta({ id: "CreateEncounterRequest", description: "File a visit, or book one. `occurredAt` is the only required field — a visit saves with a date and nothing else. A future instant with `status: PLANNED` books an appointment and mints exactly one reminder for it. `reason` and `outcome` are encrypted at rest. The three id arrays pre-link documents, lab results and condition episodes; an id naming nothing the caller owns is dropped rather than refused, so a link never blocks a save.", }); -encounterUpdateSchema.meta({ +const updateEncounterRequest = encounterUpdateSchema.meta({ id: "UpdateEncounterRequest", description: "Partial edit of a visit; an omitted key leaves the column untouched, and a body naming nothing is a 422. Moving `occurredAt` re-anchors the appointment reminder the visit already owns and never mints a second. Moving `status` to DONE closes the checkup named by `reminderId`, on the transition only. CANCELLED and NO_SHOW stop the reminder without deleting the row. A present id array replaces that link family, empty array included.", }); -encounterListQuerySchema.meta({ +const listEncountersQuery = encounterListQuerySchema.meta({ id: "ListEncountersQuery", description: "Window and filters for the visit list: `from` / `to` (ISO-8601 instants), `status`, `practitionerId`, `episodeId` (only visits filed against that condition episode — the condition side of the link, and the only direction it is offered in), and `limit` (1–200, default 100).", }); -encounterSuggestQuerySchema.meta({ +const suggestEncounterQuery = encounterSuggestQuerySchema.meta({ id: "SuggestEncounterQuery", description: "The date to resolve around, as an ISO-8601 instant with an offset. A document uses `reportDate ?? documentDate`; a lab panel uses `takenAt`.", }); -encounterLinkSchema.meta({ +const encounterLinkRequest = encounterLinkSchema.meta({ id: "EncounterLinkRequest", description: "Targets to file against, or unfile from, a visit. Idempotent in both directions and capped at 100 ids. Ids naming nothing the caller owns come back in `unknown` rather than failing the request.", }); -practitionerCreateSchema.meta({ +const createPractitionerRequest = practitionerCreateSchema.meta({ id: "CreatePractitionerRequest", description: "Add a doctor or practice to the caller's own address book. `name` is the only required field and is stored as queryable plaintext so the picker can search and sort it; `note` is encrypted at rest. `specialty` is free text rather than an enum. Nothing is unique across accounts.", }); -practitionerUpdateSchema.meta({ +const updatePractitionerRequest = practitionerUpdateSchema.meta({ id: "UpdatePractitionerRequest", description: "Partial edit; an omitted key leaves the column untouched, an explicit null clears it, and a body naming nothing is a 422.", }); -practitionerListQuerySchema.meta({ +const listPractitionersQuery = practitionerListQuerySchema.meta({ id: "ListPractitionersQuery", description: "Query params for the address book: `q` (case-insensitive substring of the name OR the practice — a person looks one up by whichever of the two they remember) and `limit` (1–200, default 100). Name-ordered.", @@ -233,7 +237,7 @@ export const encounterPaths: NonNullable = { summary: "List visits", description: "Returns the caller's live visits, split into `upcoming` and `past`. Bounded and windowed; soft-deleted visits are excluded.", - requestParams: { query: encounterListQuerySchema }, + requestParams: { query: listEncountersQuery }, responses: { ...recordRefusal(), "200": { @@ -255,7 +259,7 @@ export const encounterPaths: NonNullable = { "Creates one visit. A future instant with `status: PLANNED` books an appointment and mints its reminder; a past instant with `status: DONE` and a `reminderId` closes that checkup. Claiming both is a 422 — a visit that has not happened cannot have closed anything. Honours `Idempotency-Key`. Audits as `encounter.visit.create`.", requestBody: { required: true, - content: { "application/json": { schema: encounterCreateSchema } }, + content: { "application/json": { schema: createEncounterRequest } }, }, responses: { ...idempotentWrite(), @@ -278,7 +282,7 @@ export const encounterPaths: NonNullable = { summary: "Which visit does a record dated around this day belong to?", description: "Resolves the caller's live DONE or PLANNED visits within ±7 days of `anchor` into a verdict, so the three capture moments that ask the question — a document arriving, a lab panel committed from an extraction, a lab panel typed by hand — all read the same answer. `one` means pre-select it visibly with one undo; `many` means offer a picker with NOTHING pre-selected, because pre-selecting the first of two teaches a person to stop reading suggestions; `none` means offer nothing at all. Cancelled visits and no-shows are never candidates: neither happened, so neither produced the record being filed. No AI provider is involved and none is required.", - requestParams: { query: encounterSuggestQuerySchema }, + requestParams: { query: suggestEncounterQuery }, responses: { ...recordRefusal(), "200": { @@ -325,7 +329,7 @@ export const encounterPaths: NonNullable = { requestParams: idPath, requestBody: { required: true, - content: { "application/json": { schema: encounterUpdateSchema } }, + content: { "application/json": { schema: updateEncounterRequest } }, }, responses: { ...recordRefusal(), @@ -396,7 +400,7 @@ export const encounterPaths: NonNullable = { requestParams: idPath, requestBody: { required: true, - content: { "application/json": { schema: encounterLinkSchema } }, + content: { "application/json": { schema: encounterLinkRequest } }, }, responses: { "200": { @@ -425,7 +429,7 @@ export const encounterPaths: NonNullable = { requestParams: idPath, requestBody: { required: true, - content: { "application/json": { schema: encounterLinkSchema } }, + content: { "application/json": { schema: encounterLinkRequest } }, }, responses: { "200": { @@ -453,7 +457,7 @@ export const encounterPaths: NonNullable = { summary: "List practitioners", description: "Returns the caller's address book, name-ordered, with an optional case-insensitive name search.", - requestParams: { query: practitionerListQuerySchema }, + requestParams: { query: listPractitionersQuery }, responses: { ...recordRefusal(), "200": { @@ -478,7 +482,7 @@ export const encounterPaths: NonNullable = { "Creates one address-book entry. Honours `Idempotency-Key`. Audits as `practitioner.contact.create`.", requestBody: { required: true, - content: { "application/json": { schema: practitionerCreateSchema } }, + content: { "application/json": { schema: createPractitionerRequest } }, }, responses: { ...idempotentWrite(), @@ -524,7 +528,7 @@ export const encounterPaths: NonNullable = { requestParams: idPath, requestBody: { required: true, - content: { "application/json": { schema: practitionerUpdateSchema } }, + content: { "application/json": { schema: updatePractitionerRequest } }, }, responses: { ...recordRefusal(), diff --git a/src/lib/openapi/routes/health-record.ts b/src/lib/openapi/routes/health-record.ts index e37742ada..995101bb0 100644 --- a/src/lib/openapi/routes/health-record.ts +++ b/src/lib/openapi/routes/health-record.ts @@ -19,6 +19,32 @@ import { stdResponses, } from "./shared"; +// The two request bodies this module publishes. `.meta()` CLONES in Zod 4 +// rather than annotating in place, so the returned schema has to be captured +// and referenced: a bare `schema.meta({...})` statement registers nothing and +// the component id it names never reaches the emitted document. Both +// annotations used to sit in `./coach`, which imported the schemas for no +// other reason and never referenced them; the two operations that DO send +// these bodies are both here. +// +// v1.7.0 — health-record export selection. Strict shape: unknown keys +// (including any attempt to smuggle a userId) 422 via returnAllZodIssues. +// v1.11.0 — clinician share-link create payload. Strict; no `userId` field +// (the owner is always narrowed from the session/Bearer). `expiresAt` is +// required and capped at SHARE_LINK_MAX_DAYS; the scope columns are frozen +// write-once at creation. +const createShareLinkRequest = createShareLinkSchema.meta({ + id: "CreateShareLinkRequest", + description: + "v1.11.0 — owner request to mint a clinician share link to their own health record. `expiresAt` is required (absolute ISO instant) and capped at 90 days. `rangeStart`/`rangeEnd` freeze the reporting window (rangeEnd null = rolling). `selection` freezes which record leaves the link may serve, and omitting it means an empty scope rather than a default one. `documentIds` freezes the documents the link carries; `documentOnly` mints a share that serves those documents and no record scope at all. A share link serves the rendered page and the documents frozen onto it — there is no FHIR or other machine-readable face behind a share token. Strict: unknown keys 422.", +}); + +const healthRecordExportRequest = exportSelectionSchema.meta({ + id: "HealthRecordExportRequest", + description: + "v1.7.0 — health-record / doctor-handover export selection. `format` picks PDF, FHIR R4 document Bundle, or a combined zip package. `selection` is REQUIRED and carries the leaf inclusion list — membership is inclusion, absence is exclusion, and there is no server-side default; the retired grouped `sections` shape is a 422. No `userId` field — the user is always narrowed from the session/Bearer. The route is strict: unknown keys 422.", +}); + // v1.11.0 — clinician share-link owner-facing summary (never the raw token). const shareLinkSummary = z.object({ id: z.string(), @@ -91,7 +117,7 @@ export const healthRecordPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: exportSelectionSchema }, + "application/json": { schema: healthRecordExportRequest }, }, }, responses: { @@ -124,7 +150,7 @@ export const healthRecordPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: createShareLinkSchema }, + "application/json": { schema: createShareLinkRequest }, }, }, responses: { diff --git a/src/lib/openapi/routes/import.ts b/src/lib/openapi/routes/import.ts index d64afc87e..04cc258eb 100644 --- a/src/lib/openapi/routes/import.ts +++ b/src/lib/openapi/routes/import.ts @@ -14,12 +14,14 @@ import type { ZodOpenApiObject } from "zod-openapi"; import { z } from "zod/v4"; -import { - glucoseContextEnum, - measurementTypeEnum, -} from "@/lib/validations/measurement"; +import { glucoseContextEnum } from "@/lib/validations/measurement"; import { moodLevelEnum } from "@/lib/validations/mood"; -import { dataEnvelope, errorEnvelope, stdResponses } from "./shared"; +import { + dataEnvelope, + errorEnvelope, + measurementTypeEnum, + stdResponses, +} from "./shared"; const csvImportRowResult = z .object({ diff --git a/src/lib/openapi/routes/index.ts b/src/lib/openapi/routes/index.ts index 33a921c43..6f28752d5 100644 --- a/src/lib/openapi/routes/index.ts +++ b/src/lib/openapi/routes/index.ts @@ -82,7 +82,7 @@ import { recordSettingsPaths } from "./record-settings"; import { settingsPaths } from "./settings"; import { syncPaths } from "./sync"; import { workoutPaths } from "./workouts"; -import { coachPrefsSchema } from "@/lib/validations/coach-prefs"; +import { coachPrefsSchema } from "./shared"; export const openApiPaths: NonNullable = { ...cyclePaths, diff --git a/src/lib/openapi/routes/insights/schemas.ts b/src/lib/openapi/routes/insights/schemas.ts index 5d7dabf8e..c7d9dd371 100644 --- a/src/lib/openapi/routes/insights/schemas.ts +++ b/src/lib/openapi/routes/insights/schemas.ts @@ -6,10 +6,7 @@ * runtime request parsing, so the wire contract stays single-source. */ import { z } from "zod/v4"; -import { - measurementTypeEnum, - measurementSourceEnum, -} from "@/lib/validations/measurement"; +import { measurementSourceEnum, measurementTypeEnum } from "../shared"; import { METRIC_STATUS_IDS } from "@/lib/insights/metric-status-registry"; import { DERIVED_METRIC_IDS, diff --git a/src/lib/openapi/routes/measurements.ts b/src/lib/openapi/routes/measurements.ts index 36877fb65..6d9ca690c 100644 --- a/src/lib/openapi/routes/measurements.ts +++ b/src/lib/openapi/routes/measurements.ts @@ -7,21 +7,19 @@ */ import { z } from "zod/v4"; import type { ZodOpenApiObject } from "zod-openapi"; -import { - createMeasurementSchema, - listMeasurementsSchema, - measurementTypeEnum, - updateMeasurementSchema, - measurementSourceEnum, -} from "@/lib/validations/measurement"; +import { updateMeasurementSchema } from "@/lib/validations/measurement"; import { seriesBatchQuerySchema } from "@/lib/validations/series-batch"; import { deviceTypeEnum } from "@/lib/validations/source-priority"; import { MODULE_DISABLED_DESCRIPTION, + createMeasurementSchema, dataEnvelope, errorEnvelope, idempotencyKeyParameter, idempotentWrite, + listMeasurementsSchema, + measurementSourceEnum, + measurementTypeEnum, recordRefusal, stdResponses, } from "./shared"; diff --git a/src/lib/openapi/routes/ocr.ts b/src/lib/openapi/routes/ocr.ts index 446dfd64f..3be1612d6 100644 --- a/src/lib/openapi/routes/ocr.ts +++ b/src/lib/openapi/routes/ocr.ts @@ -24,7 +24,11 @@ import { stdResponses, } from "./shared"; -ocrCommitSchema.meta({ +// `.meta()` CLONES in Zod 4 rather than annotating in place, so the returned +// schema has to be captured and referenced. A bare `schema.meta({...})` +// statement registers nothing and the component id it names never reaches the +// emitted document. +const ocrCommitRequest = ocrCommitSchema.meta({ id: "OcrCommitRequest", description: "The rows a human confirmed on the Lab-OCR review screen. Each row is EITHER numeric (`value` + `unit`, optional reference bounds) OR qualitative (`valueText`) — exactly one. `analyte` drives a resolve-or-mint of the user-scoped biomarker; `takenAt` is a backdatable ISO instant. No `userId` field — it is narrowed from the session. 1..100 rows. The route skips a row that duplicates a live reading (same analyte + day + value). The optional `encounterId` files the panel against a visit the caller owns — ONE link per written result row, so a marker re-run on a different day belongs to its own visit; always optional, and a link that could not be made never fails the commit.", @@ -209,7 +213,7 @@ export const ocrPaths: NonNullable = { 'Writes ONLY the rows the human confirmed on the review screen. Each row resolves-or-mints a user-scoped biomarker and creates a `LabResult` with `source: "OCR"`; a row that duplicates a live reading is skipped. Idempotent (Idempotency-Key). Audits as `labs.ocr.commit`. `userId` is narrowed from the session, never a body field.', requestBody: { required: true, - content: { "application/json": { schema: ocrCommitSchema } }, + content: { "application/json": { schema: ocrCommitRequest } }, }, responses: { ...idempotentWrite(), diff --git a/src/lib/openapi/routes/profile.ts b/src/lib/openapi/routes/profile.ts index f0c2739a4..f3e50f971 100644 --- a/src/lib/openapi/routes/profile.ts +++ b/src/lib/openapi/routes/profile.ts @@ -7,7 +7,6 @@ */ import { z } from "zod/v4"; import type { ZodOpenApiObject } from "zod-openapi"; -import { coachPrefsSchema } from "@/lib/validations/coach-prefs"; import { notificationPrefsSchema } from "@/lib/validations/notification-prefs"; import { sourcePrioritySchema } from "@/lib/validations/source-priority"; import { modulePrefsPatchSchema } from "@/lib/validations/modules"; @@ -38,6 +37,7 @@ import { healthProfileFactKindSchema, } from "@/lib/validations/health-profile-facts"; import { + coachPrefsSchema, dataEnvelope, errorEnvelope, stdResponses, diff --git a/src/lib/openapi/routes/shared.ts b/src/lib/openapi/routes/shared.ts index f9aa9471c..4ce94c266 100644 --- a/src/lib/openapi/routes/shared.ts +++ b/src/lib/openapi/routes/shared.ts @@ -7,14 +7,14 @@ */ import { z } from "zod/v4"; import { - createMeasurementSchema, - listMeasurementsSchema, - measurementTypeEnum, - measurementSourceEnum, + createMeasurementSchema as createMeasurementSchemaBase, + listMeasurementsSchema as listMeasurementsSchemaBase, + measurementTypeEnum as measurementTypeEnumBase, + measurementSourceEnum as measurementSourceEnumBase, } from "@/lib/validations/measurement"; -import { loginPasswordSchema } from "@/lib/validations/auth"; -import { coachPrefsSchema } from "@/lib/validations/coach-prefs"; -import { createBatchWorkoutSchema } from "@/lib/validations/workout"; +import { loginPasswordSchema as loginPasswordSchemaBase } from "@/lib/validations/auth"; +import { coachPrefsSchema as coachPrefsSchemaBase } from "@/lib/validations/coach-prefs"; +import { createBatchWorkoutSchema as createBatchWorkoutSchemaBase } from "@/lib/validations/workout"; /** * Common envelopes — every HealthLog API response wraps payload in @@ -48,44 +48,53 @@ export function dataEnvelope(payload: T, id: string) { } // ── Schemas — annotated for spec emission ──────────────────────────── +// +// `.meta()` CLONES in Zod 4 rather than annotating in place, so the returned +// schema has to be captured and referenced: a bare `schema.meta({...})` +// statement registers nothing and the component id it names never reaches the +// emitted document. These seven are shared across route modules, so the +// annotated clone is exported from here under the name the use sites already +// spell and the base import is aliased. A module that wants the published +// form imports it from `./shared`; importing the raw schema from +// `@/lib/validations/*` instead is what inlined these anonymously. -measurementTypeEnum.meta({ +export const measurementTypeEnum = measurementTypeEnumBase.meta({ id: "MeasurementType", description: "DB-stored measurement category. v1.4.23 added 7 Apple Health values (HRV, resting HR, active energy, flights, walking/running distance, VO2 max, body temperature).", }); -measurementSourceEnum.meta({ +export const measurementSourceEnum = measurementSourceEnumBase.meta({ id: "MeasurementSource", description: "Origin of the measurement. v1.4.23 added APPLE_HEALTH for the iOS HealthKit batch ingest path.", }); -loginPasswordSchema.meta({ +export const loginPasswordSchema = loginPasswordSchemaBase.meta({ id: "LoginPasswordRequest", description: "Email-or-username login. The native-client flow returns a paired access + refresh token when X-Client-Type: native or the iOS UA prefix is present.", }); -createMeasurementSchema.meta({ +export const createMeasurementSchema = createMeasurementSchemaBase.meta({ id: "CreateMeasurementRequest", description: "Single-measurement ingest body. Plausibility-range guard runs server-side; out-of-range values fail 422. `glucoseContext` stays REQUIRED on a `BLOOD_GLUCOSE` row here: this is the hand-entry surface, where the person taking the reading knows whether it was fasting or after a meal. The bulk ingest paths (CSV import, JSON import, device sync) accept a contextless reading, because a sensor export classifies nothing per sample.", }); -listMeasurementsSchema.meta({ +export const listMeasurementsSchema = listMeasurementsSchemaBase.meta({ id: "ListMeasurementsQuery", description: "Query params for the measurements list endpoint. `limit` capped at 500.", }); -coachPrefsSchema.meta({ +export const coachPrefsSchema = coachPrefsSchemaBase.meta({ id: "CoachPrefs", description: "Per-user Coach prompt-tuning preferences (v1.4.23 H4). All fields default to the legacy v1.4.22 behaviour when omitted.", }); -createBatchWorkoutSchema.meta({ +export const createBatchWorkoutSchema = createBatchWorkoutSchemaBase.meta({ id: "CreateBatchWorkoutRequest", description: "Typed workout batch ingest. Each entry is an HKWorkout-aligned record with an optional nested GeoJSON LineString route AND an optional route-independent per-workout heart-rate series (`samples`: `[{ t, hr?, speedMs?, power?, cadence? }]`, up to 30 000 points). The `samples` series is the strain-engine input for indoor workouts that have no GPS route. Up to 100 workouts per call; nested route geometry capped at 20 000 points. Withings server-to-server callers pass source: WITHINGS and ship no route (Withings reports aggregates only).", diff --git a/src/lib/openapi/routes/workouts.ts b/src/lib/openapi/routes/workouts.ts index 99e80aea4..ceec2dcbb 100644 --- a/src/lib/openapi/routes/workouts.ts +++ b/src/lib/openapi/routes/workouts.ts @@ -7,14 +7,14 @@ */ import { z } from "zod/v4"; import type { ZodOpenApiObject } from "zod-openapi"; -import { measurementSourceEnum } from "@/lib/validations/measurement"; -import { createBatchWorkoutSchema } from "@/lib/validations/workout"; import { MODULE_DISABLED_DESCRIPTION, + createBatchWorkoutSchema, dataEnvelope, errorEnvelope, idempotencyKeyParameter, idempotentWrite, + measurementSourceEnum, moduleDisabledResponse, recordRefusal, stdResponses,