docs(openapi): publish the illness episode + day-log CRUD contract

Register /api/illness/episodes (list + create), the {id} resource
(read + edit + soft-delete), the {id}/resolve verb, and the
{id}/day-logs upsert in the OpenAPI route table, and add the Illness
tag. Request bodies + queries reuse the runtime Zod schemas from
validations/illness so the wire contract stays single-source; the
response shapes mirror the server-authoritative episode + day-log DTOs.
Regenerated docs/api/openapi.yaml; openapi:check is in sync.
This commit is contained in:
Marc-André Bombeck committed 2026-06-16 21:56:54 +02:00
1 parent 0338f18b50
commit c1d804809d
4 files changed
+954

No files matched your search

+622
View File
@@ -41,6 +41,7 @@ tags:
- name: Cycle
- name: MeasurementReminders
- name: Labs
- name: Illness
- name: Admin
- name: Meta
paths:
@@ -6167,6 +6168,373 @@ paths:
"404": *a9
"422": *a3
"429": *a4
/api/illness/episodes:
get:
tags:
- Illness
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.
parameters:
- in: query
name: limit
schema:
type: integer
minimum: 1
maximum: 100
- in: query
name: includeResolved
schema:
anyOf:
- type: string
const: "true"
- type: string
const: "false"
responses:
"200":
description: The caller's illness episodes.
content:
application/json:
schema:
$ref: "#/components/schemas/ListIllnessEpisodesEnvelope"
"401": *a1
"422": *a3
"429": *a4
post:
tags:
- Illness
summary: Open an illness episode (v1.18.1)
description: Creates one episode for the caller. `onsetAt` defaults to 'now' when omitted; the optional note is
AES-256-GCM encrypted before write. A `parentConditionId` must reference an owned, live episode. Audits as
`illness.episode.create`. Born-gated.
requestBody:
required: true
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
responses:
"201":
description: Episode created.
content:
application/json:
schema:
$ref: "#/components/schemas/CreateIllnessEpisodeEnvelope"
"401": *a1
"422": *a3
"429": *a4
/api/illness/episodes/{id}:
get:
tags:
- Illness
summary: Read a single illness episode (v1.18.1)
description: Returns the episode including its decrypted `note`. Owner-scoped; a cross-user or tombstoned id 404s. Born-gated.
parameters:
- in: path
name: id
schema:
type: string
required: true
responses:
"200":
description: The episode.
content:
application/json:
schema:
$ref: "#/components/schemas/GetIllnessEpisodeEnvelope"
"401": *a1
"404": &a11
description: Illness episode not found (or owned by another user).
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a3
"429": *a4
patch:
tags:
- Illness
summary: Edit an illness episode (v1.18.1)
description: Partial edit; omitted fields are left untouched. A re-parent must point at an owned, live episode (and
never the episode itself). Audits as `illness.episode.update`. Owner-scoped + born-gated.
parameters:
- in: path
name: id
schema:
type: string
required: true
requestBody:
required: true
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
responses:
"200":
description: Episode updated.
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateIllnessEpisodeEnvelope"
"401": *a1
"404": *a11
"422": *a3
"429": *a4
delete:
tags:
- Illness
summary: Soft-delete an illness episode (v1.18.1)
description: Stamps `deletedAt` (tombstone). Idempotent — a re-delete is a no-op. Returns 204 No Content. Audits as
`illness.episode.delete`. Owner-scoped + born-gated.
parameters:
- in: path
name: id
schema:
type: string
required: true
responses:
"204":
description: Soft-deleted (no body).
"401": *a1
"404": *a11
"422": *a3
"429": *a4
/api/illness/episodes/{id}/resolve:
patch:
tags:
- Illness
summary: Mark an illness episode recovered (v1.18.1)
description: Stamps `resolvedAt` (defaults to 'now' when the body omits it). A CHRONIC_ONGOING episode has no recovery
date and 422s with `errorCode:"illness.episode.chronic-no-resolve"`. Audits as `illness.episode.resolve`.
Owner-scoped + born-gated.
parameters:
- in: path
name: id
schema:
type: string
required: true
requestBody:
required: false
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)))$
responses:
"200":
description: Episode resolved.
content:
application/json:
schema:
$ref: "#/components/schemas/ResolveIllnessEpisodeEnvelope"
"401": *a1
"404": *a11
"422": *a3
"429": *a4
/api/illness/episodes/{id}/day-logs:
get:
tags:
- Illness
summary: Read one day-log for an episode (v1.18.1)
description: Returns the day-log for the episode + `date`, or `null` when nothing is logged that day (lets the log-day
sheet pre-fill). The parent episode must be owned + live. Born-gated.
parameters:
- in: path
name: id
schema:
type: string
required: true
- in: query
name: date
schema:
type: string
pattern: ^\d{4}-\d{2}-\d{2}$
required: true
responses:
"200":
description: The day-log, or null.
content:
application/json:
schema:
$ref: "#/components/schemas/GetIllnessDayLogEnvelope"
"401": *a1
"404": *a11
"422": *a3
"429": *a4
post:
tags:
- Illness
summary: Upsert an episode day-log (v1.18.1)
description: "Upserts the day's symptom / functional-impact / fever timeline row on `(episodeId, date)`: 201 on insert,
200 on update. The parent episode must be owned + live; the optional note is encrypted at rest. Audits as
`illness.day-log.upsert`. Born-gated."
parameters:
- in: path
name: id
schema:
type: string
required: true
requestBody:
required: true
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: 0
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
responses:
"200":
description: Existing day-log updated.
content:
application/json:
schema:
$ref: "#/components/schemas/UpsertIllnessDayLogUpdatedEnvelope"
"201":
description: New day-log created.
content:
application/json:
schema:
$ref: "#/components/schemas/UpsertIllnessDayLogCreatedEnvelope"
"401": *a1
"404": *a11
"422": *a3
"429": *a4
components:
schemas:
PasskeyLoginVerifyRequest:
@@ -17822,6 +18190,260 @@ components:
- data
- error
additionalProperties: false
ListIllnessEpisodesEnvelope:
type: object
properties:
data:
type: array
items:
$ref: "#/components/schemas/IllnessEpisode"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
IllnessEpisode:
type: object
properties:
id:
type: string
label:
type: string
type:
type: string
enum:
- INFECTION
- ALLERGY
- INJURY
- MENTAL_HEALTH
- AUTOIMMUNE
- CHRONIC
- OTHER
lifecycle:
type: string
enum:
- ACUTE
- CHRONIC_ONGOING
- RECURRING
- FLARE
onsetAt:
type: string
resolvedAt:
anyOf:
- type: string
- type: "null"
parentConditionId:
anyOf:
- type: string
- type: "null"
note:
anyOf:
- type: string
- type: "null"
createdAt:
type: string
updatedAt:
type: string
required:
- id
- label
- type
- lifecycle
- onsetAt
- resolvedAt
- parentConditionId
- note
- createdAt
- updatedAt
additionalProperties: false
description: A stored illness/condition episode. `note` is the decrypted free-text (or null on a key-rotation gap —
fail-soft, never 500). `resolvedAt` is null while the episode is open. `parentConditionId` links a
FLARE/RECURRING bout to its parent condition.
CreateIllnessEpisodeEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/IllnessEpisode"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
GetIllnessEpisodeEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/IllnessEpisode"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
UpdateIllnessEpisodeEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/IllnessEpisode"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
ResolveIllnessEpisodeEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/IllnessEpisode"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
GetIllnessDayLogEnvelope:
type: object
properties:
data:
anyOf:
- $ref: "#/components/schemas/IllnessDayLog"
- type: "null"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
IllnessDayLog:
type: object
properties:
id:
type: string
episodeId:
type: string
date:
type: string
functionalImpact:
anyOf:
- type: integer
minimum: -9007199254740991
maximum: 9007199254740991
- type: "null"
feverC:
anyOf:
- type: number
- type: "null"
symptoms:
type: array
items:
$ref: "#/components/schemas/IllnessSymptom"
note:
anyOf:
- type: string
- type: "null"
updatedAt:
type: string
required:
- id
- episodeId
- date
- functionalImpact
- feverC
- symptoms
- note
- updatedAt
additionalProperties: false
description: A stored day-log for an episode. `date` is the `YYYY-MM-DD` it covers. `functionalImpact` (0–3) and
`feverC` are plaintext; `symptoms` flattens the link rows; `note` is the decrypted free-text (or null).
IllnessSymptom:
type: object
properties:
key:
type: string
severity:
anyOf:
- type: integer
minimum: -9007199254740991
maximum: 9007199254740991
- type: "null"
required:
- key
- severity
additionalProperties: false
description: "A symptom link on a day-log: the catalog `key` plus an optional 0–3 severity (`null` = a plain presence link)."
UpsertIllnessDayLogUpdatedEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/IllnessDayLog"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
UpsertIllnessDayLogCreatedEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/IllnessDayLog"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
MedicationScheduleOutput:
type: object
properties: