diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 3fb2e9127..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. @@ -6554,96 +5606,7 @@ paths: content: application/json: schema: - type: object - properties: - clear: - type: boolean - 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 - biomarkerId: - type: string - minLength: 1 - maxLength: 64 - primary: - type: boolean + $ref: "#/components/schemas/SetMedicationEfficacyTargetRequest" responses: "200": description: Override set or cleared. @@ -9115,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. @@ -9903,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. @@ -10157,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. @@ -10243,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`)." @@ -10986,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: @@ -11307,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 @@ -12265,22 +10911,7 @@ paths: content: application/json: schema: - type: object - properties: - label: - type: string - minLength: 1 - maxLength: 40 - icon: - anyOf: - - type: string - - type: "null" - categoryKey: - type: string - minLength: 1 - maxLength: 80 - required: - - label + $ref: "#/components/schemas/CreateMoodTagRequest" responses: "201": description: Custom tag created. @@ -12309,22 +10940,7 @@ paths: content: application/json: schema: - type: object - properties: - label: - type: string - minLength: 1 - maxLength: 40 - icon: - anyOf: - - type: string - - type: "null" - isActive: - type: boolean - categoryKey: - type: string - minLength: 1 - maxLength: 80 + $ref: "#/components/schemas/UpdateMoodTagRequest" responses: "200": description: Custom tag updated. @@ -12389,12 +11005,7 @@ paths: content: application/json: schema: - type: object - properties: - hidden: - type: boolean - required: - - hidden + $ref: "#/components/schemas/HideMoodTagRequest" responses: "200": description: Visibility updated. @@ -12424,18 +11035,7 @@ paths: content: application/json: schema: - type: object - properties: - label: - type: string - minLength: 1 - maxLength: 40 - icon: - anyOf: - - type: string - - type: "null" - required: - - label + $ref: "#/components/schemas/CreateMoodTagGroupRequest" responses: "201": description: Custom group created. @@ -12464,18 +11064,7 @@ paths: content: application/json: schema: - type: object - properties: - label: - type: string - minLength: 1 - maxLength: 40 - icon: - anyOf: - - type: string - - type: "null" - isActive: - type: boolean + $ref: "#/components/schemas/UpdateMoodTagGroupRequest" responses: "200": description: Custom group updated. @@ -13401,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. @@ -13451,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. @@ -14144,68 +12708,7 @@ paths: content: application/json: schema: - type: object - properties: - biomarkerId: - type: string - minLength: 1 - maxLength: 64 - panel: - anyOf: - - type: string - minLength: 1 - maxLength: 120 - - type: string - const: "" - analyte: - type: string - minLength: 1 - maxLength: 120 - value: - type: number - valueText: - type: string - minLength: 1 - maxLength: 120 - unit: - type: string - minLength: 1 - maxLength: 40 - referenceLow: - type: number - referenceHigh: - type: number - sourceReferenceLow: - type: number - sourceReferenceHigh: - type: number - sourceReferenceText: - 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))$ - note: - anyOf: - - type: string - maxLength: 2000 - - type: string - const: "" - source: - type: string - enum: - - MANUAL - - OCR - encounterId: - type: string - minLength: 1 - maxLength: 40 - required: - - takenAt + $ref: "#/components/schemas/CreateLabResultRequest" responses: "201": description: Created lab result. @@ -14264,67 +12767,7 @@ paths: content: application/json: schema: - type: object - properties: - panel: - anyOf: - - anyOf: - - type: string - minLength: 1 - maxLength: 120 - - type: string - const: "" - - type: "null" - analyte: - type: string - minLength: 1 - maxLength: 120 - value: - type: number - valueText: - type: string - minLength: 1 - maxLength: 120 - unit: - type: string - minLength: 1 - maxLength: 40 - referenceLow: - anyOf: - - type: number - - type: "null" - referenceHigh: - anyOf: - - type: number - - type: "null" - sourceReferenceLow: - anyOf: - - type: number - - type: "null" - sourceReferenceHigh: - anyOf: - - type: number - - type: "null" - sourceReferenceText: - anyOf: - - anyOf: - - type: string - maxLength: 120 - - type: string - const: "" - - type: "null" - 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))$ - note: - anyOf: - - anyOf: - - type: string - maxLength: 2000 - - type: string - const: "" - - type: "null" + $ref: "#/components/schemas/UpdateLabResultRequest" responses: "200": description: Updated lab result. @@ -14460,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. @@ -14559,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. @@ -14649,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. @@ -14791,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. @@ -14950,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. @@ -15099,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. @@ -15147,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. @@ -15220,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. @@ -15311,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. @@ -15463,81 +13551,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)))$ - antigenSlug: - anyOf: - - type: string - minLength: 1 - maxLength: 64 - - type: "null" - vaccineName: - anyOf: - - type: string - minLength: 1 - maxLength: 200 - - type: "null" - doseNumber: - anyOf: - - type: integer - minimum: 1 - maximum: 20 - - type: "null" - seriesDoses: - anyOf: - - type: integer - minimum: 1 - maximum: 10 - - type: "null" - lotNumber: - anyOf: - - type: string - minLength: 1 - maxLength: 64 - - type: "null" - site: - anyOf: - - type: string - enum: - - LEFT_ARM - - RIGHT_ARM - - LEFT_THIGH - - RIGHT_THIGH - - ORAL - - NASAL - - OTHER - - type: "null" - practitionerId: - anyOf: - - type: string - minLength: 1 - maxLength: 40 - - type: "null" - encounterId: - anyOf: - - type: string - minLength: 1 - maxLength: 40 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - documentIds: - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - required: - - occurredAt - additionalProperties: false + $ref: "#/components/schemas/CreateVaccinationRequest" responses: "201": description: Dose logged. @@ -15596,79 +13610,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)))$ - antigenSlug: - anyOf: - - type: string - minLength: 1 - maxLength: 64 - - type: "null" - vaccineName: - anyOf: - - type: string - minLength: 1 - maxLength: 200 - - type: "null" - doseNumber: - anyOf: - - type: integer - minimum: 1 - maximum: 20 - - type: "null" - seriesDoses: - anyOf: - - type: integer - minimum: 1 - maximum: 10 - - type: "null" - lotNumber: - anyOf: - - type: string - minLength: 1 - maxLength: 64 - - type: "null" - site: - anyOf: - - type: string - enum: - - LEFT_ARM - - RIGHT_ARM - - LEFT_THIGH - - RIGHT_THIGH - - ORAL - - NASAL - - OTHER - - type: "null" - practitionerId: - anyOf: - - type: string - minLength: 1 - maxLength: 40 - - type: "null" - encounterId: - anyOf: - - type: string - minLength: 1 - maxLength: 40 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - documentIds: - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - additionalProperties: false + $ref: "#/components/schemas/UpdateVaccinationRequest" responses: "200": description: Dose updated. @@ -15753,24 +13695,7 @@ paths: content: application/json: schema: - type: object - properties: - intervalMonths: - type: integer - minimum: 1 - maximum: 600 - label: - type: string - minLength: 1 - maxLength: 120 - notifyHour: - type: integer - minimum: 0 - maximum: 23 - required: - - intervalMonths - - label - additionalProperties: false + $ref: "#/components/schemas/VaccinationBoosterRequest" responses: "200": description: Existing booster reminder re-anchored. @@ -15835,24 +13760,7 @@ paths: content: application/json: schema: - type: object - properties: - targetKind: - type: string - enum: - - document - targetIds: - minItems: 1 - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - required: - - targetKind - - targetIds - additionalProperties: false + $ref: "#/components/schemas/VaccinationLinkRequest" responses: "200": description: The dose's linked pages after the change. @@ -15881,24 +13789,7 @@ paths: content: application/json: schema: - type: object - properties: - targetKind: - type: string - enum: - - document - targetIds: - minItems: 1 - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - required: - - targetKind - - targetIds - additionalProperties: false + $ref: "#/components/schemas/VaccinationLinkRequest" responses: "200": description: The dose's linked pages after the change. @@ -15957,54 +13848,7 @@ paths: content: application/json: schema: - type: object - properties: - label: - type: string - minLength: 1 - maxLength: 120 - type: - type: string - enum: - - INFECTION - - ALLERGY - - INJURY - - MENTAL_HEALTH - - AUTOIMMUNE - - CHRONIC - - OTHER - lifecycle: - default: ACUTE - type: string - enum: - - ACUTE - - CHRONIC_ONGOING - - RECURRING - - FLARE - onsetAt: - type: string - format: date-time - pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ - resolvedAt: - 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" - parentConditionId: - anyOf: - - type: string - minLength: 1 - maxLength: 40 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - required: - - label - - type + $ref: "#/components/schemas/CreateIllnessEpisodeRequest" responses: "201": description: Episode created. @@ -16063,51 +13907,7 @@ paths: content: application/json: schema: - type: object - properties: - label: - type: string - minLength: 1 - maxLength: 120 - type: - type: string - enum: - - INFECTION - - ALLERGY - - INJURY - - MENTAL_HEALTH - - AUTOIMMUNE - - CHRONIC - - OTHER - lifecycle: - type: string - enum: - - ACUTE - - CHRONIC_ONGOING - - RECURRING - - FLARE - onsetAt: - type: string - format: date-time - pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ - resolvedAt: - 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" - parentConditionId: - anyOf: - - type: string - minLength: 1 - maxLength: 40 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - additionalProperties: false + $ref: "#/components/schemas/UpdateIllnessEpisodeRequest" responses: "200": description: Episode updated. @@ -16192,12 +13992,7 @@ paths: content: application/json: schema: - type: object - properties: - resolvedAt: - type: string - format: date-time - pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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/ResolveIllnessEpisodeRequest" responses: "200": description: Episode resolved. @@ -16279,50 +14074,7 @@ paths: content: application/json: schema: - type: object - properties: - date: - type: string - pattern: ^\d{4}-\d{2}-\d{2}$ - functionalImpact: - anyOf: - - type: integer - minimum: 0 - maximum: 3 - - type: "null" - feverC: - anyOf: - - type: number - minimum: 30 - maximum: 45 - - type: "null" - symptoms: - maxItems: 40 - type: array - items: - type: object - properties: - key: - type: string - minLength: 1 - maxLength: 80 - severity: - type: integer - minimum: 1 - maximum: 3 - required: - - key - note: - anyOf: - - type: string - maxLength: 2000 - - 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)))$ - required: - - date + $ref: "#/components/schemas/UpsertIllnessDayLogRequest" responses: "200": description: Existing day-log updated. @@ -16455,50 +14207,7 @@ paths: content: application/json: schema: - type: object - properties: - instrument: - type: string - enum: - - PHQ9 - - GAD7 - - WHO5 - - SCI - items: - minItems: 1 - maxItems: 12 - type: array - items: - type: integer - minimum: 0 - maximum: 5 - functionalDifficulty: - type: integer - minimum: 0 - maximum: 3 - 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))$ - tz: - type: string - maxLength: 64 - locale: - type: string - maxLength: 16 - source: - default: WEB - type: string - enum: - - WEB - - IOS - externalId: - type: string - minLength: 1 - maxLength: 120 - required: - - instrument - - items + $ref: "#/components/schemas/CreateMentalHealthAssessmentRequest" responses: "201": description: Assessment recorded. @@ -16583,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. @@ -16694,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. @@ -16817,44 +14424,7 @@ paths: content: application/json: schema: - type: object - properties: - relationship: - type: string - enum: - - MOTHER - - FATHER - - SISTER - - BROTHER - - DAUGHTER - - SON - - GRANDMOTHER_MATERNAL - - GRANDFATHER_MATERNAL - - GRANDMOTHER_PATERNAL - - GRANDFATHER_PATERNAL - - AUNT - - UNCLE - - COUSIN - - HALF_SIBLING - - OTHER - condition: - type: string - minLength: 1 - maxLength: 160 - ageAtOnset: - anyOf: - - type: integer - minimum: 0 - maximum: 120 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - required: - - relationship - - condition + $ref: "#/components/schemas/CreateFamilyHistoryRequest" responses: "201": description: Entry created. @@ -16912,42 +14482,7 @@ paths: content: application/json: schema: - type: object - properties: - relationship: - type: string - enum: - - MOTHER - - FATHER - - SISTER - - BROTHER - - DAUGHTER - - SON - - GRANDMOTHER_MATERNAL - - GRANDFATHER_MATERNAL - - GRANDMOTHER_PATERNAL - - GRANDFATHER_PATERNAL - - AUNT - - UNCLE - - COUSIN - - HALF_SIBLING - - OTHER - condition: - type: string - minLength: 1 - maxLength: 160 - ageAtOnset: - anyOf: - - type: integer - minimum: 0 - maximum: 120 - - type: "null" - note: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - additionalProperties: false + $ref: "#/components/schemas/UpdateFamilyHistoryRequest" responses: "200": description: Entry updated. @@ -17245,49 +14780,7 @@ paths: content: application/json: schema: - type: object - properties: - ids: - minItems: 1 - maxItems: 100 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - action: - type: string - enum: - - setKind - - linkEpisode - - unlinkEpisode - - linkEncounter - - unlinkEncounter - - delete - - restore - kind: - type: string - enum: - - DOCTOR_REPORT - - DISCHARGE_LETTER - - LAB_RESULT - - IMAGING - - PRESCRIPTION - - REFERRAL - - INSURANCE - - VACCINATION - - OTHER - episodeId: - type: string - minLength: 1 - maxLength: 40 - encounterId: - type: string - minLength: 1 - maxLength: 40 - required: - - ids - - action + $ref: "#/components/schemas/DocumentBulkRequest" responses: "200": description: Per-id outcomes. @@ -17338,44 +14831,7 @@ paths: content: application/json: schema: - type: object - properties: - title: - anyOf: - - type: string - maxLength: 200 - - type: "null" - kind: - type: string - enum: - - DOCTOR_REPORT - - DISCHARGE_LETTER - - LAB_RESULT - - IMAGING - - PRESCRIPTION - - REFERRAL - - INSURANCE - - VACCINATION - - OTHER - documentDate: - anyOf: - - type: string - pattern: ^\d{4}-\d{2}-\d{2}$ - - type: "null" - episodeIds: - maxItems: 20 - type: array - items: - type: string - minLength: 1 - maxLength: 40 - encounterIds: - maxItems: 20 - type: array - items: - type: string - minLength: 1 - maxLength: 40 + $ref: "#/components/schemas/DocumentUpdateRequest" responses: "200": description: The updated document. @@ -17871,146 +15327,7 @@ paths: content: application/json: schema: - oneOf: - - type: object - properties: - factType: - type: string - const: CONDITION - label: - type: string - minLength: 1 - maxLength: 300 - code: - anyOf: - - type: string - maxLength: 64 - - type: "null" - codeSystem: - anyOf: - - type: string - enum: - - SNOMED - - ICD10 - - LOINC - - RXNORM - - ATC - - type: "null" - clinicalStatus: - anyOf: - - type: string - maxLength: 64 - - type: "null" - verificationStatus: - anyOf: - - type: string - maxLength: 64 - - type: "null" - onsetDate: - anyOf: - - type: string - pattern: ^\d{4}-\d{2}-\d{2}$ - - type: "null" - required: - - factType - - label - - type: object - properties: - factType: - type: string - const: OBSERVATION - label: - type: string - minLength: 1 - maxLength: 300 - code: - anyOf: - - type: string - maxLength: 64 - - type: "null" - codeSystem: - anyOf: - - type: string - enum: - - SNOMED - - ICD10 - - LOINC - - RXNORM - - ATC - - type: "null" - value: - anyOf: - - type: number - - type: "null" - valueText: - anyOf: - - type: string - maxLength: 300 - - type: "null" - unit: - anyOf: - - type: string - maxLength: 80 - - type: "null" - referenceLow: - anyOf: - - type: number - - type: "null" - referenceHigh: - anyOf: - - type: number - - type: "null" - referenceText: - anyOf: - - type: string - maxLength: 120 - - type: "null" - effectiveDate: - anyOf: - - type: string - pattern: ^\d{4}-\d{2}-\d{2}$ - - type: "null" - required: - - factType - - label - - type: object - properties: - factType: - type: string - const: MEDICATION_STATEMENT - name: - type: string - minLength: 1 - maxLength: 200 - dose: - anyOf: - - type: string - maxLength: 120 - - type: "null" - rxNormCode: - anyOf: - - type: string - maxLength: 20 - - type: "null" - atcCode: - anyOf: - - type: string - maxLength: 16 - - type: "null" - statusStated: - anyOf: - - type: string - maxLength: 64 - - type: "null" - effectiveDate: - anyOf: - - type: string - pattern: ^\d{4}-\d{2}-\d{2}$ - - type: "null" - required: - - factType - - name - type: object + $ref: "#/components/schemas/InboundFactEditRequest" responses: "200": description: The updated fact. @@ -18041,29 +15358,7 @@ paths: content: application/json: schema: - type: object - properties: - decisions: - minItems: 1 - maxItems: 120 - type: array - items: - type: object - properties: - factId: - type: string - minLength: 1 - maxLength: 40 - action: - type: string - enum: - - approve - - reject - required: - - factId - - action - required: - - decisions + $ref: "#/components/schemas/InboundConfirmRequest" responses: "200": description: Per-decision outcome. @@ -18106,33 +15401,7 @@ paths: content: application/json: schema: - type: object - properties: - name: - type: string - minLength: 1 - maxLength: 120 - unit: - type: string - minLength: 1 - maxLength: 40 - targetLow: - type: number - targetHigh: - type: number - decimals: - type: integer - minimum: 0 - maximum: 6 - description: - type: string - maxLength: 2000 - correlationEnabled: - default: false - type: boolean - required: - - name - - unit + $ref: "#/components/schemas/CreateCustomMetricRequest" responses: "201": description: Created custom metric. @@ -18195,37 +15464,7 @@ paths: content: application/json: schema: - type: object - properties: - name: - type: string - minLength: 1 - maxLength: 120 - unit: - type: string - minLength: 1 - maxLength: 40 - targetLow: - anyOf: - - type: number - - type: "null" - targetHigh: - anyOf: - - type: number - - type: "null" - decimals: - anyOf: - - type: integer - minimum: 0 - maximum: 6 - - type: "null" - description: - anyOf: - - type: string - maxLength: 2000 - - type: "null" - correlationEnabled: - type: boolean + $ref: "#/components/schemas/UpdateCustomMetricRequest" responses: "200": description: Updated custom metric. @@ -18322,23 +15561,7 @@ paths: content: application/json: schema: - type: object - properties: - 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))$ - note: - anyOf: - - type: string - maxLength: 2000 - - type: string - const: "" - required: - - value - - measuredAt + $ref: "#/components/schemas/CreateCustomMetricEntryRequest" responses: "201": description: Created value. @@ -18374,22 +15597,7 @@ paths: content: application/json: schema: - type: object - properties: - 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))$ - note: - anyOf: - - anyOf: - - type: string - maxLength: 2000 - - type: string - const: "" - - type: "null" + $ref: "#/components/schemas/UpdateCustomMetricEntryRequest" responses: "200": description: Updated value. @@ -22177,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: @@ -22199,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: @@ -22735,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: @@ -22908,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: @@ -23891,6 +21985,101 @@ components: description: Free-text medication description payload. The route runs the text through the Coach provider chain and returns a partial structured payload the wizard merges. Rate-limited 10 requests / 5 minutes / user; budget-gated against the daily Coach token ceiling. + SetMedicationEfficacyTargetRequest: + type: object + properties: + clear: + type: boolean + 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 + biomarkerId: + type: string + minLength: 1 + maxLength: 64 + primary: + type: boolean + description: Set or clear the user's explicit efficacy-target override for a medication. `clear:true` removes the + override so the resolver reverts to the derived (ATC class prefix → name inference) target; otherwise pin + exactly ONE of `measurementType` (a metric series) / `biomarkerId` (a lab analyte). `userId` is never a field — + ownership is narrowed through the medication (and the biomarker for a lab target). CreateScheduleRevisionRequest: type: object properties: @@ -24682,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: @@ -24867,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: @@ -24883,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: @@ -25088,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: @@ -25467,6 +23923,85 @@ components: description: "The day around a mood entry: work, contacts, leisure and one notable event. Every field optional. Sleep, activity, vitals and body symptoms are deliberately ABSENT — they are owned by their own modules and are read from there, never captured a second time here." + CreateMoodTagRequest: + type: object + properties: + label: + type: string + minLength: 1 + maxLength: 40 + icon: + anyOf: + - type: string + - type: "null" + categoryKey: + type: string + minLength: 1 + maxLength: 80 + required: + - label + description: Create a per-user custom mood tag (BINARY only). `label` is encrypted at rest. `icon` must come from the + curated allowlist (unknown name → 422). `categoryKey` picks the home group — any seeded category key or one of + the caller's own `customcat:` group keys; omitted → the seeded `custom` category. Capped at 50 active custom + tags per user (422). + UpdateMoodTagRequest: + type: object + properties: + label: + type: string + minLength: 1 + maxLength: 40 + icon: + anyOf: + - type: string + - type: "null" + isActive: + type: boolean + categoryKey: + type: string + minLength: 1 + maxLength: 80 + description: Partial custom-tag edit; at least one field required. `isActive:false` archives (history intact), + `isActive:true` restores. `categoryKey` moves the tag to another group (a real categoryId move). + HideMoodTagRequest: + type: object + properties: + hidden: + type: boolean + required: + - hidden + description: Hide / show a CATALOGUE tag for the calling user. Custom tags are hidden via their own `isActive` flag on + the custom PATCH instead (this route 400s a `custom:` key). + CreateMoodTagGroupRequest: + type: object + properties: + label: + type: string + minLength: 1 + maxLength: 40 + icon: + anyOf: + - type: string + - type: "null" + required: + - label + description: Create a per-user custom mood-tag group. `label` is encrypted at rest; `icon` must come from the curated + allowlist. Capped at 12 active custom groups per user (422). + UpdateMoodTagGroupRequest: + type: object + properties: + label: + type: string + minLength: 1 + maxLength: 40 + icon: + anyOf: + - type: string + - type: "null" + isActive: + type: boolean + description: Partial custom-group edit; at least one field required. `isActive:false` retires the group without touching + its tags. MoodTagLayoutPutRequest: type: object properties: @@ -26160,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: @@ -26179,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 @@ -26467,6 +24959,143 @@ components: reminder's notifyHour in the profile timezone. Must be at least tomorrow and at most five years out. The regular cadence is untouched: lastSatisfiedAt and the anchor stay where they are, and the reminder resumes its normal interval after the snoozed cycle." + CreateLabResultRequest: + type: object + properties: + biomarkerId: + type: string + minLength: 1 + maxLength: 64 + panel: + anyOf: + - type: string + minLength: 1 + maxLength: 120 + - type: string + const: "" + analyte: + type: string + minLength: 1 + maxLength: 120 + value: + type: number + valueText: + type: string + minLength: 1 + maxLength: 120 + unit: + type: string + minLength: 1 + maxLength: 40 + referenceLow: + type: number + referenceHigh: + type: number + sourceReferenceLow: + type: number + sourceReferenceHigh: + type: number + sourceReferenceText: + 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))$ + note: + anyOf: + - type: string + maxLength: 2000 + - type: string + const: "" + source: + type: string + enum: + - MANUAL + - OCR + encounterId: + type: string + minLength: 1 + maxLength: 40 + required: + - takenAt + description: 'Record a single biomarker reading (HbA1c, LDL, ferritin, TSH, …). A reading is EITHER numeric (`value` + + `unit`, optional reference bounds) OR qualitative (`valueText`, e.g. "negativ" / "positiv") — exactly one, never + both, never neither. `analyte` + `unit` are free-form (a lab prints its own naming); a qualitative reading needs + no unit / bounds. When both numeric bounds are present `referenceLow` must not exceed `referenceHigh`. `takenAt` + is a backdatable ISO instant (no future, ≤ 50 years past). The optional `note` is encrypted at rest. The + optional `source` records provenance: `"OCR"` for an on-device-OCR capture (the raw image never reaches the + server on this path), defaulting to `"MANUAL"` when omitted. The optional `encounterId` files the reading + against a visit the caller owns — always optional, and a link that could not be made never fails the reading.' + UpdateLabResultRequest: + type: object + properties: + panel: + anyOf: + - anyOf: + - type: string + minLength: 1 + maxLength: 120 + - type: string + const: "" + - type: "null" + analyte: + type: string + minLength: 1 + maxLength: 120 + value: + type: number + valueText: + type: string + minLength: 1 + maxLength: 120 + unit: + type: string + minLength: 1 + maxLength: 40 + referenceLow: + anyOf: + - type: number + - type: "null" + referenceHigh: + anyOf: + - type: number + - type: "null" + sourceReferenceLow: + anyOf: + - type: number + - type: "null" + sourceReferenceHigh: + anyOf: + - type: number + - type: "null" + sourceReferenceText: + anyOf: + - anyOf: + - type: string + maxLength: 120 + - type: string + const: "" + - type: "null" + 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))$ + note: + anyOf: + - anyOf: + - type: string + maxLength: 2000 + - type: string + const: "" + - type: "null" + description: Partial edit of a lab result. An omitted key leaves the column untouched; an explicit `null` on `panel` / + `note` / a reference bound clears it. Edit the matching value field for the row's type — `value` for a numeric + row, `valueText` for a qualitative one; a request must not carry both, and a cross-type edit (the wrong field + for the row) is rejected. RestoreLabResultsRequest: type: object properties: @@ -26490,6 +25119,1085 @@ 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: + 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)))$ + antigenSlug: + anyOf: + - type: string + minLength: 1 + maxLength: 64 + - type: "null" + vaccineName: + anyOf: + - type: string + minLength: 1 + maxLength: 200 + - type: "null" + doseNumber: + anyOf: + - type: integer + minimum: 1 + maximum: 20 + - type: "null" + seriesDoses: + anyOf: + - type: integer + minimum: 1 + maximum: 10 + - type: "null" + lotNumber: + anyOf: + - type: string + minLength: 1 + maxLength: 64 + - type: "null" + site: + anyOf: + - type: string + enum: + - LEFT_ARM + - RIGHT_ARM + - LEFT_THIGH + - RIGHT_THIGH + - ORAL + - NASAL + - OTHER + - type: "null" + practitionerId: + anyOf: + - type: string + minLength: 1 + maxLength: 40 + - type: "null" + encounterId: + anyOf: + - type: string + minLength: 1 + maxLength: 40 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + documentIds: + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + required: + - occurredAt + additionalProperties: false + description: "Log one administered dose. `occurredAt` is required and must not be in the future — a vaccination is + recorded after the fact, and a planned booster is a reminder rather than a record. Exactly one identity arm is + required: either `antigenSlug` (a slug the catalogue offers) or `vaccineName` (whatever the person's own record + says, verbatim, which may be a trade name). Everything else is optional, so a decades-old paper entry with + nothing but a date and a name is loggable. `note` is encrypted at rest. `documentIds` pre-links the scanned + pages; an id naming nothing the caller owns is dropped rather than refused, so a link never blocks a save. + Logging a dose satisfies any booster reminder keyed to one of its component antigens." + UpdateVaccinationRequest: + 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)))$ + antigenSlug: + anyOf: + - type: string + minLength: 1 + maxLength: 64 + - type: "null" + vaccineName: + anyOf: + - type: string + minLength: 1 + maxLength: 200 + - type: "null" + doseNumber: + anyOf: + - type: integer + minimum: 1 + maximum: 20 + - type: "null" + seriesDoses: + anyOf: + - type: integer + minimum: 1 + maximum: 10 + - type: "null" + lotNumber: + anyOf: + - type: string + minLength: 1 + maxLength: 64 + - type: "null" + site: + anyOf: + - type: string + enum: + - LEFT_ARM + - RIGHT_ARM + - LEFT_THIGH + - RIGHT_THIGH + - ORAL + - NASAL + - OTHER + - type: "null" + practitionerId: + anyOf: + - type: string + minLength: 1 + maxLength: 40 + - type: "null" + encounterId: + anyOf: + - type: string + minLength: 1 + maxLength: 40 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + documentIds: + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + additionalProperties: false + description: Partial edit of a dose; an omitted key leaves the column untouched, and a body naming nothing is a 422. + `occurredAt` is editable because a transcription typo is the common case — and editing it deliberately does NOT + re-run the booster satisfaction, so correcting a date can never move a reminder's due date. An edit that would + leave the record with neither identity arm is refused. A present `documentIds` array replaces the links, empty + array included. + VaccinationBoosterRequest: + type: object + properties: + intervalMonths: + type: integer + minimum: 1 + maximum: 600 + label: + type: string + minLength: 1 + maxLength: 120 + notifyHour: + type: integer + minimum: 0 + maximum: 23 + required: + - intervalMonths + - label + additionalProperties: false + description: "The confirm body for a booster reminder — the person's own values, accepted or edited from the catalogue's + prefill. `intervalMonths` is bounded at 600 (a decade booster is 120); `label` is composed client-side so its + locale is the person's own. There is no antigen field: the server reads the antigen from the dose's catalogue + entry, never from the request, so a client cannot key a reminder onto an antigen the dose does not contain." + VaccinationLinkRequest: + type: object + properties: + targetKind: + type: string + enum: + - document + targetIds: + minItems: 1 + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + required: + - targetKind + - targetIds + additionalProperties: false + description: Documents to file against, or unfile from, a dose. Idempotent in both directions and capped at 100 ids. Ids + naming nothing the caller owns come back in `unknown` rather than failing the request. + CreateIllnessEpisodeRequest: + type: object + properties: + label: + type: string + minLength: 1 + maxLength: 120 + type: + type: string + enum: + - INFECTION + - ALLERGY + - INJURY + - MENTAL_HEALTH + - AUTOIMMUNE + - CHRONIC + - OTHER + lifecycle: + default: ACUTE + type: string + enum: + - ACUTE + - CHRONIC_ONGOING + - RECURRING + - FLARE + onsetAt: + type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ + resolvedAt: + 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" + parentConditionId: + anyOf: + - type: string + minLength: 1 + maxLength: 40 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + required: + - label + - type + description: Open an illness/condition episode. `label` is the user-facing name; `type` + `lifecycle` classify it. + `onsetAt` defaults to 'now' server-side when omitted. `parentConditionId` threads a FLARE/RECURRING bout under a + parent condition (must be an owned, live episode). The optional `note` is encrypted at rest. + UpdateIllnessEpisodeRequest: + type: object + properties: + label: + type: string + minLength: 1 + maxLength: 120 + type: + type: string + enum: + - INFECTION + - ALLERGY + - INJURY + - MENTAL_HEALTH + - AUTOIMMUNE + - CHRONIC + - OTHER + lifecycle: + type: string + enum: + - ACUTE + - CHRONIC_ONGOING + - RECURRING + - FLARE + onsetAt: + type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$ + resolvedAt: + 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" + parentConditionId: + anyOf: + - type: string + minLength: 1 + maxLength: 40 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + additionalProperties: false + description: Partial edit of an episode; an omitted key leaves the column untouched. A `null` `resolvedAt` re-opens a + resolved episode; a `null` `parentConditionId` detaches it from its parent. Rejects unknown keys. + ResolveIllnessEpisodeRequest: + type: object + properties: + resolvedAt: + type: string + format: date-time + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[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: Mark an episode recovered. `resolvedAt` defaults to 'now' when omitted, so the one-tap 'mark recovered' + affordance can send an empty body. A CHRONIC_ONGOING episode has no recovery date by design and 422s. + UpsertIllnessDayLogRequest: + type: object + properties: + date: + type: string + pattern: ^\d{4}-\d{2}-\d{2}$ + functionalImpact: + anyOf: + - type: integer + minimum: 0 + maximum: 3 + - type: "null" + feverC: + anyOf: + - type: number + minimum: 30 + maximum: 45 + - type: "null" + symptoms: + maxItems: 40 + type: array + items: + type: object + properties: + key: + type: string + minLength: 1 + maxLength: 80 + severity: + type: integer + minimum: 1 + maximum: 3 + required: + - key + note: + anyOf: + - type: string + maxLength: 2000 + - 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)))$ + required: + - date + description: Upsert one day's symptom / functional-impact / fever row for an episode (keyed on `(episodeId, date)`). + `date` is a `YYYY-MM-DD` tz-anchored day. `functionalImpact` (0–3) + `feverC` are queryable plaintext; + `symptoms` carry an optional 0–3 Jackson/WURSS severity per link; the optional `note` is encrypted at rest. + CreateMentalHealthAssessmentRequest: + type: object + properties: + instrument: + type: string + enum: + - PHQ9 + - GAD7 + - WHO5 + - SCI + items: + minItems: 1 + maxItems: 12 + type: array + items: + type: integer + minimum: 0 + maximum: 5 + functionalDifficulty: + type: integer + minimum: 0 + maximum: 3 + 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))$ + tz: + type: string + maxLength: 64 + locale: + type: string + maxLength: 16 + source: + default: WEB + type: string + enum: + - WEB + - IOS + externalId: + type: string + minLength: 1 + maxLength: 120 + required: + - instrument + - items + description: "Record one completed screener administration: PHQ-9 (9 items, 0–3), GAD-7 (7 items, 0–3), WHO-5 (5 items, + 0–5) or SCI (8 items, 0–4). The array length and per-item ceiling must match the instrument. The item answers + 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: + relationship: + type: string + enum: + - MOTHER + - FATHER + - SISTER + - BROTHER + - DAUGHTER + - SON + - GRANDMOTHER_MATERNAL + - GRANDFATHER_MATERNAL + - GRANDMOTHER_PATERNAL + - GRANDFATHER_PATERNAL + - AUNT + - UNCLE + - COUSIN + - HALF_SIBLING + - OTHER + condition: + type: string + minLength: 1 + maxLength: 160 + ageAtOnset: + anyOf: + - type: integer + minimum: 0 + maximum: 120 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + required: + - relationship + - condition + description: Record one condition for one relative (a FHIR FamilyMemberHistory with a single condition). `condition` is + the user-facing label (the `condition.code.text` anchor — never a machine-guessed code); `ageAtOnset` is the + relative's age (years) when it began. The free-text `note` is encrypted at rest. Patient-reported. + UpdateFamilyHistoryRequest: + type: object + properties: + relationship: + type: string + enum: + - MOTHER + - FATHER + - SISTER + - BROTHER + - DAUGHTER + - SON + - GRANDMOTHER_MATERNAL + - GRANDFATHER_MATERNAL + - GRANDMOTHER_PATERNAL + - GRANDFATHER_PATERNAL + - AUNT + - UNCLE + - COUSIN + - HALF_SIBLING + - OTHER + condition: + type: string + minLength: 1 + maxLength: 160 + ageAtOnset: + anyOf: + - type: integer + minimum: 0 + maximum: 120 + - type: "null" + note: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + additionalProperties: false + description: Partial edit of a family-history entry; an omitted key leaves the column untouched. A `null` + `ageAtOnset`/`note` clears that field. Rejects unknown keys. + DocumentBulkRequest: + type: object + properties: + ids: + minItems: 1 + maxItems: 100 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + action: + type: string + enum: + - setKind + - linkEpisode + - unlinkEpisode + - linkEncounter + - unlinkEncounter + - delete + - restore + kind: + type: string + enum: + - DOCTOR_REPORT + - DISCHARGE_LETTER + - LAB_RESULT + - IMAGING + - PRESCRIPTION + - REFERRAL + - INSURANCE + - VACCINATION + - OTHER + episodeId: + type: string + minLength: 1 + maxLength: 40 + encounterId: + type: string + minLength: 1 + maxLength: 40 + required: + - ids + - action + description: "One bulk action over up to 100 owner-scoped documents: `setKind` (requires `kind`), `linkEpisode` / + `unlinkEpisode` (require `episodeId`), `linkEncounter` / `unlinkEncounter` (require `encounterId`), `delete` + (tombstone, undo-able for 30 days), `restore` (clear tombstone). Partial failures never abort the batch — see + the per-id result array." + DocumentUpdateRequest: + type: object + properties: + title: + anyOf: + - type: string + maxLength: 200 + - type: "null" + kind: + type: string + enum: + - DOCTOR_REPORT + - DISCHARGE_LETTER + - LAB_RESULT + - IMAGING + - PRESCRIPTION + - REFERRAL + - INSURANCE + - VACCINATION + - OTHER + documentDate: + anyOf: + - type: string + pattern: ^\d{4}-\d{2}-\d{2}$ + - type: "null" + episodeIds: + maxItems: 20 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + encounterIds: + maxItems: 20 + type: array + items: + type: string + minLength: 1 + maxLength: 40 + description: "Metadata edit for a stored document: `title` (user label; null clears it), `kind` (category), + `documentDate` (user filing date, YYYY-MM-DD; null clears it), `episodeIds` (REPLACE-SET of condition links — + the document's links become exactly this set; an empty array unlinks everything; every id must be a live episode + of the caller or the whole request answers 404), `encounterIds` (the same replace-set semantics over the + caller's visits). At least one field required. No `userId` field — narrowed from the session and fed to the + Prisma `where` with the row id." InboundExtractRequest: anyOf: - type: object @@ -26539,6 +26247,285 @@ components: - de required: - message + InboundFactEditRequest: + oneOf: + - type: object + properties: + factType: + type: string + const: CONDITION + label: + type: string + minLength: 1 + maxLength: 300 + code: + anyOf: + - type: string + maxLength: 64 + - type: "null" + codeSystem: + anyOf: + - type: string + enum: + - SNOMED + - ICD10 + - LOINC + - RXNORM + - ATC + - type: "null" + clinicalStatus: + anyOf: + - type: string + maxLength: 64 + - type: "null" + verificationStatus: + anyOf: + - type: string + maxLength: 64 + - type: "null" + onsetDate: + anyOf: + - type: string + pattern: ^\d{4}-\d{2}-\d{2}$ + - type: "null" + required: + - factType + - label + - type: object + properties: + factType: + type: string + const: OBSERVATION + label: + type: string + minLength: 1 + maxLength: 300 + code: + anyOf: + - type: string + maxLength: 64 + - type: "null" + codeSystem: + anyOf: + - type: string + enum: + - SNOMED + - ICD10 + - LOINC + - RXNORM + - ATC + - type: "null" + value: + anyOf: + - type: number + - type: "null" + valueText: + anyOf: + - type: string + maxLength: 300 + - type: "null" + unit: + anyOf: + - type: string + maxLength: 80 + - type: "null" + referenceLow: + anyOf: + - type: number + - type: "null" + referenceHigh: + anyOf: + - type: number + - type: "null" + referenceText: + anyOf: + - type: string + maxLength: 120 + - type: "null" + effectiveDate: + anyOf: + - type: string + pattern: ^\d{4}-\d{2}-\d{2}$ + - type: "null" + required: + - factType + - label + - type: object + properties: + factType: + type: string + const: MEDICATION_STATEMENT + name: + type: string + minLength: 1 + maxLength: 200 + dose: + anyOf: + - type: string + maxLength: 120 + - type: "null" + rxNormCode: + anyOf: + - type: string + maxLength: 20 + - type: "null" + atcCode: + anyOf: + - type: string + maxLength: 16 + - type: "null" + statusStated: + anyOf: + - type: string + maxLength: 64 + - type: "null" + effectiveDate: + anyOf: + - type: string + pattern: ^\d{4}-\d{2}-\d{2}$ + - type: "null" + required: + - factType + - name + description: A correction to a staged fact before approval (fixes OCR / units / dates / codes). Discriminated by + `factType` so the edit cannot change the fact's resource type. A successful edit clears `needsReview` — the + values become user-asserted. + type: object + InboundConfirmRequest: + type: object + properties: + decisions: + minItems: 1 + maxItems: 120 + type: array + items: + type: object + properties: + factId: + type: string + minLength: 1 + maxLength: 40 + action: + type: string + enum: + - approve + - reject + required: + - factId + - action + required: + - decisions + description: The approve/reject decisions a human made on the review screen. Each names a staged fact by id. Approved + facts are committed to the structured stores (labs / conditions / medications) through their normal create + paths; rejected facts are discarded. A fact still flagged `needsReview` (below the confidence floor, never + edited) cannot be approved — it is reported back as `needsReview`. No `userId` field — it is narrowed from the + session; every fact id is re-scoped to the document + caller. + CreateCustomMetricRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 120 + unit: + type: string + minLength: 1 + maxLength: 40 + targetLow: + type: number + targetHigh: + type: number + decimals: + type: integer + minimum: 0 + maximum: 6 + description: + type: string + maxLength: 2000 + correlationEnabled: + default: false + type: boolean + required: + - name + - unit + description: "Define a user-scoped custom metric ONCE: free-text `name` + `unit`, an optional target window (`targetLow` + / `targetHigh`; when both present `targetLow` must not exceed `targetHigh`), optional display `decimals`, an + optional `description`, and explicit `correlationEnabled` opt-in. The name is unique per user. Logging a value + later snapshots its unit." + UpdateCustomMetricRequest: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 120 + unit: + type: string + minLength: 1 + maxLength: 40 + targetLow: + anyOf: + - type: number + - type: "null" + targetHigh: + anyOf: + - type: number + - type: "null" + decimals: + anyOf: + - type: integer + minimum: 0 + maximum: 6 + - type: "null" + description: + anyOf: + - type: string + maxLength: 2000 + - type: "null" + correlationEnabled: + type: boolean + description: Partial edit of a custom metric. An omitted key leaves the column untouched; an explicit `null` on a target + bound / `decimals` / `description` clears it. `correlationEnabled` explicitly controls use as a bounded + behaviour channel. A conflicting rename is rejected 409. + CreateCustomMetricEntryRequest: + type: object + properties: + 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))$ + note: + anyOf: + - type: string + maxLength: 2000 + - type: string + const: "" + required: + - value + - measuredAt + description: "Log a value against a custom metric: numeric `value`, ISO 8601 `measuredAt`, optional free-text `note`. + The metric's current unit is snapshotted onto the entry server-side." + UpdateCustomMetricEntryRequest: + type: object + properties: + 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))$ + note: + anyOf: + - anyOf: + - type: string + maxLength: 2000 + - type: string + const: "" + - type: "null" + description: Partial edit of a logged value. An omitted key leaves the column untouched; an explicit `null` on `note` + clears it. RestoreCustomMetricEntriesRequest: type: object properties: @@ -28488,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 @@ -28506,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: @@ -28552,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: @@ -28639,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: @@ -28852,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: @@ -28905,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 @@ -28925,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: @@ -29012,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: @@ -29532,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: @@ -36581,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: @@ -36669,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 @@ -36771,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: @@ -37012,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). @@ -37446,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: @@ -37534,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 @@ -37640,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. @@ -38061,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 @@ -38230,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 @@ -38451,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 @@ -38602,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 @@ -38709,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 @@ -38850,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 @@ -38970,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 @@ -41296,89 +40701,7 @@ components: type: object properties: data: - type: object - properties: - name: - type: string - minLength: 1 - maxLength: 100 - dose: - type: string - minLength: 1 - maxLength: 50 - doseUnit: - type: string - enum: - - mg - - ml - - iu - - tablets - - drops - - puffs - - sprays - cadenceKind: - type: string - enum: - - daily - - weekdays - - everyNWeeks - - monthly - - everyNMonths - - yearly - - rolling - - oneShot - weekdays: - maxItems: 7 - type: array - items: - type: string - enum: - - MO - - TU - - WE - - TH - - FR - - SA - - SU - intervalWeeks: - type: integer - minimum: 1 - maximum: 52 - intervalMonths: - type: integer - minimum: 1 - maximum: 12 - dayOfMonth: - type: integer - minimum: 1 - maximum: 31 - rollingIntervalDays: - type: integer - minimum: 1 - maximum: 365 - timesOfDay: - maxItems: 12 - type: array - items: - type: string - pattern: ^([01]\d|2[0-3]):[0-5]\d$ - startsOn: - type: string - pattern: ^\d{4}-\d{2}-\d{2}$ - endsOn: - type: string - pattern: ^\d{4}-\d{2}-\d{2}$ - oneShot: - type: boolean - confidence: - type: object - propertyNames: - type: string - additionalProperties: - type: number - minimum: 0 - maximum: 1 - additionalProperties: false + $ref: "#/components/schemas/MedicationExtractionResult" error: type: "null" meta: @@ -41391,6 +40714,95 @@ components: - data - error additionalProperties: false + MedicationExtractionResult: + type: object + properties: + name: + type: string + minLength: 1 + maxLength: 100 + dose: + type: string + minLength: 1 + maxLength: 50 + doseUnit: + type: string + enum: + - mg + - ml + - iu + - tablets + - drops + - puffs + - sprays + cadenceKind: + type: string + enum: + - daily + - weekdays + - everyNWeeks + - monthly + - everyNMonths + - yearly + - rolling + - oneShot + weekdays: + maxItems: 7 + type: array + items: + type: string + enum: + - MO + - TU + - WE + - TH + - FR + - SA + - SU + intervalWeeks: + type: integer + minimum: 1 + maximum: 52 + intervalMonths: + type: integer + minimum: 1 + maximum: 12 + dayOfMonth: + type: integer + minimum: 1 + maximum: 31 + rollingIntervalDays: + type: integer + minimum: 1 + maximum: 365 + timesOfDay: + maxItems: 12 + type: array + items: + type: string + pattern: ^([01]\d|2[0-3]):[0-5]\d$ + startsOn: + type: string + pattern: ^\d{4}-\d{2}-\d{2}$ + endsOn: + type: string + pattern: ^\d{4}-\d{2}-\d{2}$ + oneShot: + type: boolean + confidence: + type: object + propertyNames: + type: string + additionalProperties: + type: number + minimum: 0 + maximum: 1 + additionalProperties: false + description: Citation-guarded partial extraction of medication scheduling fields. Every field is optional; the wizard + merges what is present onto the form state and leaves the rest blank. `name` and `dose` are post-validated + against the original free-text and dropped when not substring-matched, so the wizard cannot silently land a + hallucinated brand or dose. `cadenceKind` / `doseUnit` / `weekdays` are closed enums; numeric fields are clamped + to the wizard's wire bounds. GetMedicationCadenceResponse: type: object properties: @@ -41877,315 +41289,7 @@ components: type: object properties: data: - type: object - properties: - medicationId: - type: string - medicationName: - type: string - eligible: - type: boolean - reason: - type: string - enum: - - one_shot - - no_target - startsOn: - anyOf: - - type: string - - type: "null" - resolution: - type: object - properties: - tier: - type: string - enum: - - override - - atc - - name - - none - cls: - anyOf: - - type: string - - type: "null" - required: - - tier - - cls - additionalProperties: false - windowDays: - type: integer - minimum: -9007199254740991 - maximum: 9007199254740991 - minWeeksPerSide: - type: integer - minimum: -9007199254740991 - maximum: 9007199254740991 - markers: - type: object - properties: - start: - anyOf: - - type: string - - type: "null" - startSource: - anyOf: - - type: string - enum: - - startsOn - - firstReading - - type: "null" - doseChanges: - type: array - items: - type: object - properties: - at: - type: string - label: - type: string - required: - - at - - label - additionalProperties: false - pauses: - type: array - items: - type: object - properties: - from: - type: string - to: - anyOf: - - type: string - - type: "null" - required: - - from - - to - additionalProperties: false - required: - - start - - startSource - - doseChanges - - pauses - additionalProperties: false - targets: - type: array - items: - type: object - properties: - kind: - type: string - enum: - - metric - - lab - key: - type: string - label: - type: string - unit: - anyOf: - - type: string - - type: "null" - primary: - type: boolean - referenceBand: - anyOf: - - type: object - properties: - low: - type: number - high: - type: number - required: - - low - - high - additionalProperties: false - - type: "null" - series: - type: array - items: - type: object - properties: - t: - type: string - value: - type: number - status: - type: string - enum: - - in-range - - below - - above - - unknown - required: - - t - - value - additionalProperties: false - beforeAfter: - type: object - properties: - present: - type: boolean - reason: - type: string - enum: - - insufficient_before - - insufficient_after - - no_start - - no_data - before: - type: object - properties: - mean: - type: number - count: - type: integer - minimum: -9007199254740991 - maximum: 9007199254740991 - from: - type: string - to: - type: string - required: - - mean - - count - - from - - to - additionalProperties: false - after: - type: object - properties: - mean: - type: number - count: - type: integer - minimum: -9007199254740991 - maximum: 9007199254740991 - from: - type: string - to: - type: string - required: - - mean - - count - - from - - to - additionalProperties: false - delta: - anyOf: - - type: object - properties: - mean: - type: number - pct: - anyOf: - - type: number - - type: "null" - required: - - mean - - pct - additionalProperties: false - - type: "null" - required: - - present - additionalProperties: false - levelShift: - anyOf: - - type: object - properties: - present: - type: boolean - at: - type: string - nearStart: - type: boolean - required: - - present - additionalProperties: false - - type: "null" - required: - - kind - - key - - label - - unit - - primary - - referenceBand - - series - - beforeAfter - - levelShift - additionalProperties: false - adherence: - type: array - items: - type: object - properties: - date: - type: string - rate: - type: number - taken: - type: integer - minimum: -9007199254740991 - maximum: 9007199254740991 - missed: - type: integer - minimum: -9007199254740991 - maximum: 9007199254740991 - required: - - date - - rate - - taken - - missed - additionalProperties: false - overrideOptions: - type: object - properties: - metrics: - type: array - items: - type: object - properties: - key: - type: string - label: - type: string - required: - - key - - label - additionalProperties: false - biomarkers: - type: array - items: - type: object - properties: - id: - type: string - label: - type: string - unit: - type: string - required: - - id - - label - - unit - additionalProperties: false - required: - - metrics - - biomarkers - additionalProperties: false - required: - - medicationId - - medicationName - - eligible - - startsOn - - resolution - - windowDays - - minWeeksPerSide - - markers - - targets - - adherence - - overrideOptions - additionalProperties: false + $ref: "#/components/schemas/MedicationEfficacyResponse" error: type: "null" meta: @@ -42198,6 +41302,322 @@ components: - data - error additionalProperties: false + MedicationEfficacyResponse: + type: object + properties: + medicationId: + type: string + medicationName: + type: string + eligible: + type: boolean + reason: + type: string + enum: + - one_shot + - no_target + startsOn: + anyOf: + - type: string + - type: "null" + resolution: + type: object + properties: + tier: + type: string + enum: + - override + - atc + - name + - none + cls: + anyOf: + - type: string + - type: "null" + required: + - tier + - cls + additionalProperties: false + windowDays: + type: integer + minimum: -9007199254740991 + maximum: 9007199254740991 + minWeeksPerSide: + type: integer + minimum: -9007199254740991 + maximum: 9007199254740991 + markers: + type: object + properties: + start: + anyOf: + - type: string + - type: "null" + startSource: + anyOf: + - type: string + enum: + - startsOn + - firstReading + - type: "null" + doseChanges: + type: array + items: + type: object + properties: + at: + type: string + label: + type: string + required: + - at + - label + additionalProperties: false + pauses: + type: array + items: + type: object + properties: + from: + type: string + to: + anyOf: + - type: string + - type: "null" + required: + - from + - to + additionalProperties: false + required: + - start + - startSource + - doseChanges + - pauses + additionalProperties: false + targets: + type: array + items: + type: object + properties: + kind: + type: string + enum: + - metric + - lab + key: + type: string + label: + type: string + unit: + anyOf: + - type: string + - type: "null" + primary: + type: boolean + referenceBand: + anyOf: + - type: object + properties: + low: + type: number + high: + type: number + required: + - low + - high + additionalProperties: false + - type: "null" + series: + type: array + items: + type: object + properties: + t: + type: string + value: + type: number + status: + type: string + enum: + - in-range + - below + - above + - unknown + required: + - t + - value + additionalProperties: false + beforeAfter: + type: object + properties: + present: + type: boolean + reason: + type: string + enum: + - insufficient_before + - insufficient_after + - no_start + - no_data + before: + type: object + properties: + mean: + type: number + count: + type: integer + minimum: -9007199254740991 + maximum: 9007199254740991 + from: + type: string + to: + type: string + required: + - mean + - count + - from + - to + additionalProperties: false + after: + type: object + properties: + mean: + type: number + count: + type: integer + minimum: -9007199254740991 + maximum: 9007199254740991 + from: + type: string + to: + type: string + required: + - mean + - count + - from + - to + additionalProperties: false + delta: + anyOf: + - type: object + properties: + mean: + type: number + pct: + anyOf: + - type: number + - type: "null" + required: + - mean + - pct + additionalProperties: false + - type: "null" + required: + - present + additionalProperties: false + levelShift: + anyOf: + - type: object + properties: + present: + type: boolean + at: + type: string + nearStart: + type: boolean + required: + - present + additionalProperties: false + - type: "null" + required: + - kind + - key + - label + - unit + - primary + - referenceBand + - series + - beforeAfter + - levelShift + additionalProperties: false + adherence: + type: array + items: + type: object + properties: + date: + type: string + rate: + type: number + taken: + type: integer + minimum: -9007199254740991 + maximum: 9007199254740991 + missed: + type: integer + minimum: -9007199254740991 + maximum: 9007199254740991 + required: + - date + - rate + - taken + - missed + additionalProperties: false + overrideOptions: + type: object + properties: + metrics: + type: array + items: + type: object + properties: + key: + type: string + label: + type: string + required: + - key + - label + additionalProperties: false + biomarkers: + type: array + items: + type: object + properties: + id: + type: string + label: + type: string + unit: + type: string + required: + - id + - label + - unit + additionalProperties: false + required: + - metrics + - biomarkers + additionalProperties: false + required: + - medicationId + - medicationName + - eligible + - startsOn + - resolution + - windowDays + - minWeeksPerSide + - markers + - targets + - adherence + - overrideOptions + additionalProperties: false + description: Server-authoritative, strictly-descriptive efficacy view relating a medication to the outcome + metric(s)/lab(s) its class is prescribed to move, around its start. Carries the resolved target(s) with their + series, the start/dose-change/pause markers, a before/after-start comparison (honest `{present:false}` below the + per-side data floor), an adherence lane (cadence-aware per-day rate, never recomputed), an optional conservative + level-shift note, and the retarget options. There is NO verdict / score / assessment field by construction — the + client renders numbers and neutral connective phrasing only, never a causal or dose-advice claim. SetMedicationEfficacyTargetResponse: type: object properties: @@ -47579,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: @@ -47652,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: @@ -49227,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 @@ -52905,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 @@ -68426,6 +67698,36 @@ 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: + groupOrder: + maxItems: 50 + type: array + items: + type: string + minLength: 1 + maxLength: 80 + placements: + type: object + propertyNames: + type: string + minLength: 1 + maxLength: 80 + additionalProperties: + type: array + items: + type: string + minLength: 1 + maxLength: 80 + additionalProperties: false + description: "Per-user mood-tag presentation blob. `groupOrder`: category keys in display order (unknown dropped, + missing appended in seeded order). `placements`: categoryKey → ordered tag keys; a placed tag renders in that + group at that index, un-placed tags follow in their home category. Display-only — placements referencing + hidden/archived/unknown keys are silently dropped at read time. Both fields optional: PUT merges + preserve-when-absent." parameters: AccountSelector: name: X-HealthLog-Account 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/custom-metrics.ts b/src/lib/openapi/routes/custom-metrics.ts index d24a113dd..77e9ac8fc 100644 --- a/src/lib/openapi/routes/custom-metrics.ts +++ b/src/lib/openapi/routes/custom-metrics.ts @@ -30,25 +30,28 @@ import { stdResponses, } from "./shared"; -createCustomMetricSchema.meta({ +// Zod 4's `.meta()` returns a NEW instance carrying the id rather than +// annotating in place, so each annotated schema is bound to a const and the +// route table below references that const — a bare call would register nothing. +const createCustomMetricRequest = createCustomMetricSchema.meta({ id: "CreateCustomMetricRequest", description: "Define a user-scoped custom metric ONCE: free-text `name` + `unit`, an optional target window (`targetLow` / `targetHigh`; when both present `targetLow` must not exceed `targetHigh`), optional display `decimals`, an optional `description`, and explicit `correlationEnabled` opt-in. The name is unique per user. Logging a value later snapshots its unit.", }); -updateCustomMetricSchema.meta({ +const updateCustomMetricRequest = updateCustomMetricSchema.meta({ id: "UpdateCustomMetricRequest", description: "Partial edit of a custom metric. An omitted key leaves the column untouched; an explicit `null` on a target bound / `decimals` / `description` clears it. `correlationEnabled` explicitly controls use as a bounded behaviour channel. A conflicting rename is rejected 409.", }); -createCustomMetricEntrySchema.meta({ +const createCustomMetricEntryRequest = createCustomMetricEntrySchema.meta({ id: "CreateCustomMetricEntryRequest", description: "Log a value against a custom metric: numeric `value`, ISO 8601 `measuredAt`, optional free-text `note`. The metric's current unit is snapshotted onto the entry server-side.", }); -updateCustomMetricEntrySchema.meta({ +const updateCustomMetricEntryRequest = updateCustomMetricEntrySchema.meta({ id: "UpdateCustomMetricEntryRequest", description: "Partial edit of a logged value. An omitted key leaves the column untouched; an explicit `null` on `note` clears it.", @@ -164,7 +167,7 @@ export const customMetricPaths: NonNullable = { parameters: [idempotencyKeyParameter], requestBody: { required: true, - content: { "application/json": { schema: createCustomMetricSchema } }, + content: { "application/json": { schema: createCustomMetricRequest } }, }, responses: { ...idempotentWrite(), @@ -213,7 +216,7 @@ export const customMetricPaths: NonNullable = { requestParams: { path: z.object({ id: z.string() }) }, requestBody: { required: true, - content: { "application/json": { schema: updateCustomMetricSchema } }, + content: { "application/json": { schema: updateCustomMetricRequest } }, }, responses: { "200": { @@ -296,7 +299,7 @@ export const customMetricPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: createCustomMetricEntrySchema }, + "application/json": { schema: createCustomMetricEntryRequest }, }, }, responses: { @@ -326,7 +329,7 @@ export const customMetricPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: updateCustomMetricEntrySchema }, + "application/json": { schema: updateCustomMetricEntryRequest }, }, }, responses: { 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/documents.ts b/src/lib/openapi/routes/documents.ts index 379f7a57f..81159f2b7 100644 --- a/src/lib/openapi/routes/documents.ts +++ b/src/lib/openapi/routes/documents.ts @@ -78,25 +78,28 @@ const documentChatListSchema = z }) .meta({ id: "DocumentChatList" }); -inboundConfirmSchema.meta({ +// Zod 4's `.meta()` returns a NEW instance carrying the id rather than +// annotating in place, so each annotated schema is bound to a const and the +// route table below references that const — a bare call would register nothing. +const inboundConfirmRequest = inboundConfirmSchema.meta({ id: "InboundConfirmRequest", description: "The approve/reject decisions a human made on the review screen. Each names a staged fact by id. Approved facts are committed to the structured stores (labs / conditions / medications) through their normal create paths; rejected facts are discarded. A fact still flagged `needsReview` (below the confidence floor, never edited) cannot be approved — it is reported back as `needsReview`. No `userId` field — it is narrowed from the session; every fact id is re-scoped to the document + caller.", }); -inboundFactEditSchema.meta({ +const inboundFactEditRequest = inboundFactEditSchema.meta({ id: "InboundFactEditRequest", description: "A correction to a staged fact before approval (fixes OCR / units / dates / codes). Discriminated by `factType` so the edit cannot change the fact's resource type. A successful edit clears `needsReview` — the values become user-asserted.", }); -documentUpdateSchema.meta({ +const documentUpdateRequest = documentUpdateSchema.meta({ id: "DocumentUpdateRequest", description: "Metadata edit for a stored document: `title` (user label; null clears it), `kind` (category), `documentDate` (user filing date, YYYY-MM-DD; null clears it), `episodeIds` (REPLACE-SET of condition links — the document's links become exactly this set; an empty array unlinks everything; every id must be a live episode of the caller or the whole request answers 404), `encounterIds` (the same replace-set semantics over the caller's visits). At least one field required. No `userId` field — narrowed from the session and fed to the Prisma `where` with the row id.", }); -documentBulkSchema.meta({ +const documentBulkRequest = documentBulkSchema.meta({ id: "DocumentBulkRequest", description: "One bulk action over up to 100 owner-scoped documents: `setKind` (requires `kind`), `linkEpisode` / `unlinkEpisode` (require `episodeId`), `linkEncounter` / `unlinkEncounter` (require `encounterId`), `delete` (tombstone, undo-able for 30 days), `restore` (clear tombstone). Partial failures never abort the batch — see the per-id result array.", @@ -514,7 +517,7 @@ export const inboundDocumentPaths: NonNullable = { "Applies one action to up to 100 owner-scoped documents and returns a per-id result array — a partial failure never aborts the batch. `error` is `notFound` (unknown / foreign / tombstoned where liveness is required) or `conflict` (restore blocked by a live duplicate of the same bytes).", requestBody: { required: true, - content: { "application/json": { schema: documentBulkSchema } }, + content: { "application/json": { schema: documentBulkRequest } }, }, responses: { "200": { @@ -587,7 +590,7 @@ export const inboundDocumentPaths: NonNullable = { ], requestBody: { required: true, - content: { "application/json": { schema: documentUpdateSchema } }, + content: { "application/json": { schema: documentUpdateRequest } }, }, responses: { "200": { @@ -1078,7 +1081,7 @@ export const inboundDocumentPaths: NonNullable = { ], requestBody: { required: true, - content: { "application/json": { schema: inboundFactEditSchema } }, + content: { "application/json": { schema: inboundFactEditRequest } }, }, responses: { "200": { @@ -1110,7 +1113,7 @@ export const inboundDocumentPaths: NonNullable = { ], requestBody: { required: true, - content: { "application/json": { schema: inboundConfirmSchema } }, + content: { "application/json": { schema: inboundConfirmRequest } }, }, responses: { ...idempotentWrite(), 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/family-history.ts b/src/lib/openapi/routes/family-history.ts index 5f71484f7..83ab75a4d 100644 --- a/src/lib/openapi/routes/family-history.ts +++ b/src/lib/openapi/routes/family-history.ts @@ -30,19 +30,22 @@ import { stdResponses, } from "./shared"; -familyHistoryCreateSchema.meta({ +// Zod 4's `.meta()` returns a NEW instance carrying the id rather than +// annotating in place, so each annotated schema is bound to a const and the +// route table below references that const — a bare call would register nothing. +const createFamilyHistoryRequest = familyHistoryCreateSchema.meta({ id: "CreateFamilyHistoryRequest", description: "Record one condition for one relative (a FHIR FamilyMemberHistory with a single condition). `condition` is the user-facing label (the `condition.code.text` anchor — never a machine-guessed code); `ageAtOnset` is the relative's age (years) when it began. The free-text `note` is encrypted at rest. Patient-reported.", }); -familyHistoryUpdateSchema.meta({ +const updateFamilyHistoryRequest = familyHistoryUpdateSchema.meta({ id: "UpdateFamilyHistoryRequest", description: "Partial edit of a family-history entry; an omitted key leaves the column untouched. A `null` `ageAtOnset`/`note` clears that field. Rejects unknown keys.", }); -familyHistoryListQuerySchema.meta({ +const listFamilyHistoryQuery = familyHistoryListQuerySchema.meta({ id: "ListFamilyHistoryQuery", description: "Query params for the family-history list: optional `limit` (1–200, default 100). Newest-first.", @@ -78,7 +81,7 @@ export const familyHistoryPaths: NonNullable = { summary: "List family history (v1.25)", description: "Returns the caller's live (non-deleted) family-history entries, newest-first.", - requestParams: { query: familyHistoryListQuerySchema }, + requestParams: { query: listFamilyHistoryQuery }, responses: { ...recordRefusal(), "200": { @@ -104,7 +107,7 @@ export const familyHistoryPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: familyHistoryCreateSchema }, + "application/json": { schema: createFamilyHistoryRequest }, }, }, responses: { @@ -158,7 +161,7 @@ export const familyHistoryPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: familyHistoryUpdateSchema }, + "application/json": { schema: updateFamilyHistoryRequest }, }, }, responses: { 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/illness.ts b/src/lib/openapi/routes/illness.ts index 9b51d6f9d..8cbb758ff 100644 --- a/src/lib/openapi/routes/illness.ts +++ b/src/lib/openapi/routes/illness.ts @@ -22,8 +22,6 @@ import { illnessEpisodeResolveSchema, illnessEpisodeListQuerySchema, illnessDayLogInputSchema, - illnessDayLogQuerySchema, - illnessDayLogListQuerySchema, illnessInsightsQuerySchema, illnessTypeEnum, illnessLifecycleEnum, @@ -38,48 +36,39 @@ import { stdResponses, } from "./shared"; -illnessEpisodeCreateSchema.meta({ +// Zod 4's `.meta()` returns a NEW instance carrying the id rather than +// annotating in place, so each annotated schema is bound to a const and the +// route table below references that const — a bare call would register nothing. +const createIllnessEpisodeRequest = illnessEpisodeCreateSchema.meta({ id: "CreateIllnessEpisodeRequest", description: "Open an illness/condition episode. `label` is the user-facing name; `type` + `lifecycle` classify it. `onsetAt` defaults to 'now' server-side when omitted. `parentConditionId` threads a FLARE/RECURRING bout under a parent condition (must be an owned, live episode). The optional `note` is encrypted at rest.", }); -illnessEpisodeUpdateSchema.meta({ +const updateIllnessEpisodeRequest = illnessEpisodeUpdateSchema.meta({ id: "UpdateIllnessEpisodeRequest", description: "Partial edit of an episode; an omitted key leaves the column untouched. A `null` `resolvedAt` re-opens a resolved episode; a `null` `parentConditionId` detaches it from its parent. Rejects unknown keys.", }); -illnessEpisodeResolveSchema.meta({ +const resolveIllnessEpisodeRequest = illnessEpisodeResolveSchema.meta({ id: "ResolveIllnessEpisodeRequest", description: "Mark an episode recovered. `resolvedAt` defaults to 'now' when omitted, so the one-tap 'mark recovered' affordance can send an empty body. A CHRONIC_ONGOING episode has no recovery date by design and 422s.", }); -illnessEpisodeListQuerySchema.meta({ +const listIllnessEpisodesQuery = illnessEpisodeListQuerySchema.meta({ id: "ListIllnessEpisodesQuery", description: "Query params for the episode history: optional `limit` (1–100, default 50) and `includeResolved` ('true' | 'false'); 'false' hides episodes that already carry a `resolvedAt`. Newest-first by `onsetAt`.", }); -illnessDayLogInputSchema.meta({ +const upsertIllnessDayLogRequest = illnessDayLogInputSchema.meta({ id: "UpsertIllnessDayLogRequest", description: "Upsert one day's symptom / functional-impact / fever row for an episode (keyed on `(episodeId, date)`). `date` is a `YYYY-MM-DD` tz-anchored day. `functionalImpact` (0–3) + `feverC` are queryable plaintext; `symptoms` carry an optional 0–3 Jackson/WURSS severity per link; the optional `note` is encrypted at rest.", }); -illnessDayLogQuerySchema.meta({ - id: "GetIllnessDayLogQuery", - description: - "Single-day read query: `date` is a `YYYY-MM-DD` day. Returns the matching day-log or `null` when nothing is logged for that day.", -}); - -illnessDayLogListQuerySchema.meta({ - id: "ListIllnessDayLogsQuery", - description: - "Date-less LIST query (v1.18.3): omit `date` to page the episode's whole day-log history. `limit` (1–200, default 60) + `offset` (default 0) + `sortDir` ('asc' | 'desc', default 'desc') with `meta.total`. Mutually exclusive with the single-day `date` read — presence of `date` selects the single-day mode.", -}); - /** * The documented query for `GET .../day-logs`: a `date` (single-day read) OR * the list pagination params (date-less list). Both are optional on the wire; @@ -97,7 +86,7 @@ const illnessDayLogGetQuery = z }) .meta({ id: "GetIllnessDayLogsQuery" }); -illnessInsightsQuerySchema.meta({ +const illnessInsightsQuery = illnessInsightsQuerySchema.meta({ id: "IllnessInsightsQuery", description: "Cross-episode retrospective window query: optional `windowDays` (30–1095, default 365) and optional `includeRecoveryGap` (default false). With `includeRecoveryGap` off the response carries the count breakdown and a null typical recovery gap on a single fast query; set it true to pay for the per-episode correlation that computes the recovery gap. Retrospective only — the engine summarises past episodes, never forecasts.", @@ -298,7 +287,7 @@ export const illnessPaths: NonNullable = { summary: "List illness episodes (v1.18.1)", description: "Returns the caller's live (non-deleted) illness/condition episodes, newest-first by onset. `includeResolved=false` hides episodes that already carry a `resolvedAt`. Born-gated.", - requestParams: { query: illnessEpisodeListQuerySchema }, + requestParams: { query: listIllnessEpisodesQuery }, responses: { ...recordRefusal(), "200": { @@ -324,7 +313,7 @@ export const illnessPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: illnessEpisodeCreateSchema }, + "application/json": { schema: createIllnessEpisodeRequest }, }, }, responses: { @@ -375,7 +364,7 @@ export const illnessPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: illnessEpisodeUpdateSchema }, + "application/json": { schema: updateIllnessEpisodeRequest }, }, }, responses: { @@ -456,7 +445,7 @@ export const illnessPaths: NonNullable = { requestBody: { required: false, content: { - "application/json": { schema: illnessEpisodeResolveSchema }, + "application/json": { schema: resolveIllnessEpisodeRequest }, }, }, responses: { @@ -514,7 +503,7 @@ export const illnessPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: illnessDayLogInputSchema }, + "application/json": { schema: upsertIllnessDayLogRequest }, }, }, responses: { @@ -576,7 +565,7 @@ export const illnessPaths: NonNullable = { summary: "Cross-episode retrospective insights (v1.18.1)", description: "Returns the cross-episode retrospective summary over a trailing window: 'sick N times · typical recovery gap X days', a recurrence-by-month tally, and a per-type breakdown. The typical gap is withheld (null) below the min-sample floor (asserts nothing thin). Retrospective ONLY — the recurrence figure is a count of the past, never a forecast. Born-gated + owner-scoped.", - requestParams: { query: illnessInsightsQuerySchema }, + requestParams: { query: illnessInsightsQuery }, responses: { "200": { description: "The cross-episode retrospective summary.", 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 a77ccf325..6f28752d5 100644 --- a/src/lib/openapi/routes/index.ts +++ b/src/lib/openapi/routes/index.ts @@ -72,7 +72,7 @@ import { } from "./medications"; import { mcpPaths } from "./mcp"; import { metaPaths } from "./meta"; -import { moodPaths } from "./mood"; +import { moodPaths, moodTagLayout } from "./mood"; import { notificationTransportPaths } from "./notifications-transport"; import { notificationChannelPaths } from "./notifications"; import { nutrientPaths } from "./nutrients"; @@ -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, @@ -183,6 +183,11 @@ export const openApiComponents: NonNullable = { // this forced registration the schema the description points to would be // silently absent from the published spec. // + // `MoodTagLayout` joins them for exactly the same reason: its annotation is + // in the assigning form and correct, but both consumers `.extend()` it, so + // the base name is never referenced and the description the mood surface + // relies on would go unpublished. + // // `CoachPrefs` is the same shape: the PUT `/api/auth/me/coach-prefs` // description tells the reader "Body is validated against `CoachPrefs`", // but every actual consumer is a `.extend()` of it (`PutCoachPrefsRequest`, @@ -193,6 +198,7 @@ export const openApiComponents: NonNullable = { MedicationDoseHistoryImportFatalReason: medicationDoseHistoryImportFatalReasonEnum, CoachPrefs: coachPrefsSchema, + MoodTagLayout: moodTagLayout, }, // v1.36.0 — the per-request account selector, defined in the sharing route // module so that `src/__tests__/acting-account-boundary-guard.test.ts` keeps 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/labs.ts b/src/lib/openapi/routes/labs.ts index bf0aabe21..7cddeae2a 100644 --- a/src/lib/openapi/routes/labs.ts +++ b/src/lib/openapi/routes/labs.ts @@ -25,19 +25,22 @@ import { stdResponses, } from "./shared"; -createLabResultSchema.meta({ +// Zod 4's `.meta()` returns a NEW instance carrying the id rather than +// annotating in place, so each annotated schema is bound to a const and the +// route table below references that const — a bare call would register nothing. +const createLabResultRequest = createLabResultSchema.meta({ id: "CreateLabResultRequest", description: 'Record a single biomarker reading (HbA1c, LDL, ferritin, TSH, …). A reading is EITHER numeric (`value` + `unit`, optional reference bounds) OR qualitative (`valueText`, e.g. "negativ" / "positiv") — exactly one, never both, never neither. `analyte` + `unit` are free-form (a lab prints its own naming); a qualitative reading needs no unit / bounds. When both numeric bounds are present `referenceLow` must not exceed `referenceHigh`. `takenAt` is a backdatable ISO instant (no future, ≤ 50 years past). The optional `note` is encrypted at rest. The optional `source` records provenance: `"OCR"` for an on-device-OCR capture (the raw image never reaches the server on this path), defaulting to `"MANUAL"` when omitted. The optional `encounterId` files the reading against a visit the caller owns — always optional, and a link that could not be made never fails the reading.', }); -updateLabResultSchema.meta({ +const updateLabResultRequest = updateLabResultSchema.meta({ id: "UpdateLabResultRequest", description: "Partial edit of a lab result. An omitted key leaves the column untouched; an explicit `null` on `panel` / `note` / a reference bound clears it. Edit the matching value field for the row's type — `value` for a numeric row, `valueText` for a qualitative one; a request must not carry both, and a cross-type edit (the wrong field for the row) is rejected.", }); -listLabResultsSchema.meta({ +const listLabResultsQuery = listLabResultsSchema.meta({ id: "ListLabResultsQuery", description: "Query params for the lab-result list: optional `analyte` (exact) + `panel` filters, an inclusive `from`/`to` date range, and `limit` (≤ 500) / `offset` pagination. Defaults to `takenAt` DESC.", @@ -122,7 +125,7 @@ export const labsPaths: NonNullable = { summary: "List the caller's lab results", description: "Returns the caller's live (non-deleted) lab results, newest first, with optional analyte / panel / date-range filters. Each row carries a server-computed neutral `rangeStatus` and a `hasNote` flag (the encrypted note itself is not echoed).", - requestParams: { query: listLabResultsSchema }, + requestParams: { query: listLabResultsQuery }, responses: { ...recordRefusal(), "200": { @@ -144,7 +147,7 @@ export const labsPaths: NonNullable = { parameters: [idempotencyKeyParameter], requestBody: { required: true, - content: { "application/json": { schema: createLabResultSchema } }, + content: { "application/json": { schema: createLabResultRequest } }, }, responses: { ...idempotentWrite(), @@ -190,7 +193,7 @@ export const labsPaths: NonNullable = { requestParams: { path: z.object({ id: z.string() }) }, requestBody: { required: true, - content: { "application/json": { schema: updateLabResultSchema } }, + content: { "application/json": { schema: updateLabResultRequest } }, }, responses: { ...recordRefusal(), 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/medications/paths.ts b/src/lib/openapi/routes/medications/paths.ts index 9d214a7fb..2ead8a4a4 100644 --- a/src/lib/openapi/routes/medications/paths.ts +++ b/src/lib/openapi/routes/medications/paths.ts @@ -19,7 +19,6 @@ import { bulkDeleteIntakeEventsSchema, updateIntakeEventSchema, } from "@/lib/validations/medication"; -import { medicationExtractionSchema } from "@/lib/ai/coach/medication-extract-prompt"; import { scheduleRevisionCreateSchema, scheduleRevisionUpdateSchema, @@ -39,13 +38,16 @@ import { stdResponses, } from "../shared"; -efficacyTargetOverrideSchema.meta({ +// Zod 4's `.meta()` returns a NEW instance carrying the id rather than +// annotating in place, so each annotated schema is bound to a const and the +// route table below references that const — a bare call would register nothing. +const setMedicationEfficacyTargetRequest = efficacyTargetOverrideSchema.meta({ id: "SetMedicationEfficacyTargetRequest", description: "Set or clear the user's explicit efficacy-target override for a medication. `clear:true` removes the override so the resolver reverts to the derived (ATC class prefix → name inference) target; otherwise pin exactly ONE of `measurementType` (a metric series) / `biomarkerId` (a lab analyte). `userId` is never a field — ownership is narrowed through the medication (and the biomarker for a lab target).", }); -medicationEfficacyResponseSchema.meta({ +const medicationEfficacyResponse = medicationEfficacyResponseSchema.meta({ id: "MedicationEfficacyResponse", description: "Server-authoritative, strictly-descriptive efficacy view relating a medication to the outcome metric(s)/lab(s) its class is prescribed to move, around its start. Carries the resolved target(s) with their series, the start/dose-change/pause markers, a before/after-start comparison (honest `{present:false}` below the per-side data floor), an adherence lane (cadence-aware per-day rate, never recomputed), an optional conservative level-shift note, and the retarget options. There is NO verdict / score / assessment field by construction — the client renders numbers and neutral connective phrasing only, never a causal or dose-advice claim.", @@ -62,6 +64,7 @@ import { scheduleRevisionResource, scheduleRevisionListResponse, medicationExtractRequest, + medicationExtractionResult, medicationListLayoutPutBody, medicationListLayoutResult, doseHistoryQuery, @@ -1336,7 +1339,7 @@ export const medicationPaths: NonNullable = { content: { "application/json": { schema: dataEnvelope( - medicationExtractionSchema, + medicationExtractionResult, "MedicationExtractResponse", ), }, @@ -1462,7 +1465,7 @@ export const medicationPaths: NonNullable = { content: { "application/json": { schema: dataEnvelope( - medicationEfficacyResponseSchema, + medicationEfficacyResponse, "GetMedicationEfficacyResponse", ), }, @@ -1488,7 +1491,7 @@ export const medicationPaths: NonNullable = { requestBody: { required: true, content: { - "application/json": { schema: efficacyTargetOverrideSchema }, + "application/json": { schema: setMedicationEfficacyTargetRequest }, }, }, responses: { diff --git a/src/lib/openapi/routes/medications/schemas.ts b/src/lib/openapi/routes/medications/schemas.ts index de32a1a80..2bad6dae8 100644 --- a/src/lib/openapi/routes/medications/schemas.ts +++ b/src/lib/openapi/routes/medications/schemas.ts @@ -967,7 +967,10 @@ export const doseHistoryResponse = z "Per-slot dose ledger over [from, to]: every expected slot with a status plus every off-schedule intake tagged ad-hoc. Built from the same band minter + `reconstructDoseHistory` the compliance % consumes, so the history view and the rate never contradict each other.", }); -medicationExtractionSchema.meta({ +// Zod 4's `.meta()` returns a NEW instance carrying the id rather than +// annotating in place, so the annotated clone is exported and the path table +// references it — a bare call would register nothing. +export const medicationExtractionResult = medicationExtractionSchema.meta({ id: "MedicationExtractionResult", description: "Citation-guarded partial extraction of medication scheduling fields. Every field is optional; the wizard merges what is present onto the form state and leaves the rest blank. `name` and `dose` are post-validated against the original free-text and dropped when not substring-matched, so the wizard cannot silently land a hallucinated brand or dose. `cadenceKind` / `doseUnit` / `weekdays` are closed enums; numeric fields are clamped to the wizard's wire bounds.", diff --git a/src/lib/openapi/routes/mental-health.ts b/src/lib/openapi/routes/mental-health.ts index d170d9294..6e9080da6 100644 --- a/src/lib/openapi/routes/mental-health.ts +++ b/src/lib/openapi/routes/mental-health.ts @@ -27,13 +27,16 @@ import { stdResponses, } from "./shared"; -createAssessmentSchema.meta({ +// Zod 4's `.meta()` returns a NEW instance carrying the id rather than +// annotating in place, so each annotated schema is bound to a const and the +// route table below references that const — a bare call would register nothing. +const createMentalHealthAssessmentRequest = createAssessmentSchema.meta({ id: "CreateMentalHealthAssessmentRequest", description: "Record one completed screener administration: PHQ-9 (9 items, 0–3), GAD-7 (7 items, 0–3), WHO-5 (5 items, 0–5) or SCI (8 items, 0–4). The array length and per-item ceiling must match the instrument. The item answers 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.", }); -listAssessmentsSchema.meta({ +const listMentalHealthAssessmentsQuery = listAssessmentsSchema.meta({ id: "ListMentalHealthAssessmentsQuery", description: "Filter the caller's screener history by instrument; paginate with limit/offset.", @@ -111,7 +114,7 @@ export const mentalHealthPaths: NonNullable = { summary: "List the caller's screener history", description: "Returns PHQ-9 / GAD-7 / WHO-5 / SCI administrations (newest first). Totals + bands + flags only; raw item answers are never returned.", - requestParams: { query: listAssessmentsSchema }, + requestParams: { query: listMentalHealthAssessmentsQuery }, responses: { ...recordRefusal(), "200": { @@ -136,7 +139,7 @@ export const mentalHealthPaths: NonNullable = { parameters: [idempotencyKeyParameter], requestBody: { content: { - "application/json": { schema: createAssessmentSchema }, + "application/json": { schema: createMentalHealthAssessmentRequest }, }, }, responses: { diff --git a/src/lib/openapi/routes/mood.ts b/src/lib/openapi/routes/mood.ts index 9d8b60c87..92cb9afe4 100644 --- a/src/lib/openapi/routes/mood.ts +++ b/src/lib/openapi/routes/mood.ts @@ -61,40 +61,41 @@ import { } from "./shared"; // ── Request schemas — annotated for spec emission ──────────────────── +// +// Assigned rather than annotated in place: Zod 4's `.meta()` returns a NEW +// instance carrying the id, so the route table below must reference these +// consts for the names to reach `components.schemas`. -createCustomTagSchema.meta({ +const createMoodTagRequest = createCustomTagSchema.meta({ id: "CreateMoodTagRequest", description: "Create a per-user custom mood tag (BINARY only). `label` is encrypted at rest. `icon` must come from the curated allowlist (unknown name → 422). `categoryKey` picks the home group — any seeded category key or one of the caller's own `customcat:` group keys; omitted → the seeded `custom` category. Capped at 50 active custom tags per user (422).", }); -updateCustomTagSchema.meta({ +const updateMoodTagRequest = updateCustomTagSchema.meta({ id: "UpdateMoodTagRequest", description: "Partial custom-tag edit; at least one field required. `isActive:false` archives (history intact), `isActive:true` restores. `categoryKey` moves the tag to another group (a real categoryId move).", }); -createCustomGroupSchema.meta({ +const createMoodTagGroupRequest = createCustomGroupSchema.meta({ id: "CreateMoodTagGroupRequest", description: "Create a per-user custom mood-tag group. `label` is encrypted at rest; `icon` must come from the curated allowlist. Capped at 12 active custom groups per user (422).", }); -updateCustomGroupSchema.meta({ +const updateMoodTagGroupRequest = updateCustomGroupSchema.meta({ id: "UpdateMoodTagGroupRequest", description: "Partial custom-group edit; at least one field required. `isActive:false` retires the group without touching its tags.", }); -hideCatalogueTagSchema.meta({ +const hideMoodTagRequest = hideCatalogueTagSchema.meta({ id: "HideMoodTagRequest", description: "Hide / show a CATALOGUE tag for the calling user. Custom tags are hidden via their own `isActive` flag on the custom PATCH instead (this route 400s a `custom:` key).", }); -// Assigned rather than annotated in place: Zod 4's `.meta()` returns a NEW -// instance carrying the id, so the route table below must reference these -// consts for the names to reach `components.schemas`. const createMoodEntryRequest = createMoodEntrySchema.meta({ id: "CreateMoodEntryRequest", description: @@ -113,7 +114,15 @@ const listMoodEntriesQuery = listMoodEntriesSchema.meta({ "Filters + paging for the mood-entry list. `from` / `to` bound the local-day `date` label (YYYY-MM-DD, inclusive). `limit` capped at 500.", }); -moodTagLayoutSchema.meta({ +/** + * Exported for the forced registration in `./index.ts`. + * + * Both consumers are an `.extend()` of this base, and `.extend()` builds a new + * object rather than a reference, so the name never carries forward on its own + * even though the annotation is in the assigning form. Same shape as + * `CoachPrefs` and `Medication`, and the same remedy. + */ +export const moodTagLayout = moodTagLayoutSchema.meta({ id: "MoodTagLayout", description: "Per-user mood-tag presentation blob. `groupOrder`: category keys in display order (unknown dropped, missing appended in seeded order). `placements`: categoryKey → ordered tag keys; a placed tag renders in that group at that index, un-placed tags follow in their home category. Display-only — placements referencing hidden/archived/unknown keys are silently dropped at read time. Both fields optional: PUT merges preserve-when-absent.", @@ -220,7 +229,7 @@ const moodTagLayoutResolved = z // v1.32.22 (M2) — the PUT request extends the layout schema with the base // token (stripped pre-Zod at runtime). -const moodTagLayoutPutRequest = moodTagLayoutSchema +const moodTagLayoutPutRequest = moodTagLayout .extend({ baseUpdatedAt: baseUpdatedAtField }) .meta({ id: "MoodTagLayoutPutRequest", @@ -1349,7 +1358,7 @@ export const moodPaths: NonNullable = { "Mints a `custom:` key, encrypts the label at rest, stores a BINARY tag owned by the caller under the chosen group (default: the seeded `custom` category). 422 over the 50-tag cap or on an unknown / foreign `categoryKey`.", requestBody: { required: true, - content: { "application/json": { schema: createCustomTagSchema } }, + content: { "application/json": { schema: createMoodTagRequest } }, }, responses: { "201": { @@ -1376,7 +1385,7 @@ export const moodPaths: NonNullable = { requestParams: { path: z.object({ key: z.string() }) }, requestBody: { required: true, - content: { "application/json": { schema: updateCustomTagSchema } }, + content: { "application/json": { schema: updateMoodTagRequest } }, }, responses: { "200": { @@ -1429,7 +1438,7 @@ export const moodPaths: NonNullable = { requestParams: { path: z.object({ key: z.string() }) }, requestBody: { required: true, - content: { "application/json": { schema: hideCatalogueTagSchema } }, + content: { "application/json": { schema: hideMoodTagRequest } }, }, responses: { "200": { @@ -1460,7 +1469,7 @@ export const moodPaths: NonNullable = { "Mints a `customcat:` key, encrypts the label at rest, stores a group owned by the caller. 422 over the 12-group cap.", requestBody: { required: true, - content: { "application/json": { schema: createCustomGroupSchema } }, + content: { "application/json": { schema: createMoodTagGroupRequest } }, }, responses: { "201": { @@ -1487,7 +1496,7 @@ export const moodPaths: NonNullable = { requestParams: { path: z.object({ key: z.string() }) }, requestBody: { required: true, - content: { "application/json": { schema: updateCustomGroupSchema } }, + content: { "application/json": { schema: updateMoodTagGroupRequest } }, }, responses: { "200": { 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/vaccinations.ts b/src/lib/openapi/routes/vaccinations.ts index 6e60f1643..193a7904a 100644 --- a/src/lib/openapi/routes/vaccinations.ts +++ b/src/lib/openapi/routes/vaccinations.ts @@ -41,37 +41,40 @@ import { stdResponses, } from "./shared"; -vaccinationCreateSchema.meta({ +// Zod 4's `.meta()` returns a NEW instance carrying the id rather than +// annotating in place, so each annotated schema is bound to a const and the +// route table below references that const — a bare call would register nothing. +const createVaccinationRequest = vaccinationCreateSchema.meta({ id: "CreateVaccinationRequest", description: "Log one administered dose. `occurredAt` is required and must not be in the future — a vaccination is recorded after the fact, and a planned booster is a reminder rather than a record. Exactly one identity arm is required: either `antigenSlug` (a slug the catalogue offers) or `vaccineName` (whatever the person's own record says, verbatim, which may be a trade name). Everything else is optional, so a decades-old paper entry with nothing but a date and a name is loggable. `note` is encrypted at rest. `documentIds` pre-links the scanned pages; an id naming nothing the caller owns is dropped rather than refused, so a link never blocks a save. Logging a dose satisfies any booster reminder keyed to one of its component antigens.", }); -vaccinationUpdateSchema.meta({ +const updateVaccinationRequest = vaccinationUpdateSchema.meta({ id: "UpdateVaccinationRequest", description: "Partial edit of a dose; an omitted key leaves the column untouched, and a body naming nothing is a 422. `occurredAt` is editable because a transcription typo is the common case — and editing it deliberately does NOT re-run the booster satisfaction, so correcting a date can never move a reminder's due date. An edit that would leave the record with neither identity arm is refused. A present `documentIds` array replaces the links, empty array included.", }); -vaccinationListQuerySchema.meta({ +const listVaccinationsQuery = vaccinationListQuerySchema.meta({ id: "ListVaccinationsQuery", description: "Filters for the immunization list: `antigenSlug`, `from` / `to` (ISO-8601 instants) and `limit` (1–500, default 300). The list is full and bounded rather than paged — a vaccination record is a lifetime document but a short one.", }); -vaccinationBoosterSchema.meta({ +const vaccinationBoosterRequest = vaccinationBoosterSchema.meta({ id: "VaccinationBoosterRequest", description: "The confirm body for a booster reminder — the person's own values, accepted or edited from the catalogue's prefill. `intervalMonths` is bounded at 600 (a decade booster is 120); `label` is composed client-side so its locale is the person's own. There is no antigen field: the server reads the antigen from the dose's catalogue entry, never from the request, so a client cannot key a reminder onto an antigen the dose does not contain.", }); -vaccinationSuggestQuerySchema.meta({ +const vaccinationSuggestQuery = vaccinationSuggestQuerySchema.meta({ id: "VaccinationSuggestQuery", description: "The single input to the upload suggestion: the document's anchor date (ISO-8601 with offset). No ids — the candidate doses come from the caller's own record, narrowed from the session.", }); -vaccinationLinkSchema.meta({ +const vaccinationLinkRequest = vaccinationLinkSchema.meta({ id: "VaccinationLinkRequest", description: "Documents to file against, or unfile from, a dose. Idempotent in both directions and capped at 100 ids. Ids naming nothing the caller owns come back in `unknown` rather than failing the request.", @@ -224,7 +227,7 @@ export const vaccinationPaths: NonNullable = { summary: "List vaccinations", description: "Returns the caller's live doses, newest first, each with its series resolved per component antigen. Soft-deleted doses are excluded.", - requestParams: { query: vaccinationListQuerySchema }, + requestParams: { query: listVaccinationsQuery }, responses: { ...recordRefusal(), "200": { @@ -246,7 +249,7 @@ export const vaccinationPaths: NonNullable = { "Records one administered dose and satisfies every live booster reminder keyed to one of its component antigens — so a single combined dose settles each of the antigens it contains. Satisfaction is forward-only: a dose older than a reminder's last satisfaction is a no-op, which is what makes back-entering an old paper record safe. Honours `Idempotency-Key`. Audits as `vaccination.record.create`.", requestBody: { required: true, - content: { "application/json": { schema: vaccinationCreateSchema } }, + content: { "application/json": { schema: createVaccinationRequest } }, }, responses: { ...idempotentWrite(), @@ -292,7 +295,7 @@ export const vaccinationPaths: NonNullable = { requestParams: idPath, requestBody: { required: true, - content: { "application/json": { schema: vaccinationUpdateSchema } }, + content: { "application/json": { schema: updateVaccinationRequest } }, }, responses: { ...recordRefusal(), @@ -363,7 +366,7 @@ export const vaccinationPaths: NonNullable = { requestParams: idPath, requestBody: { required: true, - content: { "application/json": { schema: vaccinationBoosterSchema } }, + content: { "application/json": { schema: vaccinationBoosterRequest } }, }, responses: { ...recordRefusal(), @@ -406,7 +409,7 @@ export const vaccinationPaths: NonNullable = { summary: "Suggest the dose a document belongs to", description: "Which dose does a document dated `anchor` most plausibly belong to? The document upload review asks this when a scan is classified `VACCINATION`. The window (±7 days, shared with the visit moment) and the one-pre-selects / many-offer-a-picker verdict are decided server-side so the browser never re-derives them. Owner-scoped; the anchor is the only input and it is a date, not an id.", - requestParams: { query: vaccinationSuggestQuerySchema }, + requestParams: { query: vaccinationSuggestQuery }, responses: { ...recordRefusal(), "200": { @@ -433,7 +436,7 @@ export const vaccinationPaths: NonNullable = { requestParams: idPath, requestBody: { required: true, - content: { "application/json": { schema: vaccinationLinkSchema } }, + content: { "application/json": { schema: vaccinationLinkRequest } }, }, responses: { "200": { @@ -462,7 +465,7 @@ export const vaccinationPaths: NonNullable = { requestParams: idPath, requestBody: { required: true, - content: { "application/json": { schema: vaccinationLinkSchema } }, + content: { "application/json": { schema: vaccinationLinkRequest } }, }, responses: { "200": { 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,