From 90ca2cd899a71850820ffbf2a92dbb86131560b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 10 Sep 2026 00:02:15 +0200 Subject: [PATCH] chore(api): regenerate the OpenAPI contract Picks up the managed-profile read and edit endpoints, the sex field on the create body, the record-scoped module map on the account payload, and the delegated cycle field on the record's module settings. Refs #939 --- docs/api/openapi.yaml | 189 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 173 insertions(+), 16 deletions(-) diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 72915fbc3..560c942eb 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -18446,6 +18446,82 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" /api/managed-profiles/{id}: + get: + tags: + - Account sharing + summary: Read a managed record's identity + description: "What the record is right now — the five fields its creation took. The read an edit form fills itself from, + and the reason it exists: `accountAccess.accounts` on the account payload carries the record's name and nothing + else, so a form offered from the Guardian's own panel had no way to show the timezone, the language or the date + of birth it was about to overwrite. Cookie transport only, an ACTOR surface (the profile is named in the path + and the caller acts as themselves, so it answers without switching into the record), and NOT step-up gated: this + is a read, it discloses to a Guardian only what they already administer, and the gate belongs to the acts." + parameters: + - in: path + name: id + schema: + type: string + required: true + responses: + "200": + description: The record as it stands. + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedProfileEnvelope" + "401": *a1 + "404": + description: "No such managed profile, or the caller is not one of its Guardians (`meta.errorCode: + managed_profile.not_found`). The two are byte-identical, so the refusal is not an enumeration oracle." + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + "422": *a4 + "429": *a5 + patch: + tags: + - Account sharing + summary: Change a managed record's identity + description: "Edit the record after it exists — the name that was a placeholder, the timezone that moved with the + household, the date of birth somebody finally has, the sex that decides whether the cycle module is on. Any + active Guardian may do it, from a cookie session with a fresh second factor: the same gate creation and deletion + carry, because the three acts mint, change and end an account that can never prove anything about itself. + Answers with the whole record, so a client re-renders from the server's version rather than from what it sent." + parameters: + - in: path + name: id + schema: + type: string + required: true + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/UpdateManagedProfileRequest" + responses: + "200": + description: The record as it now stands. + content: + application/json: + schema: + $ref: "#/components/schemas/ManagedProfileEnvelope" + "401": + description: No fresh second-factor proof, or none enrolled. Same unconditional gate as creation and deletion. + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + "404": + description: "No such managed profile, or the caller is not one of its Guardians (`meta.errorCode: + managed_profile.not_found`). The two are byte-identical." + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + "422": *a4 + "429": *a5 delete: tags: - Account sharing @@ -28012,6 +28088,14 @@ components: type: string minLength: 1 maxLength: 64 + gender: + anyOf: + - type: string + enum: + - MALE + - FEMALE + - OTHER + - type: "null" required: - displayName - locale @@ -28021,12 +28105,58 @@ components: The caller becomes its first Guardian in the same transaction, through an ordinary MANAGE grant with the new record as grantor, so the profile appears in `accountAccess.accounts` on the next `GET /api/auth/me` and is switched into like any other shared record. `displayName` is the only required text; `dateOfBirth` is an - optional `YYYY-MM-DD` and is never synthesised from a year. `locale` and `timezone` belong to the RECORD and not - to the Guardian reading it: they decide how the profile's own reminders are worded and when its day starts. - Strict — an unexpected field is a 422 rather than an ignored key. Cookie transport only and step-up gated + optional `YYYY-MM-DD` and is never synthesised from a year. `gender` is optional and takes the same three values + `PATCH /api/auth/me` takes for an ordinary account (`MALE`, `FEMALE`, `OTHER`) or null; absent and null both + mean not recorded, and the value is what the cycle module derives its default from, so a record created for a + child who does not need it starts with it off. `locale` and `timezone` belong to the RECORD and not to the + Guardian reading it: they decide how the profile's own reminders are worded and when its day starts. Strict — an + unexpected field is a 422 rather than an ignored key. Cookie transport only and step-up gated (`requireFreshMfa`, unconditional): a Bearer caller cannot mint a credential-less person and a permanent management relationship, and an account with no second factor enrolled is refused rather than waved through. Rate-limited to ten an hour per caller." + UpdateManagedProfileRequest: + type: object + properties: + displayName: + type: string + minLength: 1 + maxLength: 80 + dateOfBirth: + anyOf: + - type: string + format: date + pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))$ + - type: "null" + locale: + type: string + enum: + - de + - en + - es + - fr + - it + - pl + - ko + timezone: + type: string + minLength: 1 + maxLength: 64 + gender: + anyOf: + - type: string + enum: + - MALE + - FEMALE + - OTHER + - type: "null" + additionalProperties: false + description: "Change what a managed record is, from the Guardian's own panel. The same five fields creation takes, every + one of them optional, and at least one required — an empty body is a 422 rather than a no-op that audits a + change nobody made. Absence means leave the field alone; an explicit null on `dateOfBirth` or `gender` clears + it, and the two are different answers. Nothing else on the record is reachable here: credentials, provider + connections, notification routing and the health profile are not identity, and the wider configuration surface + is `PATCH /api/record-settings/{family}`, which is guardian-fenced and answers only while switched into the + record. Strict, cookie-only and step-up gated, like every act in this family." InviteManagedProfileGuardianRequest: type: object properties: @@ -28291,10 +28421,15 @@ components: vaccinations: type: boolean additionalProperties: false - required: - - modulePreferences + cycleTrackingEnabled: + type: boolean additionalProperties: false - description: Partial module toggle. Merged onto the record's current preferences; omitted keys keep their value. + description: "Partial module toggle. `modulePreferences` is merged onto the record's current preferences; omitted keys + keep their value. `cycleTrackingEnabled` is separate because `cycle` is a DELEGATED module: its state lives in + the record's own cycle profile and `modulePreferences.cycle` is refused, so this is the field that turns cycle + tracking on or off for the record — an explicit boolean overrides the default the record's sex would derive. + Both fields are optional and at least one is required; an empty body is a 422 rather than a save that changed + nothing." RecordNotificationSettingsPatch: type: object properties: @@ -43748,9 +43883,12 @@ components: - nutrients - vaccinations additionalProperties: false - description: Fully-resolved per-user module enable/disable map. Every toggleable module key is present. `false` means - the surface should disappear end-to-end (nav, dashboard, insights, …). `cycle` mirrors `cycleTrackingEnabled`; - `coach` mirrors the resolved per-user opt-out + operator master flag. + description: Fully-resolved module enable/disable map. Every toggleable module key is present. `false` means the surface + should disappear end-to-end (nav, dashboard, insights, …). `cycle` mirrors `cycleTrackingEnabled`; `coach` + mirrors the resolved per-user opt-out + operator master flag. On `GET /api/auth/me` it answers for the RECORD + the session is inside rather than for the caller, because every surface it gates shows the record's data; with + no switch — which is every native request, since the Bearer transport carries none — the two are the same + account and the payload is unchanged. PatchModulesResponse: type: object properties: @@ -66252,13 +66390,31 @@ components: description: The profile's account id — the value `POST /api/account/switch` and the `X-HealthLog-Account` selector take, and the `{id}` in this family's paths. displayName: - type: string + anyOf: + - type: string + - type: "null" dateOfBirth: anyOf: - type: string - type: "null" + description: "`YYYY-MM-DD`, or null. The same date-only shape the request takes — this is a calendar date, not an + instant, and publishing it with a time would invite a client to apply a zone to it." + gender: + anyOf: + - type: string + enum: + - MALE + - FEMALE + - OTHER + - type: "null" + description: "`User.gender` for this record. Null means not recorded, which is a real answer and not a missing one." locale: - type: string + anyOf: + - type: string + - type: "null" + description: The record's own language. Every profile this family creates carries one, because creation requires it; the + column is nullable, so null means a row written before that and a client renders its own language rather + than the word. timezone: type: string recordKind: @@ -66270,15 +66426,16 @@ components: - id - displayName - dateOfBirth + - gender - locale - timezone - recordKind additionalProperties: false - description: "A record somebody looks after, as its creation answers with it. There is deliberately no list endpoint: - the profiles a Guardian looks after are the `managed` entries of `accountAccess.accounts` on GET /api/auth/me, - because a Guardian's relationship to a profile IS a MANAGE grant. Re-read the account payload after any change - here; a client that refreshed only its own panel would leave the new record out of the switcher and the banner - until the next boot." + description: "A record somebody looks after, as its creation, its read and its edit all answer with it. There is + deliberately no list endpoint: the profiles a Guardian looks after are the `managed` entries of + `accountAccess.accounts` on GET /api/auth/me, because a Guardian's relationship to a profile IS a MANAGE grant. + Re-read the account payload after any change here; a client that refreshed only its own panel would leave the + new record out of the switcher and the banner until the next boot." DeletedManagedProfileEnvelope: type: object properties: