{"openapi":"3.1.0","info":{"title":"Health Agent API","version":"0.1.0"},"paths":{"/api/v1/auth/logout":{"post":{"tags":["auth"],"summary":"Logout","description":"No-op retained for client compatibility.\n\nClerk owns sessions; sign-out happens client-side (Clerk clears its session\nand rotates keys). There is no server-side session to revoke, so this simply\nacknowledges the request.","operationId":"logout_api_v1_auth_logout_post","responses":{"204":{"description":"Successful Response"}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/me":{"get":{"tags":["auth"],"summary":"Me","operationId":"me_api_v1_auth_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/auth/me/complete-onboarding":{"post":{"tags":["auth"],"summary":"Complete Onboarding","description":"Mark the caller's first-run onboarding walkthrough as complete.\n\nIdempotent: the first call stamps ``onboarded_at``; later calls leave the\noriginal timestamp untouched. The mobile client shows the walkthrough while\nthis is NULL, so stamping it stops the auto-show on future sign-ins.","operationId":"complete_onboarding_api_v1_auth_me_complete_onboarding_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/account/export":{"get":{"tags":["account"],"summary":"Export Account Data","description":"Return a complete, secret-free JSON export of the caller's data.","operationId":"export_account_data_api_v1_account_export_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/account":{"delete":{"tags":["account"],"summary":"Delete Account","description":"Permanently delete the caller's account and all associated data.\n\nA single delete of the user row cascades to every user-owned table via\nDB-level ON DELETE CASCADE (see migration b7e1c9d2a3f4). Refresh sessions are\ncascaded away; the now-missing user makes any still-valid access token fail\nauthentication on its next use.","operationId":"delete_account_api_v1_account_delete","responses":{"204":{"description":"Successful Response"}},"security":[{"HTTPBearer":[]}]}},"/api/v1/account/data":{"delete":{"tags":["account"],"summary":"Delete Account Data","description":"Delete all of the caller's logged and synced data, keeping the account.\n\nClears every category, metric, measurement, note, voice session, hunch, and\nconnected integration — but preserves the account itself: the login, the Pro\nentitlement, and MCP tokens all survive, so the user stays signed in with\ntheir subscription and AI connections intact. Backs the \"Delete All Data\"\ncontrol in Settings and the /delete-account web page. See\n``cohere/account/deletion.py`` for exactly what is and isn't removed.","operationId":"delete_account_data_api_v1_account_data_delete","responses":{"204":{"description":"Successful Response"}},"security":[{"HTTPBearer":[]}]}},"/api/v1/billing/status":{"get":{"tags":["billing"],"summary":"Billing Status","description":"Return the caller's plan with the detail the Settings screen needs.\n\n``GET /auth/me`` already carries ``is_pro`` for gating; this adds the renewal date,\npending-cancellation state, and whether the account is comped — the things a user\nasks \"when does this renew?\" about.","operationId":"billing_status_api_v1_billing_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntitlementResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/billing/checkout":{"post":{"tags":["billing"],"summary":"Create Checkout","description":"Start a Polar checkout for the Pro plan and hand back its URL.\n\nThe session is bound to ``users.id`` as Polar's external customer id, which is what\nlets the resulting ``customer.state_changed`` webhook find this account again.\n\nReconciles first. If a webhook was lost, our row says \"free\" while Polar says\n\"active\" — and sending that user to checkout produces a dead end, because Polar\nrefuses with *\"You already have an active subscription\"* and the user is left staring\nat a red error on a page they cannot complete. Pulling the real state first turns\nthat into a 409 the client can act on.","operationId":"create_checkout_api_v1_billing_checkout_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/billing/portal":{"post":{"tags":["billing"],"summary":"Create Portal","description":"Return a Polar customer-portal URL so the user can manage or cancel their plan.\n\nSelf-service cancellation is a requirement of most app stores and of basic decency;\nit also keeps cancellation requests out of the support inbox.","operationId":"create_portal_api_v1_billing_portal_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortalResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/billing/reconcile":{"post":{"tags":["billing"],"summary":"Reconcile Entitlement","description":"Re-read this user's plan from the provider and return the refreshed entitlement.\n\nA second, independent path to the same truth as the webhook — and the one the\npost-checkout redirect uses. The two race by design: Polar sends the browser back\nthe moment payment clears, which can beat ``customer.state_changed`` reaching us.\nWorse, the webhook may never arrive at all (no dead-letter queue, no replay, and an\nendpoint auto-disabled after 10 failures), which leaves a paying user on the free\ntier with nothing to retry.\n\nPulling on the success screen closes both holes: the common case is a few hundred\nmilliseconds faster, and the pathological case self-heals instead of becoming a\nsupport ticket.\n\nIdempotent — it applies the same full snapshot the webhook would.","operationId":"reconcile_entitlement_api_v1_billing_reconcile_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EntitlementResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/billing/webhook":{"post":{"tags":["billing"],"summary":"Polar Webhook","description":"Receive Polar webhooks — ``customer.state_changed`` only.\n\nThat event carries the customer's complete state (subscriptions, benefits, meters)\nrather than a diff, which makes the handler idempotent and immune to redelivery.\n\nAcks immediately and does the DB write in a background task: Polar times out at 10\nseconds and **disables an endpoint after 10 consecutive non-2xx responses**, with no\ndead-letter queue and no replay. So anything that is not \"this request was forged\"\nstill returns 200 — a parse failure we can investigate in logs beats a disabled\nendpoint we discover a week later.\n\nDeliberately unauthenticated: the Standard Webhooks signature *is* the authentication.","operationId":"polar_webhook_api_v1_billing_webhook_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"boolean"},"type":"object","title":"Response Polar Webhook Api V1 Billing Webhook Post"}}}}}}},"/api/v1/log":{"post":{"tags":["log"],"summary":"Start Log","operationId":"start_log_api_v1_log_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/log/{session_id}/respond":{"post":{"tags":["log"],"summary":"Respond To Session","operationId":"respond_to_session_api_v1_log__session_id__respond_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"integer","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/log/{session_id}/accept":{"post":{"tags":["log"],"summary":"Accept Log Session","operationId":"accept_log_session_api_v1_log__session_id__accept_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"integer","title":"Session Id"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptRequest","default":{"timestamp_overrides":{},"rejected_indices":[],"field_overrides":{}}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/log/{session_id}/abandon":{"post":{"tags":["log"],"summary":"Abandon Log Session","operationId":"abandon_log_session_api_v1_log__session_id__abandon_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"integer","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/log/{session_id}":{"get":{"tags":["log"],"summary":"Get Log Session","description":"Return session transcript, rounds, and any user feedback.","operationId":"get_log_session_api_v1_log__session_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"integer","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/log/{session_id}/feedback":{"post":{"tags":["log"],"summary":"Submit Session Feedback","description":"Create or update feedback for a session.","operationId":"submit_session_feedback_api_v1_log__session_id__feedback_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"integer","title":"Session Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/measurements":{"get":{"tags":["measurements"],"summary":"List Measurements","operationId":"list_measurements_api_v1_measurements_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["measurements"],"summary":"Create Measurement","description":"Create a measurement for a metric owned by the current user.","operationId":"create_measurement_api_v1_measurements_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MeasurementCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/measurements/{measurement_id}":{"patch":{"tags":["measurements"],"summary":"Update Measurement","description":"Partial update of a measurement.\n\nAccepts subset of value, timestamp, end_timestamp, notes. An explicit\n``end_timestamp: null`` clears the range end (point metric). Pass\n``{\"revert\": true}`` to restore raw ingest values.","operationId":"update_measurement_api_v1_measurements__measurement_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"measurement_id","in":"path","required":true,"schema":{"type":"integer","title":"Measurement Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["measurements"],"summary":"Delete Measurement","operationId":"delete_measurement_api_v1_measurements__measurement_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"measurement_id","in":"path","required":true,"schema":{"type":"integer","title":"Measurement Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/categories":{"get":{"tags":["categories"],"summary":"List Categories","operationId":"list_categories_api_v1_categories_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["categories"],"summary":"Create Category","operationId":"create_category_api_v1_categories_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/categories/{category_id}":{"patch":{"tags":["categories"],"summary":"Update Category","operationId":"update_category_api_v1_categories__category_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"category_id","in":"path","required":true,"schema":{"type":"integer","title":"Category Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["categories"],"summary":"Delete Category","operationId":"delete_category_api_v1_categories__category_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"category_id","in":"path","required":true,"schema":{"type":"integer","title":"Category Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/categories/reorder":{"put":{"tags":["categories"],"summary":"Reorder Categories","description":"Persist a new display order for the user's categories.\n\nBody: ``{\"category_ids\": [3, 1, 2]}`` — ids in top-to-bottom order.","operationId":"reorder_categories_api_v1_categories_reorder_put","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/categories/{category_id}/measurements":{"get":{"tags":["categories"],"summary":"Category Measurements","description":"A merged, newest-first feed of measurements across every metric in a category.\n\nPowers the Category detail page's \"Recent activity\" log. Each row is tagged\nwith its metric (id, name, data_type, unit, ordinal_map) so the feed is\nself-contained and rows can route to the owning metric's detail screen.","operationId":"category_measurements_api_v1_categories__category_id__measurements_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"category_id","in":"path","required":true,"schema":{"type":"integer","title":"Category Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/metrics/date-range":{"get":{"tags":["metrics"],"summary":"Metrics Date Range","description":"Union of first/last measurement dates across the requested metrics.\n\nWhen ``metric_ids`` is omitted or empty, spans every metric that has data.\nReturns ``{start, end}`` as ``YYYY-MM-DD`` strings, or ``null`` fields when\nno measurements match.","operationId":"metrics_date_range_api_v1_metrics_date_range_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"metric_ids","in":"query","required":false,"schema":{"type":"array","items":{"type":"integer"},"default":[],"title":"Metric Ids"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/metrics/overview":{"get":{"tags":["metrics"],"summary":"Metrics Overview","description":"One round-trip view powering the Metrics tab.\n\nReturns categories, each with a flat list of its metrics. Every metric\ncarries recent measurements for the requested ``range`` (newest first,\ndownsampled for 1y), a total count (not capped), and the global latest\nvalue/time. Derived metrics are evaluated over the window (daily grain) so\ntheir cards render like any other; their ``count`` is days-with-data in the\nwindow and ``latest`` is the last evaluated day.","operationId":"metrics_overview_api_v1_metrics_overview_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"range","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Preview window: 30d, 90d, or 1y","default":"30d","title":"Range"},"description":"Preview window: 30d, 90d, or 1y"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/metrics":{"post":{"tags":["metrics"],"summary":"Create Metric","description":"Create a metric.\n\nWith a ``derived`` block ``{fn, expr?, inputs: [{metric_id, agg?, missing?}]}``\nthe metric is a calculated one: it holds no measurements, its ``data_type``\nis inferred from the combiner, and its inputs are stored as\n``metric_inputs`` edges. Without it, a plain source metric.","operationId":"create_metric_api_v1_metrics_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/metrics/suggest-missing-policy":{"post":{"tags":["metrics"],"summary":"Suggest Metric Missing Policy","description":"Suggest what an unlogged day means for a metric that's being created.\n\nBody: ``{name, category?, data_type, unit?, description?}``. Returns\n``{\"policy\", \"source\": \"classifier\"|\"type_default\", \"confidence\", \"applied\"}`` —\nthe policy creating the metric without an explicit choice would use (a confident\nclassifier pick, else the type default). Never errors on classifier failure.","operationId":"suggest_metric_missing_policy_api_v1_metrics_suggest_missing_policy_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/metrics/{metric_id}/fill-summary":{"get":{"tags":["metrics"],"summary":"Metric Fill Summary","description":"How the metric's effective missing policy plays out over a window.\n\nReturns ``{effective_policy, is_type_default, days_in_range, logged_days,\nfilled_days, unfilled_days, first_logged, last_logged}`` computed by the same\ndaily series code analysis uses, so the numbers match what correlations saw.","operationId":"metric_fill_summary_api_v1_metrics__metric_id__fill_summary_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"metric_id","in":"path","required":true,"schema":{"type":"integer","title":"Metric Id"}},{"name":"start","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/datetime, inclusive; 'first_logged' = the metric's first log; default 90 days ago","title":"Start"},"description":"ISO date/datetime, inclusive; 'first_logged' = the metric's first log; default 90 days ago"},{"name":"end","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO date/datetime, exclusive; default now","title":"End"},"description":"ISO date/datetime, exclusive; default now"},{"name":"tz","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"IANA timezone for day boundaries","title":"Tz"},"description":"IANA timezone for day boundaries"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/metrics/{metric_id}/dependents":{"get":{"tags":["metrics"],"summary":"Metric Dependents","description":"Derived metrics that consume this metric — the delete-warning pre-flight.","operationId":"metric_dependents_api_v1_metrics__metric_id__dependents_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"metric_id","in":"path","required":true,"schema":{"type":"integer","title":"Metric Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/metrics/reorder":{"put":{"tags":["metrics"],"summary":"Reorder Metrics","description":"Persist metric display order and group membership after a drag-and-drop.\n\nBody: ``{\"groups\": [{\"category_id\": 3, \"metric_ids\": [7, 2, 9]}, ...]}``.\nEach entry lists a category's metrics in top-to-bottom order. Every listed\nmetric is assigned ``category_id`` = the entry's category and ``position`` =\nits index — so one call covers both within-group reordering (send one entry)\nand moving a metric across groups (send the source and destination entries,\neach with its full new membership, so positions stay dense on both sides).\n\nIntegration-backed groups are read-only: a metric can neither be reordered\ninto one nor moved out of one.","operationId":"reorder_metrics_api_v1_metrics_reorder_put","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/metrics/{metric_id}":{"patch":{"tags":["metrics"],"summary":"Update Metric","description":"Partial update of a metric.\n\nAccepts any subset of: name, unit, description, ordinal_map, value_min,\nvalue_max, missing_policy, tracking_cadence, category_id, pinned, derived.\n\n``kind`` is immutable: a source metric can't become calculated (or vice\nversa) — delete and recreate instead. ``derived`` (calculated metrics only)\nis a FULL replacement of the formula: fn + expr + inputs. The cycle check\nwalks the proposed inputs' closure and rejects a spec that reaches this\nmetric. ``data_type`` is likewise immutable — sending a different value is\na 400 (calculated metrics re-infer it when their formula changes). ``tracking_cadence`` is likewise rejected for calculated\nmetrics — they're never logged, so a reminder cadence is a meaningless state.","operationId":"update_metric_api_v1_metrics__metric_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"metric_id","in":"path","required":true,"schema":{"type":"integer","title":"Metric Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["metrics"],"summary":"Delete Metric","operationId":"delete_metric_api_v1_metrics__metric_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"metric_id","in":"path","required":true,"schema":{"type":"integer","title":"Metric Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/metrics/{metric_id}/measurements":{"get":{"tags":["metrics"],"summary":"Metric Measurements","description":"Paginated measurements for one metric, newest first.","operationId":"metric_measurements_api_v1_metrics__metric_id__measurements_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"metric_id","in":"path","required":true,"schema":{"type":"integer","title":"Metric Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","minimum":0,"default":0,"title":"Offset"}},{"name":"since","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/analysis/build":{"post":{"tags":["analysis"],"summary":"Analysis Build","description":"Consolidated analysis: one raw fetch → series + summaries + correlations.\n\nThe mobile analysis page calls this instead of the three single-purpose\nendpoints, so a Build fetches raw measurements once (and only the uncovered\ngaps, via the Layer 1 cache) rather than three times.\n\nReturns the same payloads the legacy endpoints do for the same request,\nincluding their per-endpoint ``resample`` defaults: series/summary default to\n``\"None\"`` and correlations to ``\"D\"`` when the client omits ``resample``\n(when ``resample`` is provided, all three use it). ``correlations`` is\n``None`` for fewer than two metrics, so the client can drive its empty state.\n\nTo bound payload size, the series + summary resample is downsampled to a\ncoarser rule when the requested one would exceed :data:`SERIES_POINT_BUDGET`\npoints across all traces; when that happens a ``notice`` describes the change\nso the client can surface it. ``notice`` is ``None`` otherwise.","operationId":"analysis_build_api_v1_analysis_build_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/analysis/ecosystem":{"post":{"tags":["analysis"],"summary":"Analysis Ecosystem","description":"All-vs-all correlation across the user's metrics — the Signal tab payload.\n\nUnlike Build, each metric contributes only its *own* measurements (no\nsubtree aggregation): a parent aggregated over its children would be\ntrivially correlated with every one of them, which is definitional noise in\nan all-vs-all view. Container metrics with no direct measurements drop out\nvia the empty-series filter.\n\nPer-metric missing policy defaults to each metric's saved policy, else its\ntype default (:func:`_metric_default_missing`), and an explicit ``missing``\nmap overrides per metric. On top of the matrix\npayload (r/p/n/ci_low/ci_high) this adds:\n\n- ``q``: Benjamini–Hochberg FDR-adjusted p-values — edge visibility should\n  key on these, not raw p (all-vs-all is a multiple-comparisons machine).\n- ``order``: seriation permutation (indices) from hierarchical clustering,\n  so a heatmap renders with similar metrics adjacent.\n- ``groups``: cluster id per metric (aligned with ``metric_ids``).\n- ``observed``: non-empty buckets per metric (node sizing).\n- ``category_ids``: category per metric (node coloring / grouping).\n- ``dismissed``: ``[metric_id_a, metric_id_b]`` pairs the user has marked\n  unimportant (see ``/analysis/dismissed-correlations``) — the client hides\n  these by default rather than the server dropping them, so a \"show\n  dismissed\" toggle needs no extra round trip.\n\n**Multi-scale mode** (``scales: [\"D\",\"W\",\"M\"]`` in the body): computes the\nmatrix on each requested calendar grid, pools every scale's p-values into\nONE Benjamini–Hochberg family (per-scale families would give each pair\nseveral shots at significance), and combines them into best-scale top-level\nmatrices — each pair reported at the scale with the highest\nconfidence-adjusted strength (LCB), ties to the finer scale. The daily grid\nreads a trailing ``DAILY_RECENCY_CAP_DAYS`` window unless\n``custom_window: true``; coarse scales read the full window and are skipped\n(never partially computed) when the data span can't reach ``MIN_CORR_N``\nbuckets. Adds ``best_scale`` (per-pair scale matrix), ``scales`` (computed\ncodes), ``scale_meta`` (window/buckets/needed/computed per requested\nscale), and ``per_scale`` (full matrix set per computed scale, on the same\nunion id order — the edge sheet's scale profile). ``insights`` are\ngenerated at each pair's chosen scale and carry a ``scale`` field. Without\n``scales`` the endpoint behaves exactly as before (single ``resample``).\n\n**Free plans get an outline, not the matrix.** Everything above describes the Pro\npayload. A free caller receives the topology-only projection built by\n:func:`_project_ecosystem` — opaque node indices, cluster ids, bucketed sizes and\nindex-pair edges — with no labels, statistics or insights. The endpoint itself stays\nopen (rather than returning 402) precisely so the locked Signal page can still draw\nthe user's real network shape as the thing they are being offered.","operationId":"analysis_ecosystem_api_v1_analysis_ecosystem_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/measurements/series":{"post":{"tags":["analysis"],"summary":"Measurements Series","description":"Return aligned per-metric time series for the requested metric ids.","operationId":"measurements_series_api_v1_measurements_series_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/analysis/summary":{"post":{"tags":["analysis"],"summary":"Analysis Summary","description":"Return per-metric typed summary statistics for the requested metric ids.","operationId":"analysis_summary_api_v1_analysis_summary_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/analysis/correlations":{"post":{"tags":["analysis"],"summary":"Analysis Correlations","description":"Return a correlation matrix (Spearman by default) for the requested metric ids.","operationId":"analysis_correlations_api_v1_analysis_correlations_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/analysis/lag":{"post":{"tags":["analysis"],"summary":"Analysis Lag","description":"Lagged correlation between two metrics: r/p/q/n across a window of lags.\n\n**Pro only** (402 for free plans), alongside the rest of the Signal surface. A free\ncaller has no way to name a pair here anyway: the outline they get from\n``/analysis/ecosystem`` carries opaque indices, not metric ids.\n\nFor each lag *L* in ``[-max_lag, +max_lag]`` (in resample buckets), metric_b\nis shifted by *L* and correlated with metric_a over the overlapping points. A\npositive lag means changes in metric_b tend to *follow* metric_a — temporal\nprecedence, not proof of causation.\n\nTwo guards against the profile's multiple-comparisons problem (up to 61\ncorrelated tests whose tallest bar the UI invites the user to read off):\n\n- each ``p`` is corrected for autocorrelation via Bartlett's effective\n  sample size ``n_eff = n·(1−ρ₁ρ₂)/(1+ρ₁ρ₂)`` (daily health series are\n  strongly AR(1), which makes raw p wildly anti-conservative), and\n- ``q`` is Benjamini–Hochberg across the lag family, so the client can\n  emphasize only lags with q ≤ 0.05 and render the rest as context.\n\n``resample=\"None\"`` is rejected: a row-shift over irregular raw timestamps\nis not a time shift, so lags are only meaningful on a regular grid.","operationId":"analysis_lag_api_v1_analysis_lag_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/analysis/regression":{"post":{"tags":["analysis"],"summary":"Analysis Regression","description":"Least-squares fit of ``metric_y`` on ``metric_x`` — the scatter + line view.\n\nFits an ordinary least-squares (OLS) straight line to the two metrics' aligned\ndaily series and returns everything the Analysis page's Regression section\ndraws and states:\n\n- ``points``: the aligned (x, y) scatter cloud.\n- ``fit.line``: the two-point OLS line; ``fit.lowess``: a locally-weighted\n  smoother showing the data's actual shape (so curvature is visible next to\n  the straight line); ``fit.fitted``/``fit.residuals``: the residual-vs-fitted\n  diagnostic.\n- ``fit``: slope, intercept, Pearson ``r``, **R²**, the slope's 95% CI, a\n  robust **Theil–Sen** slope cross-check, the Spearman ``r`` + ``is_linear``\n  linearity gate, and both the naive and autocorrelation-adjusted p-values.\n\n``lag`` shifts the response relative to the predictor (positive = y follows x)\non the resample grid; ``resample=\"None\"`` is rejected because a row shift over\nirregular raw timestamps is not a time shift. Regression is asymmetric — x is\nthe predictor, y the response — so the pair order matters.","operationId":"analysis_regression_api_v1_analysis_regression_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/analysis/derived-preview":{"post":{"tags":["analysis"],"summary":"Derived Preview","description":"Evaluate a derived spec WITHOUT persisting it — the builder's live preview.\n\nRuns the exact evaluator the analysis endpoints use, so what the user sees\nat creation is literally what analysis will show. Returns the evaluated\npoints, each input's own series (for the faint underlay), coverage counts,\nthe plain-English read-back sentence, and the inferred type/unit.","operationId":"derived_preview_api_v1_analysis_derived_preview_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/analysis/dismissed-correlations":{"post":{"tags":["analysis"],"summary":"Dismiss Correlation","description":"Mark one metric pair as an uninteresting correlation (hidden by default in Signal).\n\n**Pro only.** Curating the network is a Signal affordance, and a free caller cannot\nhave identified a pair to dismiss — the outline they receive carries no metric ids.","operationId":"dismiss_correlation_api_v1_analysis_dismissed_correlations_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/analysis/dismissed-correlations/{metric_id_a}/{metric_id_b}":{"delete":{"tags":["analysis"],"summary":"Undismiss Correlation","description":"Restore a previously dismissed pair so it shows in Signal again.\n\n**Pro only**, mirroring its POST counterpart.","operationId":"undismiss_correlation_api_v1_analysis_dismissed_correlations__metric_id_a___metric_id_b__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"metric_id_a","in":"path","required":true,"schema":{"type":"integer","title":"Metric Id A"}},{"name":"metric_id_b","in":"path","required":true,"schema":{"type":"integer","title":"Metric Id B"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/hunches":{"get":{"tags":["hunches"],"summary":"List Hunches","description":"List the user's hunches with their latest evaluations.\n\n``status=active`` returns the Tracked lane (monitored hunches only);\n``resolved`` the graduated shelf (learned/unsupported); ``all`` both.\nProposed hunches (agent-staged, unconfirmed) are never returned — they\nlive entirely in the MCP conversation until confirmed.","operationId":"list_hunches_api_v1_hunches_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","description":"active | resolved | all","default":"all","title":"Status"},"description":"active | resolved | all"},{"name":"tz","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tz"}},{"name":"refresh","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Refresh"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["hunches"],"summary":"Create Hunch","description":"Create a hunch from the save sheet, baselining it server-side.\n\nBody: ``{predictor_metric_id, response_metric_id, lag_days?, method?,\npreferred_lens?, hypothesis?, window_start?, window_end?, tz?}``. The\nwindow defaults to the trailing year. Creation requires a computable\nbaseline (>= MIN_CORR_N overlapping days) — a hunch is a promotion of a\ncomputed relationship, never a blind bookmark.","operationId":"create_hunch_api_v1_hunches_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/hunches/{hunch_id}":{"patch":{"tags":["hunches"],"summary":"Update Hunch","description":"Edit, graduate, confirm, or re-baseline a hunch.\n\nAccepted fields (any combination):\n\n- ``hypothesis`` — replace the statement (single editable statement by design)\n- ``status`` — ``learned`` / ``unsupported`` graduate; ``active`` confirms a\n  proposed hunch or re-tracks a graduated one\n- ``rebaseline: true`` — snapshot the relationship as it stands now as the\n  new reference point; the old baseline is kept in ``prior_baselines``","operationId":"update_hunch_api_v1_hunches__hunch_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hunch_id","in":"path","required":true,"schema":{"type":"integer","title":"Hunch Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["hunches"],"summary":"Delete Hunch","description":"Hard-delete a hunch (user-initiated only; graduation never deletes).","operationId":"delete_hunch_api_v1_hunches__hunch_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"hunch_id","in":"path","required":true,"schema":{"type":"integer","title":"Hunch Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/integrations/oura/connect-url":{"get":{"tags":["integrations"],"summary":"Get Oura Connect Url","description":"Return the Oura OAuth consent URL with a signed state JWT carrying the user id.\n\nArgs:\n    platform: ``\"web\"`` when the flow starts in the hosted web app, so the callback\n        redirects the browser back to the web origin instead of the native deep link.","operationId":"get_oura_connect_url_api_v1_integrations_oura_connect_url_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"platform","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectUrlResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/integrations/oura/callback":{"get":{"tags":["integrations"],"summary":"Oura Oauth Callback","description":"Handle Oura OAuth redirect and persist tokens.","operationId":"oura_oauth_callback_api_v1_integrations_oura_callback_get","parameters":[{"name":"code","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"}},{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}},{"name":"scope","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope"}},{"name":"error","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},{"name":"error_description","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error Description"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/integrations/oura/connect-pat":{"post":{"tags":["integrations"],"summary":"Connect Oura Pat","description":"Store an Oura Personal Access Token directly (no OAuth flow needed).","operationId":"connect_oura_pat_api_v1_integrations_oura_connect_pat_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PATConnectRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/integrations/oura/status":{"get":{"tags":["integrations"],"summary":"Oura Status","description":"Return connection status for the Oura integration.\n\nSelf-heals orphaned imports: a run left at ``running``/``pending`` by a process that\ndied mid-backfill (redeploy, OOM, SIGTERM — not a Python exception) is re-queued in the\nbackground so the status reflects an actually-progressing import rather than lying\n\"in progress\" forever. See :func:`_maybe_resume_orphaned_backfill`.","operationId":"oura_status_api_v1_integrations_oura_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/integrations/oura/disconnect":{"delete":{"tags":["integrations"],"summary":"Disconnect Oura","description":"Remove the Oura integration along with all data it produced.\n\nDisconnecting deletes the integration's category, which cascades (via DB-level\nON DELETE CASCADE FKs) to every metric in it, all their measurements, and the\nintegration row itself (user_integrations.category_id). Older integrations that\nwere never linked to a category fall back to deleting just the tokens row.","operationId":"disconnect_oura_api_v1_integrations_oura_disconnect_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/integrations/oura/sync":{"post":{"tags":["integrations"],"summary":"Trigger Oura Sync","description":"Start or resume an Oura import in the background.\n\nWith no body (or ``since=\"all\"``) this starts/resumes the full historical backfill from\nthe persisted cursor. Pass ``since=\"30d\"`` for a recent-only recovery sync. Returns 409\nif an import is already running in this process.","operationId":"trigger_oura_sync_api_v1_integrations_oura_sync_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/SyncRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/integrations/health-connect/supported":{"get":{"tags":["integrations"],"summary":"List Supported Types","description":"List the record types the server knows how to ingest.\n\nLets the mobile client (or a curious developer) confirm the shared vocabulary\nwithout reading source.","operationId":"list_supported_types_api_v1_integrations_health_connect_supported_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Supported Types Api V1 Integrations Health Connect Supported Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/integrations/health-connect/ingest":{"post":{"tags":["integrations"],"summary":"Ingest","description":"Ingest a batch of Health Connect records for the current user.\n\nIdempotent: re-sending the same window overwrites rather than duplicates. Unknown\nrecord types are skipped and reported in the response, not rejected, so a newer app\ncan send types an older server hasn't learned yet without failing the whole batch.","operationId":"ingest_api_v1_integrations_health_connect_ingest_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Ingest Api V1 Integrations Health Connect Ingest Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/integrations/health-connect/status":{"get":{"tags":["integrations"],"summary":"Status","description":"Return whether Health Connect has ingested anything, and how much.\n\n\"Connected\" here just means at least one successful ingest has created the\nintegration row — there is no token or remote account to be connected to.","operationId":"status_api_v1_integrations_health_connect_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Status Api V1 Integrations Health Connect Status Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/integrations/health-connect/disconnect":{"delete":{"tags":["integrations"],"summary":"Disconnect","description":"Remove the Health Connect integration and everything it imported.\n\nDeleting the category cascades (DB-level ON DELETE CASCADE) to its metrics, their\nmeasurements, and the integration row (user_integrations.category_id) — mirroring\nthe Oura disconnect. Nothing is written back to Health Connect on the device.","operationId":"disconnect_api_v1_integrations_health_connect_disconnect_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Disconnect Api V1 Integrations Health Connect Disconnect Delete"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/mcp/tokens":{"get":{"tags":["mcp"],"summary":"List Mcp Tokens","description":"List the caller's MCP personal access tokens (metadata only).","operationId":"list_mcp_tokens_api_v1_mcp_tokens_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpTokenListResponse"}}}}},"security":[{"HTTPBearer":[]}]},"post":{"tags":["mcp"],"summary":"Create Mcp Token","description":"Mint a new MCP token. The raw secret is returned once — store it securely.","operationId":"create_mcp_token_api_v1_mcp_tokens_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpTokenCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpTokenCreateResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v1/mcp/tokens/{token_id}":{"delete":{"tags":["mcp"],"summary":"Delete Mcp Token","description":"Revoke an MCP token belonging to the caller.","operationId":"delete_mcp_token_api_v1_mcp_tokens__token_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"token_id","in":"path","required":true,"schema":{"type":"integer","title":"Token Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v1/oura/webhook":{"get":{"tags":["oura"],"summary":"Verify Oura Webhook","description":"Respond to Oura's subscription verification challenge.\n\nWhen creating a webhook subscription, Oura sends a GET request with\n``verification_token`` and ``challenge`` query params. We must confirm the\ntoken matches our configured secret and echo back the challenge.\n\nArgs:\n    verification_token: Token Oura received when the subscription was created.\n    challenge: Random string Oura expects in the response body.\n\nReturns:\n    JSON body containing the challenge string.\n\nRaises:\n    HTTPException: If the verification token does not match.","operationId":"verify_oura_webhook_api_v1_oura_webhook_get","parameters":[{"name":"verification_token","in":"query","required":true,"schema":{"type":"string","title":"Verification Token"}},{"name":"challenge","in":"query","required":true,"schema":{"type":"string","title":"Challenge"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Verify Oura Webhook Api V1 Oura Webhook Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["oura"],"summary":"Receive Oura Webhook","description":"Receive and process Oura webhook events.\n\nOura sends a JSON payload with an ``event_type``, ``data_type``, ``object_id``,\nand ``user_id``. Signature is verified via HMAC-SHA256 before any processing\noccurs. The actual data fetch + DB write is dispatched as a background task so\nthe 200 is returned to Oura promptly (Oura retries on non-2xx responses).\n\nArgs:\n    request: Incoming HTTP request.\n    background_tasks: FastAPI background task queue.\n    db: Async database session.\n\nReturns:\n    JSON acknowledgement.","operationId":"receive_oura_webhook_api_v1_oura_webhook_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/v1/oura/webhook/status":{"get":{"tags":["oura"],"summary":"Oura Webhook Status","description":"Return the current Oura webhook subscriptions registered for this app.\n\nOperator-only: gated behind the service API key. It proxies to Oura's admin API\nusing this app's client secret and returns subscription metadata (IDs, callback\nURLs), so it must not be reachable unauthenticated.\n\nUseful for verifying subscriptions are active after deployment or re-registration.\n\nReturns:\n    Raw Oura subscription list.","operationId":"oura_webhook_status_api_v1_oura_webhook_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/v1/health":{"get":{"summary":"Health","operationId":"health_api_v1_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"AcceptRequest":{"properties":{"timestamp_overrides":{"additionalProperties":{"type":"string"},"type":"object","title":"Timestamp Overrides"},"rejected_indices":{"items":{"type":"integer"},"type":"array","title":"Rejected Indices"},"field_overrides":{"additionalProperties":{"$ref":"#/components/schemas/FieldOverride"},"type":"object","title":"Field Overrides"}},"type":"object","title":"AcceptRequest","description":"User choices when accepting a proposal session."},"CheckoutResponse":{"properties":{"url":{"type":"string","title":"Url"}},"type":"object","required":["url"],"title":"CheckoutResponse","description":"Where to send the browser to complete a purchase."},"ConnectUrlResponse":{"properties":{"url":{"type":"string","title":"Url"}},"type":"object","required":["url"],"title":"ConnectUrlResponse"},"EntitlementResponse":{"properties":{"tier":{"type":"string","title":"Tier"},"is_pro":{"type":"boolean","title":"Is Pro"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"source":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source"},"renews_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Renews At"},"cancel_at_period_end":{"type":"boolean","title":"Cancel At Period End","default":false}},"type":"object","required":["tier","is_pro"],"title":"EntitlementResponse","description":"A user's plan, as the client should see it.\n\nProvider-neutral by design — ``source`` distinguishes a comped account from a paid\none, but never names the Merchant of Record, so swapping it is invisible to the app."},"FeedbackRequest":{"properties":{"rating":{"type":"string","enum":["negative","positive"],"title":"Rating","default":"negative"},"category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category","description":"Optional failure category."},"comment":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Comment"}},"type":"object","title":"FeedbackRequest","description":"User feedback on an extraction session."},"FieldOverride":{"properties":{"value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Value"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"new_metric_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"New Metric Name"},"new_metric_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"New Metric Unit"},"new_metric_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"New Metric Category"},"new_metric_missing_policy":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"New Metric Missing Policy"}},"type":"object","title":"FieldOverride","description":"User edits to a single proposed measurement, applied at commit time.\n\nKeyed by the 0-based index among commit_measurement actions (same indexing as\ntimestamp_overrides). value/notes apply to the measurement itself. The\nnew_metric_* fields apply only to *new-metric* proposals (a commit_measurement\nwith a metric_temp_ref) — they override the linked propose_metric before the\nmetric is created. They're ignored for existing-metric proposals."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthConnectRecordIn":{"properties":{"type":{"type":"string","title":"Type"},"value":{"type":"number","title":"Value"},"start":{"type":"string","title":"Start"},"end":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"End"},"record_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Record Id"}},"type":"object","required":["type","value","start"],"title":"HealthConnectRecordIn","description":"One normalized record read from Health Connect on-device."},"IngestRequest":{"properties":{"records":{"items":{"$ref":"#/components/schemas/HealthConnectRecordIn"},"type":"array","title":"Records"}},"type":"object","title":"IngestRequest"},"McpTokenCreateRequest":{"properties":{"label":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Label"},"expiry_preset":{"type":"string","enum":["30d","90d","1y","never"],"title":"Expiry Preset"}},"type":"object","required":["expiry_preset"],"title":"McpTokenCreateRequest","description":"Body for creating a new MCP personal access token."},"McpTokenCreateResponse":{"properties":{"id":{"type":"integer","title":"Id"},"token":{"type":"string","title":"Token"},"token_prefix":{"type":"string","title":"Token Prefix"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"mcp_url":{"type":"string","title":"Mcp Url"},"cursor_snippet":{"type":"string","title":"Cursor Snippet"}},"type":"object","required":["id","token","token_prefix","label","expires_at","mcp_url","cursor_snippet"],"title":"McpTokenCreateResponse","description":"One-time response after token creation — includes the raw secret."},"McpTokenListResponse":{"properties":{"tokens":{"items":{"$ref":"#/components/schemas/McpTokenSummary"},"type":"array","title":"Tokens"}},"type":"object","required":["tokens"],"title":"McpTokenListResponse","description":"List of the caller's MCP tokens."},"McpTokenSummary":{"properties":{"id":{"type":"integer","title":"Id"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"token_prefix":{"type":"string","title":"Token Prefix"},"expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Expires At"},"last_used_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Used At"},"status":{"type":"string","title":"Status"},"created_at":{"type":"string","format":"date-time","title":"Created At"}},"type":"object","required":["id","label","token_prefix","expires_at","last_used_at","status","created_at"],"title":"McpTokenSummary","description":"Token metadata returned by list endpoints — never includes the raw secret."},"MeResponse":{"properties":{"id":{"type":"integer","title":"Id"},"email":{"type":"string","title":"Email"},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name"},"onboarded_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Onboarded At"},"is_pro":{"type":"boolean","title":"Is Pro","default":false}},"type":"object","required":["id","email"],"title":"MeResponse"},"MeasurementCreate":{"properties":{"value":{"type":"string","title":"Value"},"timestamp":{"type":"string","format":"date-time","title":"Timestamp"},"end_timestamp":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"End Timestamp"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"extra_data":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Extra Data"},"metric_id":{"type":"integer","title":"Metric Id"}},"type":"object","required":["value","timestamp","metric_id"],"title":"MeasurementCreate"},"PATConnectRequest":{"properties":{"access_token":{"type":"string","title":"Access Token"}},"type":"object","required":["access_token"],"title":"PATConnectRequest"},"PortalResponse":{"properties":{"url":{"type":"string","title":"Url"}},"type":"object","required":["url"],"title":"PortalResponse","description":"Where to send the browser to manage an existing subscription."},"SyncRequest":{"properties":{"since":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Since"}},"type":"object","title":"SyncRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"},"input":{"title":"Input"},"ctx":{"type":"object","title":"Context"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"},"APIKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"}}}}