diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml index 8638f44b0..e6762c399 100644 --- a/docs/api/openapi.yaml +++ b/docs/api/openapi.yaml @@ -9631,14 +9631,26 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchUnitPreferenceEnvelope" + "400": &a21 + description: The request body is not parseable JSON. Nothing was written. + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": *a9 - "413": + "413": &a22 description: Request body exceeds 1024 bytes. Nothing was written. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" + "415": &a23 + description: "The request did not carry `Content-Type: application/json`. Nothing was read and nothing was written." + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 /api/auth/me/glucose-unit: @@ -9689,14 +9701,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchGlucoseUnitEnvelope" + "400": *a21 "401": *a1 "403": *a9 - "413": - description: Request body exceeds 1024 bytes. Nothing was written. - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "413": *a22 + "415": *a23 "422": *a4 "429": *a5 /api/auth/me/documents-auto-ai-read: @@ -9752,14 +9761,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchDocumentsAutoAiReadEnvelope" + "400": *a21 "401": *a1 "403": *a9 - "413": - description: Request body exceeds 1024 bytes. Nothing was written. - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "413": *a22 + "415": *a23 "422": *a4 "429": *a5 /api/auth/me/injection-site-prefs: @@ -10061,14 +10067,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchLabsLocalOcrEnvelope" + "400": *a21 "401": *a1 "403": *a9 - "413": - description: Request body exceeds 1024 bytes. Nothing was written. - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "413": *a22 + "415": *a23 "422": *a4 "429": *a5 /api/auth/me/workout-routes: @@ -10109,14 +10112,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchWorkoutRoutesEnvelope" + "400": *a21 "401": *a1 "403": *a9 - "413": - description: Request body exceeds 1024 bytes. Nothing was written. - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "413": *a22 + "415": *a23 "422": *a4 "429": *a5 /api/auth/me/use-central-codex: @@ -10162,14 +10162,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PatchUseCentralCodexEnvelope" + "400": *a21 "401": *a1 "403": *a9 - "413": - description: Request body exceeds 1024 bytes. Nothing was written. - content: - application/json: - schema: - $ref: "#/components/schemas/ErrorEnvelope" + "413": *a22 + "415": *a23 "422": *a4 "429": *a5 /api/auth/me/locale: @@ -10376,7 +10373,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 - "503": &a23 + "503": &a26 description: "The AI capability could not be resolved (its inputs could not be read) and the answer failed closed: `meta.errorCode` = `ai.unavailable`, `meta.reason` = `check_failed`. Retry later; no data depends on it." content: @@ -11592,7 +11589,7 @@ paths: $ref: "#/components/schemas/CorrectAnamnesisFactResponse" "401": *a1 "403": *a9 - "404": &a21 + "404": &a24 description: No current revision with this id exists for the authenticated account. content: application/json: @@ -11629,7 +11626,7 @@ paths: $ref: "#/components/schemas/RemoveAnamnesisFactResponse" "401": *a1 "403": *a9 - "404": *a21 + "404": *a24 "409": description: Anamnesis fact changed since it was loaded (optimistic-concurrency conflict). No write happened. Re-read the resource, re-apply the user's change against the fresh state, and resend with the new token. @@ -12168,7 +12165,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteList" "401": *a1 - "403": &a22 + "403": &a25 description: The session is valid but its account is not an administrator. `meta.errorCode` = `auth.admin.required`. content: application/json: @@ -12209,7 +12206,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteCreated" "401": *a1 - "403": *a22 + "403": *a25 "422": *a4 "429": *a5 /api/admin/invites/{id}: @@ -12234,7 +12231,7 @@ paths: schema: $ref: "#/components/schemas/AdminInviteRevoked" "401": *a1 - "403": *a22 + "403": *a25 "422": *a4 "429": *a5 /api/insights/chat/messages/{id}/feedback: @@ -12290,7 +12287,7 @@ paths: schema: $ref: "#/components/schemas/AdminMeasurementMaintenanceStatusResponse" "401": *a1 - "403": *a22 + "403": *a25 "422": *a4 "429": *a5 post: @@ -12601,7 +12598,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "503": *a23 + "503": *a26 post: tags: - Insights @@ -14062,7 +14059,7 @@ paths: $ref: "#/components/schemas/MoodCustomTagPatchEnvelope" "401": *a1 "403": *a9 - "404": &a24 + "404": &a27 description: Not found / not owned by the caller. content: application/json: @@ -14098,7 +14095,7 @@ paths: $ref: "#/components/schemas/MoodCustomTagDeleteEnvelope" "401": *a1 "403": *a9 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 /api/mood/tags/{key}/hidden: @@ -14135,7 +14132,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": *a9 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 /api/mood/tags/groups: @@ -14190,7 +14187,7 @@ paths: $ref: "#/components/schemas/MoodTagGroupPatchEnvelope" "401": *a1 "403": *a9 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 delete: @@ -14222,7 +14219,7 @@ paths: $ref: "#/components/schemas/MoodTagGroupDeleteEnvelope" "401": *a1 "403": *a9 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 /api/mood/tags/layout: @@ -14390,7 +14387,7 @@ paths: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 "403": *a15 - "409": &a25 + "409": &a28 description: A mood entry with the same `(date, moodLoggedAt)` instant already exists. `meta.errorCode` = `mood.duplicate_timestamp`. Only a request without `externalId` can answer this — with one, the write upserts in place. @@ -14422,7 +14419,7 @@ paths: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 "403": *a15 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 put: @@ -14457,8 +14454,8 @@ paths: $ref: "#/components/schemas/MoodEntryEnvelope" "401": *a1 "403": *a15 - "404": *a24 - "409": *a25 + "404": *a27 + "409": *a28 "422": *a4 "429": *a5 delete: @@ -14484,7 +14481,7 @@ paths: $ref: "#/components/schemas/MoodEntryDeleteEnvelope" "401": *a1 "403": *a15 - "404": *a24 + "404": *a27 "422": *a4 "429": *a5 /api/mood-entries/restore: @@ -14635,7 +14632,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PutNtfySettingsResponse" - "400": &a26 + "400": &a29 description: Body is not parseable JSON. content: application/json: @@ -14650,13 +14647,13 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "413": &a27 + "413": &a30 description: Body exceeds 64 KiB. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "415": &a28 + "415": &a31 description: Content-Type is not `application/json`. content: application/json: @@ -14717,7 +14714,7 @@ paths: application/json: schema: $ref: "#/components/schemas/PutTelegramSettingsResponse" - "400": *a26 + "400": *a29 "401": *a1 "403": description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the @@ -14733,8 +14730,8 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "413": *a27 - "415": *a28 + "413": *a30 + "415": *a31 "422": description: Body matched neither accepted shape; or `enabled` is true without both a bot token and a chat id; or the deployment's `APP_URL` is not a public HTTPS host on port 80/88/443/8443; or Telegram refused the webhook @@ -14789,11 +14786,11 @@ paths: application/json: schema: $ref: "#/components/schemas/PutWebhookSettingsResponse" - "400": *a26 + "400": *a29 "401": *a1 "403": *a9 - "413": *a27 - "415": *a28 + "413": *a30 + "415": *a31 "422": description: Body matched neither accepted shape, or `enabled` is true while no webhook URL is stored; or the URL is on a private network the operator has not listed in `NOTIFICATION_PRIVATE_ORIGINS` (`meta.errorCode` = @@ -15458,7 +15455,7 @@ paths: anyOf: - $ref: "#/components/schemas/ConsentLatestForKindEnvelope" - $ref: "#/components/schemas/ConsentLatestByKindEnvelope" - "400": &a29 + "400": &a32 description: "`kind` was present but is not one of `ai_full`, `ai_insights_only`, `ai_coach`, `ai_extraction`. Multi-issue envelope; nothing was read or revoked. The consent family answers 400 here where the rest of the API answers 422." @@ -15519,7 +15516,7 @@ paths: anyOf: - $ref: "#/components/schemas/ConsentRevokeForKindEnvelope" - $ref: "#/components/schemas/ConsentRevokeAllEnvelope" - "400": *a29 + "400": *a32 "401": *a1 "403": *a9 "422": *a4 @@ -16312,7 +16309,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderEnvelope" "401": *a1 "403": *a15 - "404": &a30 + "404": &a33 description: Measurement reminder not found / not owned. content: application/json: @@ -16350,7 +16347,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderPatchEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 delete: @@ -16373,7 +16370,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderDeleteEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/satisfy: @@ -16398,7 +16395,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderSatisfyEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/complete: @@ -16426,7 +16423,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderCompleteEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/skip: @@ -16456,7 +16453,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderSkipEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/snooze: @@ -16490,7 +16487,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderSnoozeEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/measurement-reminders/{id}/history: @@ -16531,7 +16528,7 @@ paths: $ref: "#/components/schemas/MeasurementReminderHistoryEnvelope" "401": *a1 "403": *a15 - "404": *a30 + "404": *a33 "422": *a4 "429": *a5 /api/labs: @@ -16693,7 +16690,7 @@ paths: $ref: "#/components/schemas/GetLabResultResponse" "401": *a1 "403": *a15 - "404": &a31 + "404": &a34 description: Lab result not found (or owned by another user). content: application/json: @@ -16728,7 +16725,7 @@ paths: $ref: "#/components/schemas/UpdateLabResultResponse" "401": *a1 "403": *a15 - "404": *a31 + "404": *a34 "422": *a4 "429": *a5 delete: @@ -16751,7 +16748,7 @@ paths: $ref: "#/components/schemas/DeleteLabResultResponse" "401": *a1 "403": *a15 - "404": *a31 + "404": *a34 "422": *a4 "429": *a5 /api/labs/restore: @@ -16839,7 +16836,7 @@ paths: schema: $ref: "#/components/schemas/OcrTextExtractRequest" responses: - "202": &a41 + "202": &a44 description: Queued for the background worker. Every refusal the route can answer quickly (capability, provider, rate limit, budget, validation) has already been answered; anything that goes wrong from here is reported on the run. @@ -17020,7 +17017,7 @@ paths: $ref: "#/components/schemas/GetBiomarkerResponse" "401": *a1 "403": *a15 - "404": &a32 + "404": &a35 description: "Biomarker not found (or owned by another user). `meta.errorCode` = `labs.biomarker.notFound`. Since v1.43 a marker that was merged into another answers with `meta.mergedInto` naming the marker it went into: drop the cached marker and open that one." @@ -17062,7 +17059,7 @@ paths: $ref: "#/components/schemas/UpdateBiomarkerResponse" "401": *a1 "403": *a15 - "404": *a32 + "404": *a35 "409": description: "`labs.biomarker.duplicate`: a rename collides with another of the caller's markers. `labs.biomarker.alias.taken` (v1.43): one of `aliases` is already the name or an alias of another marker; @@ -17096,7 +17093,7 @@ paths: $ref: "#/components/schemas/DeleteBiomarkerResponse" "401": *a1 "403": *a15 - "404": *a32 + "404": *a35 "422": *a4 "429": *a5 /api/biomarkers/{id}/merge: @@ -17134,7 +17131,7 @@ paths: $ref: "#/components/schemas/MergeBiomarkersResponse" "401": *a1 "403": *a15 - "404": *a32 + "404": *a35 "409": *a7 "422": description: "`labs.biomarker.merge.invalid`: a malformed body, or the target among the sources (Zod issues in @@ -17359,7 +17356,7 @@ paths: $ref: "#/components/schemas/GetEncounterEnvelope" "401": *a1 "403": *a15 - "404": &a33 + "404": &a36 description: Visit not found (or owned by another user). content: application/json: @@ -17393,7 +17390,7 @@ paths: $ref: "#/components/schemas/UpdateEncounterEnvelope" "401": *a1 "403": *a15 - "404": *a33 + "404": *a36 "422": *a4 "429": *a5 delete: @@ -17418,7 +17415,7 @@ paths: $ref: "#/components/schemas/DeleteEncounterEnvelope" "401": *a1 "403": *a15 - "404": *a33 + "404": *a36 "422": *a4 "429": *a5 /api/encounters/{id}/restore: @@ -17444,7 +17441,7 @@ paths: $ref: "#/components/schemas/RestoreEncounterEnvelope" "401": *a1 "403": *a15 - "404": *a33 + "404": *a36 "422": *a4 "429": *a5 /api/encounters/{id}/links: @@ -17476,7 +17473,7 @@ paths: $ref: "#/components/schemas/LinkEncounterEnvelope" "401": *a1 "403": *a9 - "404": *a33 + "404": *a36 "422": *a4 "429": *a5 delete: @@ -17506,7 +17503,7 @@ paths: $ref: "#/components/schemas/UnlinkEncounterEnvelope" "401": *a1 "403": *a9 - "404": *a33 + "404": *a36 "422": *a4 "429": *a5 /api/practitioners: @@ -17595,7 +17592,7 @@ paths: $ref: "#/components/schemas/GetPractitionerEnvelope" "401": *a1 "403": *a15 - "404": &a34 + "404": &a37 description: Practitioner not found (or owned by another user). content: application/json: @@ -17629,7 +17626,7 @@ paths: $ref: "#/components/schemas/UpdatePractitionerEnvelope" "401": *a1 "403": *a15 - "404": *a34 + "404": *a37 "422": *a4 "429": *a5 delete: @@ -17654,7 +17651,7 @@ paths: $ref: "#/components/schemas/DeletePractitionerEnvelope" "401": *a1 "403": *a15 - "404": *a34 + "404": *a37 "422": *a4 "429": *a5 /api/practitioners/{id}/restore: @@ -17680,7 +17677,7 @@ paths: $ref: "#/components/schemas/RestorePractitionerEnvelope" "401": *a1 "403": *a15 - "404": *a34 + "404": *a37 "422": *a4 "429": *a5 /api/vaccinations/custom: @@ -17724,7 +17721,7 @@ paths: $ref: "#/components/schemas/CreateCustomVaccineEnvelope" "401": *a1 "403": *a15 - "409": &a35 + "409": &a38 description: A live definition on the record already carries this name, compared without case (`vaccination.custom.name-taken`). content: @@ -17761,13 +17758,13 @@ paths: $ref: "#/components/schemas/UpdateCustomVaccineEnvelope" "401": *a1 "403": *a15 - "404": &a36 + "404": &a39 description: No live definition with this id on the record (`vaccination.custom.not-found`). content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "409": *a35 + "409": *a38 "422": *a4 "429": *a5 delete: @@ -17792,7 +17789,7 @@ paths: $ref: "#/components/schemas/DeleteCustomVaccineEnvelope" "401": *a1 "403": *a15 - "404": *a36 + "404": *a39 "422": *a4 "429": *a5 /api/vaccinations: @@ -17904,7 +17901,7 @@ paths: $ref: "#/components/schemas/GetVaccinationEnvelope" "401": *a1 "403": *a15 - "404": &a37 + "404": &a40 description: Vaccination not found (or owned by another user). content: application/json: @@ -17939,7 +17936,7 @@ paths: $ref: "#/components/schemas/UpdateVaccinationEnvelope" "401": *a1 "403": *a15 - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 delete: @@ -17964,7 +17961,7 @@ paths: $ref: "#/components/schemas/DeleteVaccinationEnvelope" "401": *a1 "403": *a15 - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 /api/vaccinations/{id}/restore: @@ -17989,7 +17986,7 @@ paths: $ref: "#/components/schemas/RestoreVaccinationEnvelope" "401": *a1 "403": *a15 - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 /api/vaccinations/{id}/booster: @@ -18057,7 +18054,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 /api/vaccinations/suggest: @@ -18118,7 +18115,7 @@ paths: $ref: "#/components/schemas/LinkVaccinationEnvelope" "401": *a1 "403": *a9 - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 delete: @@ -18148,7 +18145,7 @@ paths: $ref: "#/components/schemas/UnlinkVaccinationEnvelope" "401": *a1 "403": *a9 - "404": *a37 + "404": *a40 "422": *a4 "429": *a5 /api/illness/episodes: @@ -18263,7 +18260,7 @@ paths: $ref: "#/components/schemas/GetIllnessEpisodeEnvelope" "401": *a1 "403": *a15 - "404": &a38 + "404": &a41 description: Illness episode not found (or owned by another user). content: application/json: @@ -18298,7 +18295,7 @@ paths: $ref: "#/components/schemas/UpdateIllnessEpisodeEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 delete: @@ -18322,7 +18319,7 @@ paths: $ref: "#/components/schemas/DeleteIllnessEpisodeEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 /api/illness/episodes/{id}/restore: @@ -18350,7 +18347,7 @@ paths: $ref: "#/components/schemas/RestoreIllnessEpisodeEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "409": *a7 "422": *a4 "429": *a5 @@ -18383,7 +18380,7 @@ paths: $ref: "#/components/schemas/ResolveIllnessEpisodeEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 /api/illness/episodes/{id}/day-logs: @@ -18434,7 +18431,7 @@ paths: $ref: "#/components/schemas/GetIllnessDayLogEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 post: @@ -18471,7 +18468,7 @@ paths: $ref: "#/components/schemas/UpsertIllnessDayLogCreatedEnvelope" "401": *a1 "403": *a15 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 /api/illness/episodes/{id}/correlation: @@ -18499,7 +18496,7 @@ paths: $ref: "#/components/schemas/IllnessCorrelationEnvelope" "401": *a1 "403": *a9 - "404": *a38 + "404": *a41 "422": *a4 "429": *a5 /api/illness/insights: @@ -18716,7 +18713,7 @@ paths: $ref: "#/components/schemas/GetAllergyEnvelope" "401": *a1 "403": *a15 - "404": &a39 + "404": &a42 description: Allergy not found (or owned by another user). content: application/json: @@ -18750,7 +18747,7 @@ paths: $ref: "#/components/schemas/UpdateAllergyEnvelope" "401": *a1 "403": *a15 - "404": *a39 + "404": *a42 "422": *a4 "429": *a5 delete: @@ -18774,7 +18771,7 @@ paths: $ref: "#/components/schemas/DeleteAllergyEnvelope" "401": *a1 "403": *a15 - "404": *a39 + "404": *a42 "422": *a4 "429": *a5 /api/family-history: @@ -18848,7 +18845,7 @@ paths: $ref: "#/components/schemas/GetFamilyHistoryEnvelope" "401": *a1 "403": *a15 - "404": &a40 + "404": &a43 description: Family history entry not found (or owned by another user). content: application/json: @@ -18882,7 +18879,7 @@ paths: $ref: "#/components/schemas/UpdateFamilyHistoryEnvelope" "401": *a1 "403": *a15 - "404": *a40 + "404": *a43 "422": *a4 "429": *a5 delete: @@ -18906,7 +18903,7 @@ paths: $ref: "#/components/schemas/DeleteFamilyHistoryEnvelope" "401": *a1 "403": *a15 - "404": *a40 + "404": *a43 "422": *a4 "429": *a5 /api/documents/inbound: @@ -19579,7 +19576,7 @@ paths: required: true schema: type: string - - &a42 + - &a45 name: Prefer in: header required: false @@ -19604,9 +19601,9 @@ paths: application/json: schema: $ref: "#/components/schemas/InboundExtractEnvelope" - "202": *a41 + "202": *a44 "401": *a1 - "403": &a43 + "403": &a46 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: AI refusal: the `documentAi` capability is unavailable for this record. The envelope carries `meta.errorCode`, `meta.capability` and @@ -19632,7 +19629,7 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "422": *a4 "429": *a5 - "503": &a44 + "503": &a47 description: "`ai.unavailable` — the inputs behind the AI capability could not be read, and the answer failed closed. Nothing was sent. Retry later." content: @@ -19662,7 +19659,7 @@ paths: required: true schema: type: string - - *a42 + - *a45 requestBody: required: false content: @@ -19698,12 +19695,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentSuggestEnvelope" - "202": *a41 + "202": *a44 "401": *a1 - "403": *a43 + "403": *a46 "422": *a4 "429": *a5 - "503": *a44 + "503": *a47 /api/documents/inbound/{id}/summary: post: tags: @@ -19753,7 +19750,7 @@ paths: type: boolean default: false description: With `persist=true`, explicitly replace an existing stored summary. - - *a42 + - *a45 requestBody: required: false content: @@ -19789,12 +19786,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentSummaryEnvelope" - "202": *a41 + "202": *a44 "401": *a1 - "403": *a43 + "403": *a46 "422": *a4 "429": *a5 - "503": *a44 + "503": *a47 /api/documents/inbound/{id}/index: post: tags: @@ -19820,7 +19817,7 @@ paths: required: true schema: type: string - - *a42 + - *a45 requestBody: required: false content: @@ -19856,12 +19853,12 @@ paths: application/json: schema: $ref: "#/components/schemas/DocumentIndexEnvelope" - "202": *a41 + "202": *a44 "401": *a1 - "403": *a43 + "403": *a46 "422": *a4 "429": *a5 - "503": *a44 + "503": *a47 /api/documents/inbound/{id}/chat: post: tags: @@ -19901,10 +19898,10 @@ paths: type: string description: "SSE frames: `data: \\n\\n`. See the operation description for the per-`type` frame shapes." "401": *a1 - "403": *a43 + "403": *a46 "422": *a4 "429": *a5 - "503": *a44 + "503": *a47 get: tags: - Documents @@ -19968,10 +19965,10 @@ paths: schema: $ref: "#/components/schemas/DocumentReindexEnvelope" "401": *a1 - "403": *a43 + "403": *a46 "422": *a4 "429": *a5 - "503": *a44 + "503": *a47 /api/documents/inbound/{id}/facts/{factId}: patch: tags: @@ -20081,7 +20078,7 @@ paths: $ref: "#/components/schemas/CreateCustomMetricResponse" "401": *a1 "403": *a9 - "409": &a46 + "409": &a49 description: A custom metric with this name already exists for the caller. content: application/json: @@ -20110,7 +20107,7 @@ paths: $ref: "#/components/schemas/GetCustomMetricResponse" "401": *a1 "403": *a15 - "404": &a45 + "404": &a48 description: Custom metric not found (or owned by another user). content: application/json: @@ -20145,8 +20142,8 @@ paths: $ref: "#/components/schemas/UpdateCustomMetricResponse" "401": *a1 "403": *a9 - "404": *a45 - "409": *a46 + "404": *a48 + "409": *a49 "422": *a4 "429": *a5 delete: @@ -20170,7 +20167,7 @@ paths: $ref: "#/components/schemas/DeleteCustomMetricResponse" "401": *a1 "403": *a9 - "404": *a45 + "404": *a48 "422": *a4 "429": *a5 /api/custom-metrics/{id}/entries: @@ -20214,7 +20211,7 @@ paths: $ref: "#/components/schemas/ListCustomMetricEntriesEnvelope" "401": *a1 "403": *a15 - "404": *a45 + "404": *a48 "422": *a4 "429": *a5 post: @@ -20244,7 +20241,7 @@ paths: $ref: "#/components/schemas/CreateCustomMetricEntryResponse" "401": *a1 "403": *a9 - "404": *a45 + "404": *a48 "409": *a7 "422": *a4 "429": *a16 @@ -20281,7 +20278,7 @@ paths: $ref: "#/components/schemas/UpdateCustomMetricEntryResponse" "401": *a1 "403": *a9 - "404": &a47 + "404": &a50 description: Custom metric entry not found (or owned by another user). content: application/json: @@ -20315,7 +20312,7 @@ paths: $ref: "#/components/schemas/DeleteCustomMetricEntryResponse" "401": *a1 "403": *a9 - "404": *a47 + "404": *a50 "422": *a4 "429": *a5 /api/custom-metrics/{id}/entries/restore: @@ -20623,7 +20620,7 @@ paths: schema: $ref: "#/components/schemas/NutrientCatalogEnvelope" "401": *a1 - "403": &a48 + "403": &a51 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: The opt-in `nutrients` module is @@ -20772,7 +20769,7 @@ paths: schema: $ref: "#/components/schemas/NutrientOverviewEnvelope" "401": *a1 - "403": *a48 + "403": *a51 "422": *a4 "429": *a5 /api/nutrients/daily: @@ -20809,7 +20806,7 @@ paths: schema: $ref: "#/components/schemas/NutrientDailySeriesEnvelope" "401": *a1 - "403": *a48 + "403": *a51 "422": *a4 "429": *a5 /api/nutrients/water: @@ -20838,7 +20835,7 @@ paths: schema: $ref: "#/components/schemas/NutrientWaterWriteEnvelope" "401": *a1 - "403": *a48 + "403": *a51 "409": *a7 "422": *a4 "429": *a5 @@ -21359,7 +21356,7 @@ paths: application/json: schema: $ref: "#/components/schemas/WithingsCredentialsUpdateEnvelope" - "400": &a49 + "400": &a52 description: Body is not parseable JSON. content: application/json: @@ -21367,19 +21364,19 @@ paths: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 "403": *a9 - "413": &a50 + "413": &a53 description: Body exceeds 64 KiB. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "415": &a51 + "415": &a54 description: Content-Type is not `application/json`. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": &a52 + "422": &a55 description: "`clientId` and `clientSecret` are both required and each must be 1–200 characters. The envelope carries a single fixed message, not per-field issues." content: @@ -21570,12 +21567,12 @@ paths: application/json: schema: $ref: "#/components/schemas/FitbitCredentialsUpdateEnvelope" - "400": *a49 + "400": *a52 "401": *a1 "403": *a9 - "413": *a50 - "415": *a51 - "422": *a52 + "413": *a53 + "415": *a54 + "422": *a55 "429": *a5 delete: tags: @@ -21730,12 +21727,12 @@ paths: application/json: schema: $ref: "#/components/schemas/GoogleHealthCredentialsUpdateEnvelope" - "400": *a49 + "400": *a52 "401": *a1 "403": *a9 - "413": *a50 - "415": *a51 - "422": *a52 + "413": *a53 + "415": *a54 + "422": *a55 "429": *a5 delete: tags: @@ -21901,12 +21898,12 @@ paths: application/json: schema: $ref: "#/components/schemas/OuraCredentialsUpdateEnvelope" - "400": *a49 + "400": *a52 "401": *a1 "403": *a9 - "413": *a50 - "415": *a51 - "422": *a52 + "413": *a53 + "415": *a54 + "422": *a55 "429": *a5 delete: tags: @@ -21986,21 +21983,21 @@ paths: $ref: "#/components/schemas/OuraTestEnvelope" "401": *a1 "403": *a9 - "422": &a53 + "422": &a56 description: No usable connection for this provider — nothing stored, or the stored grant could no longer be resolved into an access token. `meta.errorCode` = `not_configured`. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "429": &a54 + "429": &a57 description: More than 5 requests in 60 s from this user. `meta.errorCode` = `rate_limited_self` — this is the local limiter, distinct from a `rate_limited` 502 which is the provider's. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "502": &a55 + "502": &a58 description: "The probe did not complete. `meta.errorCode` names the class: `credentials_rejected` (the provider answered 401/403 — the stored grant is gone), `rate_limited` (the PROVIDER answered 429, which is not this endpoint's own limiter), `upstream_error` (5xx), `timeout` (no answer within 5 s), `connection_failed` @@ -22046,12 +22043,12 @@ paths: application/json: schema: $ref: "#/components/schemas/PolarCredentialsUpdateEnvelope" - "400": *a49 + "400": *a52 "401": *a1 "403": *a9 - "413": *a50 - "415": *a51 - "422": *a52 + "413": *a53 + "415": *a54 + "422": *a55 "429": *a5 delete: tags: @@ -22131,9 +22128,9 @@ paths: $ref: "#/components/schemas/PolarTestEnvelope" "401": *a1 "403": *a9 - "422": *a53 - "429": *a54 - "502": *a55 + "422": *a56 + "429": *a57 + "502": *a58 /api/strava/credentials: get: tags: @@ -22170,12 +22167,12 @@ paths: application/json: schema: $ref: "#/components/schemas/StravaCredentialsUpdateEnvelope" - "400": *a49 + "400": *a52 "401": *a1 "403": *a9 - "413": *a50 - "415": *a51 - "422": *a52 + "413": *a53 + "415": *a54 + "422": *a55 "429": *a5 delete: tags: @@ -22257,9 +22254,9 @@ paths: $ref: "#/components/schemas/StravaTestEnvelope" "401": *a1 "403": *a9 - "422": *a53 - "429": *a54 - "502": *a55 + "422": *a56 + "429": *a57 + "502": *a58 /api/integrations/status: get: tags: @@ -22297,7 +22294,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a54 + "429": *a57 /api/integrations/fitbit/test: post: tags: @@ -22323,9 +22320,9 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a53 - "429": *a54 - "502": *a55 + "422": *a56 + "429": *a57 + "502": *a58 /api/integrations/google-health/resume: post: tags: @@ -22343,7 +22340,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a54 + "429": *a57 /api/integrations/google-health/test: post: tags: @@ -22377,9 +22374,9 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a53 - "429": *a54 - "502": *a55 + "422": *a56 + "429": *a57 + "502": *a58 /api/integrations/whoop/resume: post: tags: @@ -22397,7 +22394,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a54 + "429": *a57 /api/integrations/whoop/test: post: tags: @@ -22415,9 +22412,9 @@ paths: $ref: "#/components/schemas/WhoopIntegrationTestEnvelope" "401": *a1 "403": *a9 - "422": *a53 - "429": *a54 - "502": *a55 + "422": *a56 + "429": *a57 + "502": *a58 /api/integrations/withings/resume: post: tags: @@ -22436,7 +22433,7 @@ paths: "401": *a1 "403": *a9 "422": *a4 - "429": *a54 + "429": *a57 /api/integrations/withings/test: post: tags: @@ -22455,8 +22452,8 @@ paths: $ref: "#/components/schemas/WithingsIntegrationTestEnvelope" "401": *a1 "403": *a9 - "422": *a53 - "429": *a54 + "422": *a56 + "429": *a57 "502": description: "The probe did not complete. The shared codes apply (`credentials_rejected`, `rate_limited`, `upstream_error`, `timeout`, `connection_failed`) and this path adds one of its own: @@ -22874,7 +22871,7 @@ paths: schema: $ref: "#/components/schemas/RecordActivityEnvelope" "401": *a1 - "403": &a56 + "403": &a59 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Refused. `meta.errorCode` is `sharing.not_permitted` when the request was made while acting on another account (grant management is never @@ -22901,7 +22898,7 @@ paths: schema: $ref: "#/components/schemas/AccountGrantListEnvelope" "401": *a1 - "403": *a56 + "403": *a59 "422": *a4 "429": *a5 post: @@ -23005,7 +23002,7 @@ paths: schema: $ref: "#/components/schemas/AcceptedAccountGrantEnvelope" "401": *a1 - "403": *a56 + "403": *a59 "404": description: No such invitation for this account. content: @@ -23049,7 +23046,7 @@ paths: schema: $ref: "#/components/schemas/RevokedAccountGrantEnvelope" "401": *a1 - "403": *a56 + "403": *a59 "404": description: No such grant on this account's record. content: @@ -23090,7 +23087,7 @@ paths: schema: $ref: "#/components/schemas/RenouncedAccountGrantEnvelope" "401": *a1 - "403": *a56 + "403": *a59 "404": description: No such grant held by this account. content: @@ -23482,7 +23479,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "403": *a56 + "403": *a59 "409": description: "The record this browser session is on is not the record the request asserted. `meta.errorCode` is `sharing.session.changed`. It is not a refusal of access — the caller may well be entitled to both records — @@ -23871,7 +23868,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectionListEnvelope" "401": *a1 - "403": &a57 + "403": &a60 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: The operator has disabled the API instance-wide. Every route in this module refuses while that setting is off, for a cookie session as well as @@ -23905,7 +23902,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectionRevokeEnvelope" "401": *a1 - "403": *a57 + "403": *a60 "404": description: No live connection with this id for this caller — also the answer for an already-revoked one, so a repeat revoke is 404 rather than an idempotent 200. @@ -23932,7 +23929,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectorTokenListEnvelope" "401": *a1 - "403": *a57 + "403": *a60 "422": *a4 "429": *a5 post: @@ -23965,7 +23962,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "401": *a1 - "403": *a57 + "403": *a60 "413": description: Body exceeds 16 KiB. content: @@ -24008,7 +24005,7 @@ paths: schema: $ref: "#/components/schemas/McpConnectorTokenRevokeEnvelope" "401": *a1 - "403": *a57 + "403": *a60 "404": description: No `health:read` token with this id for this caller. An already-revoked token is still found, so a repeat revoke answers 200. @@ -24070,7 +24067,7 @@ paths: record (audited). Fitbit is not offered: its classic API is retired. Cookie session, while acting on a managed record the caller guards." parameters: - - &a58 + - &a61 name: provider in: path required: true @@ -24117,7 +24114,7 @@ paths: Audited against the record with the Guardian as the actor. Cookie session or Bearer, while acting on a managed record the caller guards." parameters: - - *a58 + - *a61 responses: "200": description: The provider is disconnected. @@ -24143,7 +24140,7 @@ paths: description: Returns the named family's resolved settings for the record the session is acting on. Cookie session or Bearer, while acting on a managed record the caller guards. parameters: - - &a59 + - &a62 name: family in: path required: true @@ -24187,7 +24184,7 @@ paths: ride the audit row. Body capped at 64 KiB. Cookie session or Bearer, while acting on a managed record the caller guards. parameters: - - *a59 + - *a62 requestBody: required: true content: @@ -24814,13 +24811,13 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupDocument" - "403": &a60 + "403": &a63 description: Caller is not an admin. `requireAdmin()` is cookie-only, so a Bearer token lands here whatever its scope. content: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "404": &a61 + "404": &a64 description: No stored copy with this id. content: application/json: @@ -24894,8 +24891,8 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "403": *a60 - "404": *a61 + "403": *a63 + "404": *a64 "409": description: A restore of the same account is already queued or running (`meta.errorCode` = `backup.restore.active`, `meta.jobId` names it), the stored copy was replaced by a newer one while it was being checked @@ -24939,7 +24936,7 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupRestoreJobListResponse" - "403": *a60 + "403": *a63 /api/admin/backups/restores/{jobId}: get: tags: @@ -24962,7 +24959,7 @@ paths: application/json: schema: $ref: "#/components/schemas/AdminBackupRestoreJobResponse" - "403": *a60 + "403": *a63 "404": description: No restore job with this id. content: @@ -24981,7 +24978,7 @@ paths: request leaves the server. Needs neither the Documents module nor the list: with the list unset, `available` is false and any connection still stored is listed (with `originAllowed: false`) so it can be removed. Cookie session only; not delegable." - security: &a62 + security: &a65 - cookieAuth: [] responses: "200": @@ -24991,7 +24988,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourcesStatusEnvelope" "401": *a1 - "403": &a63 + "403": &a66 description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Refused before any request to the source: `documents.sources.browserOnly` for a Bearer caller, `sharing.not_permitted` while acting on another @@ -25016,9 +25013,9 @@ paths: `documents.sources.tokenRequired`), so a stored token is never sent to an address it was not saved for. A Papra organization id is letters, digits, `_` and `-`. The token is stored encrypted (AES-256-GCM) and never returned. Ten saves and tests a minute per person. Cookie session only; not delegable." - security: *a62 + security: *a65 parameters: - - &a64 + - &a67 name: system in: path required: true @@ -25057,8 +25054,8 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceConnectionEnvelope" "401": *a1 - "403": *a63 - "404": &a65 + "403": *a66 + "404": &a68 description: "`documents.sources.unavailable`: the operator has not listed any origin in `DOCUMENT_SOURCE_ORIGINS`, so the picker is off. `documents.sources.notConnected`: no connection is saved for this system. `documents.sources.notFound`: the source has no such document, or (Papra) no such organization, including @@ -25077,7 +25074,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "502": &a66 + "502": &a69 description: "The source could not answer usefully, with the reason in `meta.errorCode` and, when the source answered at all, its HTTP status in `meta.upstreamStatus` (its body is never passed on): `documents.sources.unreachable` (no connection, timeout, or a destination refused at dial time), `documents.sources.redirected` (the source @@ -25096,9 +25093,9 @@ paths: description: Deletes the caller's connection for this system, and its token with it. Works even after the operator removed the origin from the list, and with the Documents module switched off. `disconnected` is false when there was nothing to delete. Cookie session only; not delegable. - security: *a62 + security: *a65 parameters: - - *a64 + - *a67 responses: "200": description: Removed, or there was nothing to remove. @@ -25107,7 +25104,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceDisconnectEnvelope" "401": *a1 - "403": *a63 + "403": *a66 "422": *a4 "429": *a5 /api/documents/sources/{system}/test: @@ -25118,9 +25115,9 @@ paths: description: One request to the source with the stored token. Answers `ok` with the round-trip time and stamps `lastVerifiedAt`, or the reason in `meta.errorCode`. Shares the save's allowance of ten a minute. Cookie session only; not delegable. - security: *a62 + security: *a65 parameters: - - *a64 + - *a67 responses: "200": description: The source answered. @@ -25129,9 +25126,9 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceTestEnvelope" "401": *a1 - "403": *a63 - "404": *a65 - "422": &a67 + "403": *a66 + "404": *a68 + "422": &a70 description: "`documents.sources.originNotAllowed`: the base address's origin is not on the operator's list (checked on every request, so a connection saved earlier is cut off once its origin is removed), or another validation failure." @@ -25140,7 +25137,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "502": *a66 + "502": *a69 /api/documents/sources/{system}/tags: get: tags: @@ -25148,9 +25145,9 @@ paths: summary: Tags of a connected archive description: The source's tags for the picker's filter, the first 250 by name, in the source's own ids. Shares the search allowance of sixty a minute. Cookie session only; not delegable. - security: *a62 + security: *a65 parameters: - - *a64 + - *a67 responses: "200": description: The tags. @@ -25159,11 +25156,11 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceTagsEnvelope" "401": *a1 - "403": *a63 - "404": *a65 - "422": *a67 + "403": *a66 + "404": *a68 + "422": *a70 "429": *a5 - "502": *a66 + "502": *a69 /api/documents/sources/{system}/search: get: tags: @@ -25177,9 +25174,9 @@ paths: under that source key: `new`, `imported` with the vault document's `documentId`, or `deleted` (deleted in HealthLog; importing it again stores nothing). Nothing is stored. Sixty searches a minute, shared with the tag read. Cookie session only; not delegable." - security: *a62 + security: *a65 parameters: - - *a64 + - *a67 - name: q in: query required: false @@ -25220,11 +25217,11 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceSearchEnvelope" "401": *a1 - "403": *a63 - "404": *a65 - "422": *a67 + "403": *a66 + "404": *a68 + "422": *a70 "429": *a5 - "502": *a66 + "502": *a69 /api/documents/sources/{system}/import: post: tags: @@ -25243,9 +25240,9 @@ paths: fetched), including a duplicate. `kind` sets the document type (default OTHER). Automatic AI reading follows the person's own setting. Counts against the person's upload allowance (60 an hour) and storage quota. 201 for a new document, 200 otherwise. Cookie session only; not delegable." - security: *a62 + security: *a65 parameters: - - *a64 + - *a67 requestBody: required: true content: @@ -25303,7 +25300,7 @@ paths: schema: $ref: "#/components/schemas/DocumentSourceImportEnvelope" "401": *a1 - "403": *a63 + "403": *a66 "404": description: "`documents.sources.unavailable`: the operator has not listed any origin in `DOCUMENT_SOURCE_ORIGINS`, so the picker is off. `documents.sources.notConnected`: no connection is saved for this system. @@ -25334,7 +25331,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "422": *a67 + "422": *a70 "429": description: "`documents.inbound.rateLimited` (the person's hourly upload allowance, with the rate-limit headers) or `documents.inbound.uploadBusy` (too many uploads in progress; `Retry-After: 1`)." @@ -25342,7 +25339,7 @@ paths: application/json: schema: $ref: "#/components/schemas/ErrorEnvelope" - "502": *a66 + "502": *a69 /api/symptoms/definitions: get: tags: @@ -25366,7 +25363,7 @@ paths: schema: $ref: "#/components/schemas/ListSymptomDefinitionsEnvelope" "401": *a1 - "403": &a68 + "403": &a71 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: `illness.disabled`: the record @@ -25410,7 +25407,7 @@ paths: schema: $ref: "#/components/schemas/CreateSymptomDefinitionEnvelope" "401": *a1 - "403": *a68 + "403": *a71 "409": *a7 "422": *a4 "429": *a5 @@ -25440,8 +25437,8 @@ paths: schema: $ref: "#/components/schemas/UpdateSymptomDefinitionEnvelope" "401": *a1 - "403": *a68 - "404": &a69 + "403": *a71 + "404": &a72 description: "`symptoms.definition.notFound`: no definition with this id on the record." content: application/json: @@ -25475,8 +25472,8 @@ paths: schema: $ref: "#/components/schemas/DeleteSymptomDefinitionEnvelope" "401": *a1 - "403": *a68 - "404": *a69 + "403": *a71 + "404": *a72 "422": *a4 "429": *a5 /api/symptoms/events: @@ -25525,7 +25522,7 @@ paths: schema: $ref: "#/components/schemas/ListSymptomEventsEnvelope" "401": *a1 - "403": *a68 + "403": *a71 "422": *a4 "429": *a5 post: @@ -25551,8 +25548,8 @@ paths: schema: $ref: "#/components/schemas/CreateSymptomEventEnvelope" "401": *a1 - "403": *a68 - "404": *a69 + "403": *a71 + "404": *a72 "409": *a7 "422": *a4 "429": *a5 @@ -25582,8 +25579,8 @@ paths: schema: $ref: "#/components/schemas/UpdateSymptomEventEnvelope" "401": *a1 - "403": *a68 - "404": &a70 + "403": *a71 + "404": &a73 description: "`symptoms.event.notFound`: no occurrence with this id on the record." content: application/json: @@ -25610,8 +25607,8 @@ paths: schema: $ref: "#/components/schemas/DeleteSymptomEventEnvelope" "401": *a1 - "403": *a68 - "404": *a70 + "403": *a71 + "404": *a73 "422": *a4 "429": *a5 /api/day/index: @@ -25762,7 +25759,7 @@ paths: schema: $ref: "#/components/schemas/TimelineEnvelope" "401": *a1 - "403": &a71 + "403": &a74 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: `module.disabled` with @@ -25801,7 +25798,7 @@ paths: schema: $ref: "#/components/schemas/TimelineReadinessEnvelope" "401": *a1 - "403": *a71 + "403": *a74 "422": *a4 "429": *a5 /api/life-events: @@ -25819,7 +25816,7 @@ paths: schema: $ref: "#/components/schemas/LifeEventListEnvelope" "401": *a1 - "403": &a72 + "403": &a75 description: >- `meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the generic auth gate answers it before the route's own checks run. Otherwise: Owner-only in v1.42: no share @@ -25861,7 +25858,7 @@ paths: schema: $ref: "#/components/schemas/CreateLifeEventEnvelope" "401": *a1 - "403": *a72 + "403": *a75 "409": *a7 "422": *a4 "429": *a16 @@ -25892,8 +25889,8 @@ paths: schema: $ref: "#/components/schemas/UpdateLifeEventEnvelope" "401": *a1 - "403": *a72 - "404": &a73 + "403": *a75 + "404": &a76 description: No such life event on this account. content: application/json: @@ -25920,8 +25917,8 @@ paths: schema: $ref: "#/components/schemas/DeleteLifeEventEnvelope" "401": *a1 - "403": *a72 - "404": *a73 + "403": *a75 + "404": *a76 "422": *a4 "429": *a5 /api/search: @@ -26096,7 +26093,7 @@ paths: required: - file responses: - "202": *a41 + "202": *a44 "401": *a1 "403": description: "`meta.errorCode` = `auth.scope.insufficient` when a Bearer token's scope does not reach this route; the @@ -26132,7 +26129,7 @@ paths: schema: $ref: "#/components/schemas/ErrorEnvelope" "429": *a5 - "503": *a23 + "503": *a26 /api/anamnesis/life-circumstances: get: tags: diff --git a/src/lib/openapi/routes/profile.ts b/src/lib/openapi/routes/profile.ts index a510c4fad..a19f2eb14 100644 --- a/src/lib/openapi/routes/profile.ts +++ b/src/lib/openapi/routes/profile.ts @@ -1141,6 +1141,29 @@ const profileSummaryResponse = z "The bounded profile view a record viewer gets: allergies, family history and current lifestyle facts. Structurally incapable of becoming a Settings or AI surface.", }); +/** + * What the single-value `/api/auth/me/*` writes answer for a body they refuse + * to read. Each reads through `safeJson` with a 1 KB cap, so the three + * refusals are the cap's, not the route's: a wrong content type is 415, a body + * over the cap 413, and one that does not parse 400. All three are answered + * before validation, and nothing is written. + */ +const cappedScalarBodyRefusals = { + "400": { + description: "The request body is not parseable JSON. Nothing was written.", + content: { "application/json": { schema: errorEnvelope } }, + }, + "413": { + description: "Request body exceeds 1024 bytes. Nothing was written.", + content: { "application/json": { schema: errorEnvelope } }, + }, + "415": { + description: + "The request did not carry `Content-Type: application/json`. Nothing was read and nothing was written.", + content: { "application/json": { schema: errorEnvelope } }, + }, +}; + export const profilePaths: NonNullable = { "/api/profile/summary": { get: { @@ -1863,10 +1886,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, @@ -1917,10 +1937,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, @@ -1971,10 +1988,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, @@ -2257,10 +2271,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, @@ -2307,10 +2318,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, @@ -2360,10 +2368,7 @@ export const profilePaths: NonNullable = { }, }, }, - "413": { - description: "Request body exceeds 1024 bytes. Nothing was written.", - content: { "application/json": { schema: errorEnvelope } }, - }, + ...cappedScalarBodyRefusals, ...stdResponses, }, }, diff --git a/tests/integration/routes/auth-me-disable-coach.test.ts b/tests/integration/routes/auth-me-disable-coach.test.ts index fff8b24d3..26a75e251 100644 --- a/tests/integration/routes/auth-me-disable-coach.test.ts +++ b/tests/integration/routes/auth-me-disable-coach.test.ts @@ -174,9 +174,6 @@ describe("PATCH /api/auth/me/disable-coach", () => { route: ROUTE, headers: { "content-type": "application/json" }, auth: await makeSession(user), - contract: { - skip: "the registry declares no 400 for this PATCH, which answers one for a body that is not JSON", - }, }); expect(res.status).toBe(400); expect(res.error).toBe("Invalid JSON body"); diff --git a/tests/integration/routes/auth-me-modules.test.ts b/tests/integration/routes/auth-me-modules.test.ts index db2c39337..7e9faec7d 100644 --- a/tests/integration/routes/auth-me-modules.test.ts +++ b/tests/integration/routes/auth-me-modules.test.ts @@ -37,11 +37,6 @@ type ModulesResponse = { updatedAt?: string; }; -const NO_400 = - "the registry declares no 400 for PATCH /api/auth/me/modules although the handler answers one for a body that is not JSON"; -const NO_409 = - "the registry declares no 409 for PATCH /api/auth/me/modules although the handler answers modules_conflict for a stale base token"; - beforeEach(() => truncateAllTables(prisma)); async function storedPrefs(userId: string): Promise { @@ -63,14 +58,12 @@ async function storedUpdatedAt(userId: string): Promise { async function patch( auth: Awaited>, body: Record, - contract?: { skip: string }, ) { return callRoute(PATCH, { method: "PATCH", route: ROUTE, body, auth, - contract, }); } @@ -229,7 +222,6 @@ describe("PATCH /api/auth/me/modules", () => { route: ROUTE, headers: { "content-type": "application/json" }, auth: await makeSession(user), - contract: { skip: NO_400 }, }); expect(res.status).toBe(400); expect(res.error).toBe("Invalid JSON body"); @@ -281,11 +273,10 @@ describe("PATCH /api/auth/me/modules evicts the cached score", () => { const user = await makeUser(); caches.analytics.set(`${user.id}|score`, { score: 71 }); - const res = await patch( - await makeSession(user), - { sleep: false, baseUpdatedAt: "2020-01-01T00:00:00.000Z" }, - { skip: NO_409 }, - ); + const res = await patch(await makeSession(user), { + sleep: false, + baseUpdatedAt: "2020-01-01T00:00:00.000Z", + }); expect(res.status).toBe(409); expect(caches.analytics.get(`${user.id}|score`)).toEqual({ score: 71 }); }); @@ -320,11 +311,7 @@ describe("PATCH /api/auth/me/modules optimistic concurrency", () => { const first = await patch(session, { mood: false, baseUpdatedAt: base }); expect(first.status).toBe(200); - const second = await patch( - session, - { sleep: false, baseUpdatedAt: base }, - { skip: NO_409 }, - ); + const second = await patch(session, { sleep: false, baseUpdatedAt: base }); expect(second.status).toBe(409); expect(second.meta?.errorCode).toBe("modules_conflict"); expect(await storedPrefs(user.id)).toEqual({ mood: false }); diff --git a/tests/integration/routes/auth-me-unit-preference.test.ts b/tests/integration/routes/auth-me-unit-preference.test.ts index c9abbc0fd..9c9aa5cec 100644 --- a/tests/integration/routes/auth-me-unit-preference.test.ts +++ b/tests/integration/routes/auth-me-unit-preference.test.ts @@ -171,9 +171,6 @@ describe("PATCH /api/auth/me/unit-preference", () => { route: ROUTE, headers: { "content-type": "application/json" }, auth: await makeSession(user), - contract: { - skip: "the registry declares no 400 for this PATCH although the description names it", - }, }); expect(res.status).toBe(400); expect(await storedPreference(user.id)).toBe("metric"); @@ -199,9 +196,6 @@ describe("PATCH /api/auth/me/unit-preference", () => { body: "unitPreference=imperial", headers: { "content-type": "application/x-www-form-urlencoded" }, auth: await makeSession(user), - contract: { - skip: "the registry declares no 415 for this PATCH although the description names it", - }, }); expect(res.status).toBe(415); expect(await storedPreference(user.id)).toBe("metric");