{"openapi":"3.1.0","info":{"title":"imperfect-api","description":"FastAPI backend for the Imperfect health and fitness application","version":"0.1.0"},"paths":{"/health":{"get":{"tags":["health"],"summary":"Health","description":"Liveness check for load balancers and monitoring.","operationId":"health_health_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}},"head":{"tags":["health"],"summary":"Health","description":"Liveness check for load balancers and monitoring.","operationId":"health_health_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/attest/challenge":{"post":{"tags":["auth"],"summary":"Attest Challenge","description":"Issue a single-use register nonce (5 min TTL).","operationId":"attest_challenge_attest_challenge_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChallengeResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/attest/register":{"post":{"tags":["auth"],"summary":"Attest Register","description":"Verify a fresh attestation and upsert the device's App Attest key.\n\nIdempotent: a re-register with the same ``key_id`` overwrites the\nexisting row (the public key is deterministic for a given key id,\nso a \"different key\" would have failed Apple's attestation check).","operationId":"attest_register_attest_register_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttestRegisterRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttestRegisterResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/auth/health-probe/challenge":{"post":{"tags":["auth"],"summary":"Health Probe Challenge","description":"Issue a single-use probe nonce (60s TTL).","operationId":"health_probe_challenge_auth_health_probe_challenge_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProbeChallengeResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/auth/health-probe":{"post":{"tags":["auth"],"summary":"Health Probe","description":"Validate App Attest assertion + alice lookup → maybe mint a custom token.\n\nValidation order matches the cross-repo spec:\n1. App Attest (consume nonce, verify assertion, monotonic counter).\n2. ``fresh_sample.end_date`` within adaptive freshness window.\n3. ``len(record_ids) >= 10`` else ``insufficient_signal``.\n4. Rate-limit per key (5/hour).\n5. Alice ``/apple/identity-lookup`` and score.\n\nOn ``confident`` we set the ``imperfect_user_id`` custom claim before\nminting the token so alice (and other services reading the JWT) see\nthe resolved id immediately — mirrors ``/health-providers`` flow.\n\nThe raw body is read first so the canonical-JSON hash is computed\nfrom the parsed-but-not-Pydantic-reserialized ``payload`` sub-tree.\nPydantic v2 normalizes datetimes (``\"...Z\"`` → ``\"...+00:00\"``),\nwhich would mismatch the assertion's expected hash from the\nmobile client.","operationId":"health_probe_auth_health_probe_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProbeResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/channel_associations":{"post":{"tags":["channels"],"summary":"Confirm Channel Association","description":"Bind the channel identity from the token to the authenticated user.\n\nMounted on both ``POST /channel_associations`` (canonical, #858) and\n``POST /whatsapp_associations`` (legacy alias kept so the existing\nmobile client keeps working without a coordinated release). Both routes\nshare this handler; the channel is resolved from the token's\n:class:`ChannelAssociationRequest` row.","operationId":"confirm_channel_association_channel_associations_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}},{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}},{"name":"x-units","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Units"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelAssociationConfirmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Confirm Channel Association Channel Associations Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["channels"],"summary":"List Channel Associations","description":"List the authenticated user's active channel links (#1342).\n\nBacks the mobile on-open banner: the app reads this to decide whether to\nshow the \"you can plan routes on WhatsApp now\" nudge (mobile#726), and it\nunblocks the point-2 resume-poll fallback (mobile#725) by letting a client\nthat missed the silent ``channel_link_result`` push confirm link-state.\n\nResponse shape (narrow read contract — see :func:`_link_state_entry`)::\n\n    {\n      \"associations\": [\n        {\"channel\": \"whatsapp\", \"status\": \"active\", \"phone_number\": \"+52...\"}\n      ],\n      \"whatsapp_linked\": true\n    }\n\n``whatsapp_linked`` is the convenience flag the banner gates on; the list\nis there for any channel-agnostic UI. ``phone_number`` is WhatsApp-only;\n``display_name`` is omitted (it lives on the link-request row, not the\ndurable association).","operationId":"list_channel_associations_channel_associations_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response List Channel Associations Channel Associations Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/whatsapp_associations":{"post":{"tags":["channels"],"summary":"Confirm Channel Association","description":"Bind the channel identity from the token to the authenticated user.\n\nMounted on both ``POST /channel_associations`` (canonical, #858) and\n``POST /whatsapp_associations`` (legacy alias kept so the existing\nmobile client keeps working without a coordinated release). Both routes\nshare this handler; the channel is resolved from the token's\n:class:`ChannelAssociationRequest` row.","operationId":"confirm_channel_association_whatsapp_associations_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}},{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}},{"name":"x-units","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Units"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelAssociationConfirmRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Confirm Channel Association Whatsapp Associations Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/channel_associations/{channel}":{"delete":{"tags":["channels"],"summary":"Unlink Channel Association","description":"Revoke the caller's active link on ``channel`` (#2282).\n\nBacks the app's disconnect screen: 204 when this call revoked the link,\n404 when there was nothing to unlink (never linked, or already unlinked),\nso a repeat DELETE converges on the same unlinked state. The row is\nrevoked rather than deleted — the partial unique indexes cover live rows\nonly, so re-linking later works like a first link, while the account\nmerge's conflict check still sees the history. A replay of the old\nconfirmed deep-link token 409s via the revoked-link branch in\n:func:`confirm_channel_association`.\n\nThe user's live ``#IMP`` codes die first: with the row revoked, nothing\nblocks a re-link, so a consumed code re-sent from the same phone within\nits TTL would silently undo the disconnect via the code handler's\nself-heal replay (the PR #2283 review finding). Codes are deleted before\nthe row flips — every step is idempotent, so a failure between them\nleaves a retryable DELETE, never a revoked row with a live credential.","operationId":"unlink_channel_association_channel_associations__channel__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"channel","in":"path","required":true,"schema":{"$ref":"#/components/schemas/Channel"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/channel_link_codes":{"post":{"tags":["channels"],"summary":"Request Channel Link Code","description":"Issue a one-time WhatsApp link code for the authenticated user.\n\n``already_linked`` when the user already has an active WhatsApp\nassociation (just open the bot chat); otherwise mint a fresh code.","operationId":"request_channel_link_code_channel_link_codes_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Request Channel Link Code Channel Link Codes Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/channels/message-references":{"post":{"tags":["channels"],"summary":"Capture Channel Message Reference","description":"Capture ``platform_message_id -> share_id`` after a bridge sends.","operationId":"capture_channel_message_reference_channels_message_references_post","parameters":[{"name":"X-WhatsApp-Bridge-Token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Whatsapp-Bridge-Token"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelMessageReferenceCaptureRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelMessageReferenceCaptureResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{slug}/translations/{lang}":{"post":{"tags":["events"],"summary":"Translate Event On Demand","description":"Translate an event into ``lang`` on demand from the frontend (#659).\n\nIdempotent: if the cache is already warm for ``(slug, lang)``, the\nagent is NOT re-run (the ``source_hash`` short-circuit fires) and\nthe cached entry is returned verbatim. Frontend can call this on\nevery cache-miss without worrying about double-billing.\n\nReturns the persisted translation payload — the caller doesn't need a\nsecond read.","operationId":"translate_event_on_demand_events__slug__translations__lang__post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","description":"Event slug.","title":"Slug"},"description":"Event slug."},{"name":"lang","in":"path","required":true,"schema":{"type":"string","description":"Target BCP47 locale (``de-DE``, ``es-MX``, ``ca-ES``, ``en-GB``, …). Any code ``langcodes`` resolves to a real (language, territory) pair is accepted; de-DE, es-MX are pre-warmed during enrichment so cache hits are cheap, but other locales are translated on first request and cached the same way.","title":"Lang"},"description":"Target BCP47 locale (``de-DE``, ``es-MX``, ``ca-ES``, ``en-GB``, …). Any code ``langcodes`` resolves to a real (language, territory) pair is accepted; de-DE, es-MX are pre-warmed during enrichment so cache hits are cheap, but other locales are translated on first request and cached the same way."},{"name":"x-revalidate-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Revalidate-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Translate Event On Demand Events  Slug  Translations  Lang  Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/search":{"post":{"tags":["events"],"summary":"Search For Events","description":"Search for events using the hybrid discovery agent (DB + Web).\nReturns a STREAM of NDJSON (Newline Delimited JSON) events.\n\nEvent Types:\n- {\"type\": \"status\", \"msg\": \"...\", \"step\": \"...\"}\n- {\"type\": \"result\", \"data\": [...events...]}\n- {\"type\": \"error\", \"msg\": \"...\"}\n\n- **force_refresh**: If True, bypasses local cache and triggers a fresh web scrape.","operationId":"search_for_events_events_search_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventSearchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/by-slug/{slug}":{"get":{"tags":["events"],"summary":"Get Event By Slug","description":"Fetch a single canonical ``Event`` by its ``slug``.\n\nAuth is ``verify_token`` (not ``current_user``): a valid Firebase\ntoken is required, but the domain ``User`` does not have to exist\nyet. Anonymous callers MUST be able to reach this endpoint before\nthe provider-connect step mints their domain User, otherwise they\ncan't render the public event they came in for.","operationId":"get_event_by_slug_events_by_slug__slug__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Event By Slug Events By Slug  Slug  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/{event_id}":{"get":{"tags":["events"],"summary":"Get Event By Id","description":"Fetch a single canonical ``Event`` by its ID.","operationId":"get_event_by_id_events__event_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Event By Id Events  Event Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/events/discover-url":{"post":{"tags":["events"],"summary":"Extract Event From Url","description":"Extract event data from a user-provided URL.\nSkips web search and LLM selection — goes directly to Browser Use extraction.\nReturns a STREAM of NDJSON (Newline Delimited JSON) events.\n\nEvent Types:\n- {\"type\": \"status\", \"msg\": \"...\", \"step\": \"...\"}\n- {\"type\": \"result\", \"data\": [...events...]}\n- {\"type\": \"error\", \"msg\": \"...\"}","operationId":"extract_event_from_url_events_discover_url_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventURLSearchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/goals/session":{"get":{"tags":["goals"],"summary":"Bootstrap Goal Browser Session","description":"Create or refresh this browser's goals session; hand back its CSRF token.\n\nThe page's first call, and its call again after a reload — the CSRF token\nlives in memory, so asking again is how the page gets back the right to take\na turn on a conversation it already owns. Asking again returns the *same*\ntoken, so a second tab does not take that right away from the first (#1883).","operationId":"bootstrap_goal_browser_session_goals_session_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalBrowserSessionResponse"}}}}}}},"/goals/session/reset":{"post":{"tags":["goals"],"summary":"Reset Goal Browser Session","description":"Start over: this browser gets a new session, and so a new conversation.\n\nField-found on the voice e2e (#1947): there was no way to begin again. ``GET\n/goals/session`` keeps a live cookie's session on purpose — that is what makes a\nconversation survive a reload — ``GET /goals/conversation`` resumes the durable\none, and nothing rotated either, so \"new goal\" could only navigate back to the\nsame conversation forever.\n\nUnsafe, taking the same three signals every unsafe goals route takes, because\nfrom the athlete's point of view this ends their conversation: a cross-site page\nthat could reach it could throw away work somebody is in the middle of. The\nanswer is shaped exactly like the bootstrap's — replacement cookie, fresh CSRF\ntoken, the new session's id and expiry — so a client swallows it with the code\nit already has for ``GET /goals/session``.\n\nThe old goal session is left to its TTL rather than deleted;\n:meth:`~cheshire.resources.browser_sessions.BrowserSurface.rotate` says why.\n\n**An active voice call is not this route's business.** The audio is held between\nthe browser and the voice service, so nothing here could end one; the client\nhangs up first, and a call left running would only be delegating turns onto a\nsession its own page no longer holds.","operationId":"reset_goal_browser_session_goals_session_reset_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalBrowserSessionResponse"}}}}}}},"/goals/conversation":{"get":{"tags":["goals"],"summary":"Read Goal Conversation","description":"This browser's conversation as it stands, so a reload costs no turn.\n\nA safe read on the cookie alone: it changes nothing, and asking the athlete\nto re-say what they already said is the one thing a resumable conversation\nis supposed to make impossible. An empty answer is the honest one for a\nbrowser that has not spoken — a first open is not an error.\n\nThe rim is parsed once and both ``status`` and ``picture`` come off it, so\nthe two can never describe different documents — the same property the\nstream's own pair of frames has.","operationId":"read_goal_conversation_goals_conversation_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalConversationResponse"}}}}}}},"/goals/shared/{share_id}":{"get":{"tags":["goals"],"summary":"Read Shared Goal","description":"One published goal, for anyone holding the link.\n\n**No cookie and no Origin**, unlike every other route here: this serves only\nwhat an athlete deliberately published, to a stranger who was handed a URL,\nand requiring a browser session would mean the recipient needs one before\nthey can read a page meant for them. A revoked goal is not published, so it\n404s the moment it is taken down.","operationId":"read_shared_goal_goals_shared__share_id__get","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalSharePage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/goals/shared/{share_id}/revise":{"post":{"tags":["goals"],"summary":"Revise Shared Goal","description":"Start a conversation that revises this published goal, from the page alone.\n\nwonderland goals.md puts revising in the open on the page: while the live\nconversation is open the page just returns to it, and when it is gone —\nthe cookie lost, the session expired — this is the path. The share payload\nis the whole input: a fresh conversation is seeded with the goal exactly\nas the page carries it, and nothing else survives, so the first coach turn\nworks from the page without re-asking what it already says.\n\nUnsafe with all three browser signals, because it replaces whatever\nconversation this browser had — the same posture, rotation and response\nshape as ``POST /goals/session/reset``, so a client swallows it with the\ncode it already has. The published goal itself is untouched: what this\nsettles is a *new* goal, and publishing it is its own deliberate act.","operationId":"revise_shared_goal_goals_shared__share_id__revise_post","parameters":[{"name":"share_id","in":"path","required":true,"schema":{"type":"string","title":"Share Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalBrowserSessionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/goals/{goal_id}":{"get":{"tags":["goals"],"summary":"Read Goal Detail","description":"One goal in full, for the browser that settled it.\n\nThe owner's read, and the only one that carries the why, the checkpoints and\nthe fallbacks — the share page withholds all three on purpose and still does.\nOwning it means holding the goals cookie, exactly as it does for publishing:\nthis surface has no Firebase and an athlete settles goals before they have an\naccount, so the browser session IS the identity here (#1851).\n\nSafe, so the cookie alone is enough — no CSRF token and no Origin, like the\nconversation read. A goal owned by someone else 404s rather than 403s, so the\nstatus code cannot be used to tell a real goal id from an invented one.","operationId":"read_goal_detail_goals__goal_id__get","parameters":[{"name":"goal_id","in":"path","required":true,"schema":{"type":"string","title":"Goal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/goals/{goal_id}/publish":{"post":{"tags":["goals"],"summary":"Publish Goal","description":"Publish this browser's goal, and hand back the page it produced.\n\nOnly a settled goal has a promise to show, so an unsettled one is refused\nrather than published as a blank. Publishing twice is idempotent on an\nunchanged promise; publishing a changed one keeps the live link and dates\nthe change.","operationId":"publish_goal_goals__goal_id__publish_post","parameters":[{"name":"goal_id","in":"path","required":true,"schema":{"type":"string","title":"Goal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalSharePage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/goals/{goal_id}/unpublish":{"post":{"tags":["goals"],"summary":"Unpublish Goal","description":"Take this browser's goal down. The link is dead from this moment.\n\nThe athlete keeps the goal — only the page goes. Answers with the page as it\nlast stood, so the caller can show what was taken down without a second\nread.","operationId":"unpublish_goal_goals__goal_id__unpublish_post","parameters":[{"name":"goal_id","in":"path","required":true,"schema":{"type":"string","title":"Goal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalSharePage"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/goals/voice/session":{"get":{"tags":["goals"],"summary":"Probe Goal Voice","description":"Say the voice call exists, to a caller allowed to know that.\n\nlooking-glass's show-or-hide-the-button signal, and the whole client-side\ndark mechanism: a 404 means no button.","operationId":"probe_goal_voice_goals_voice_session_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalVoiceProbeResponse"}}}}}},"post":{"tags":["goals"],"summary":"Mint Goal Voice Call","description":"Mint one browser's call, and start answering its delegated goal turns.\n\nThe three browser signals are the same ones every unsafe goals route takes\n— the harness's JSON-body cookie was a relay artifact, and a deployed\ncaller's cookie rides its own request.\n\nThe delegated turns then run back through this very surface over HTTP,\npresenting the caller's session and an allowlisted origin. Self-HTTP rather\nthan an in-process call on purpose: it is the boundary the whole feature was\ntested against, and it is replica-safe because a goal session lives in\nPostgres rather than in whichever process answers.","operationId":"mint_goal_voice_call_goals_voice_session_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalVoiceCallRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GoalVoiceCallResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/goals/turn/stream":{"post":{"tags":["goals"],"summary":"Stream Goal Turn","description":"Run one turn and stream it: reply deltas first, document last.\n\nWritten or spoken, it is the same turn against the same conversation, so\nit is one endpoint taking one of the two: a ``message``, or an ``audio``\nnote that gets transcribed into one. Both at once is a request the page\ncould not have meant, so it is refused rather than silently resolved.\n\n``delivery`` names how the REPLY reaches the athlete — ``voice`` only when\na live voice front will speak it (the call sideband's turns). A voice-note\nupload is still a text turn: its reply lands on the screen.","operationId":"stream_goal_turn_goals_turn_stream_post","requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_stream_goal_turn_goals_turn_stream_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health-providers":{"get":{"tags":["health-providers"],"summary":"List Health Providers","description":"List all health provider connections for the current user.\n\nReturns an empty list when the caller holds an authenticated Firebase\ntoken but no domain User has been created yet (anonymous onboarding,\npre-Terra-auth). This avoids a noisy 403 on the welcome screen.","operationId":"list_health_providers_health_providers_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProviderListResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/health-providers/supported":{"get":{"tags":["health-providers"],"summary":"List Supported Providers","description":"List all supported health providers.","operationId":"list_supported_providers_health_providers_supported_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Supported Providers Health Providers Supported Get"}}}}}}},"/health-providers/apple-health/register":{"post":{"tags":["health-providers"],"summary":"Register Apple Health","description":"Register Apple Health as the user's health data provider.\n\nApple Health has no OAuth flow — the mobile client calls this endpoint\nafter the user grants HealthKit permissions. ``device_uuid`` is an\nopaque identifier persisted in the iCloud-synced Keychain, so it\nsurvives reinstalls on the same Apple ID. Branch behavior mirrors the\nOAuth callback — see ``_link_provider_identity``.\n\nDevice timezone arrives via the ``X-Device-Timezone`` header (validated\nby the ``device_timezone`` dependency) so the auto-created ``User`` on\nthe anonymous-onboarding branch lands with the caller's real IANA\ntimezone instead of the ``User.create`` default of ``\"UTC\"``. Mirrors\nthe Garmin OAuth callback path landed in #689 — required so mobile\ncan drop its preemptive ``POST /users`` priming call (#541) and let\nthe recovery branch of ``_link_provider_identity`` fire (#711).","operationId":"register_apple_health_health_providers_apple_health_register_post","deprecated":true,"security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProviderAppleRegisterRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProviderCallbackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health-providers/apple/register":{"post":{"tags":["health-providers"],"summary":"Register Apple Health","description":"Register Apple Health as the user's health data provider.\n\nApple Health has no OAuth flow — the mobile client calls this endpoint\nafter the user grants HealthKit permissions. ``device_uuid`` is an\nopaque identifier persisted in the iCloud-synced Keychain, so it\nsurvives reinstalls on the same Apple ID. Branch behavior mirrors the\nOAuth callback — see ``_link_provider_identity``.\n\nDevice timezone arrives via the ``X-Device-Timezone`` header (validated\nby the ``device_timezone`` dependency) so the auto-created ``User`` on\nthe anonymous-onboarding branch lands with the caller's real IANA\ntimezone instead of the ``User.create`` default of ``\"UTC\"``. Mirrors\nthe Garmin OAuth callback path landed in #689 — required so mobile\ncan drop its preemptive ``POST /users`` priming call (#541) and let\nthe recovery branch of ``_link_provider_identity`` fire (#711).","operationId":"register_apple_health_health_providers_apple_register_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProviderAppleRegisterRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProviderCallbackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health-providers/{provider}/connect":{"post":{"tags":["health-providers"],"summary":"Initiate Provider Connection","description":"Initiate OAuth connection with a health provider.\n\nReturns an authorization URL that the app should open in a browser/webview.\nThe user will authenticate with the provider and be redirected back to the app.\nAnonymous Firebase tokens are accepted so onboarding can begin before a\nUser exists; identity is resolved on the callback.","operationId":"initiate_provider_connection_health_providers__provider__connect_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"$ref":"#/components/schemas/HealthProviderType"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProviderConnectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProviderConnectResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health-providers/{provider}/callback":{"post":{"tags":["health-providers"],"summary":"Handle Provider Callback","description":"Handle OAuth callback from health provider.\n\nThe app should call this endpoint after receiving the OAuth callback\nwith the authorization code and state parameters. ``user`` may be\n``None`` during first-time onboarding when the caller holds an\nanonymous Firebase token — identity is resolved via\n``_link_provider_identity``.\n\nFlow:\n1. Validate OAuth state\n2. Delegate code exchange + token storage to alice\n3. Resolve mapping via ``_link_provider_identity`` (branches on\n   whether the provider identity is new, already-owned, a collision,\n   or recoverable).","operationId":"handle_provider_callback_health_providers__provider__callback_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"$ref":"#/components/schemas/HealthProviderType"}},{"name":"login_only","in":"query","required":false,"schema":{"type":"boolean","description":"When true, return 404 if the provider identity does not already map to a User. Used by the mobile login flow to avoid auto-creating ghost accounts.","default":false,"title":"Login Only"},"description":"When true, return 404 if the provider identity does not already map to a User. Used by the mobile login flow to avoid auto-creating ghost accounts."},{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProviderCallbackRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProviderCallbackResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health-providers/{provider}":{"get":{"tags":["health-providers"],"summary":"Get Provider Connection","description":"Get the current user's connection to a specific health provider.","operationId":"get_provider_connection_health_providers__provider__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"$ref":"#/components/schemas/HealthProviderType"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthProviderConnectionResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["health-providers"],"summary":"Disconnect Provider","description":"Disconnect from a health provider.\n\nDelegates revocation to alice and deletes the local connection mapping.","operationId":"disconnect_provider_health_providers__provider__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"$ref":"#/components/schemas/HealthProviderType"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Disconnect Provider Health Providers  Provider  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health-providers/connections/{connection_id}":{"delete":{"tags":["health-providers"],"summary":"Disconnect By Connection Id","description":"Disconnect a specific health provider connection by ID.\n\nUseful when a user has multiple connections for the same provider\n(e.g., multiple Terra brands like Whoop + Clue).","operationId":"disconnect_by_connection_id_health_providers_connections__connection_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"connection_id","in":"path","required":true,"schema":{"type":"string","title":"Connection Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Disconnect By Connection Id Health Providers Connections  Connection Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/onboarding/{provider}/insights":{"get":{"tags":["health-providers"],"summary":"Get Onboarding Insights","description":"Seven onboarding insight cards for the teaser animation.\n\nMirrors the cards the mobile HealthKit teaser extracts locally for Apple\nHealth users, but computed server-side from the provider the user just\nconnected. Kinds with no supporting data are omitted.\n\n``provider`` is the brand the mobile client just connected: ``garmin``\n(direct OAuth), ``apple_health``, or any Terra-backed wearable (``coros``,\n``whoop``, …). The factory's brand scope is the gatekeeper — passing a\nbrand the user isn't connected to returns 422 — and it is what keeps the\nread deduped against the user's *other* links (#1693).","operationId":"get_onboarding_insights_onboarding__provider__insights_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"provider","in":"path","required":true,"schema":{"type":"string","title":"Provider"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingInsightsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/onboarding/intake":{"post":{"tags":["onboarding"],"summary":"Onboarding Intake","description":"Drive one turn of the conversational onboarding intake.\n\nAccepts either ``text`` or ``audio`` (exactly one). Returns an\n``application/x-ndjson`` stream with the per-turn event sequence:\n``session`` → optional ``transcribing``/``transcribed`` →\n``thinking`` → ``writes`` → ``agent_reply`` → ``done``. Heartbeats\n(``{\"event\": \"heartbeat\"}``) keep the socket alive during slow\nsteps.","operationId":"onboarding_intake_onboarding_intake_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_onboarding_intake_onboarding_intake_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/public/events":{"get":{"tags":["public-events"],"summary":"List Public Events","description":"Slug + ``updated_at`` projection of every event.\n\nFeeds looking-glass's ``generateStaticParams`` and the events\nsitemap — both want the full slug universe and nothing else.","operationId":"list_public_events_public_events_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response List Public Events Public Events Get"}}}}}}},"/public/events/{slug_or_id}":{"get":{"tags":["public-events"],"summary":"Get Public Event","description":"Full event document, resolved by slug first, then by ``_id``.","operationId":"get_public_event_public_events__slug_or_id__get","parameters":[{"name":"slug_or_id","in":"path","required":true,"schema":{"type":"string","title":"Slug Or Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Public Event Public Events  Slug Or Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/public/event-details/{event_id}":{"get":{"tags":["public-events"],"summary":"Get Public Event Details","description":"Full ``event_details`` document for the shared event ``_id``.","operationId":"get_public_event_details_public_event_details__event_id__get","parameters":[{"name":"event_id","in":"path","required":true,"schema":{"type":"string","title":"Event Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Public Event Details Public Event Details  Event Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/onboarding/race-recommendations":{"post":{"tags":["onboarding"],"summary":"Race Recommendations","description":"Stream race recommendations as NDJSON.\n\nAccepts text, audio (transcribed), or neither. Location is resolved\nfrom the request IP via ipgeolocation.io (cached in Valkey); failures\nfall back to ``None`` and the agent runs without a country bias.","operationId":"race_recommendations_onboarding_race_recommendations_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_race_recommendations_onboarding_race_recommendations_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/onboarding/race-recommendations/discover":{"post":{"tags":["onboarding"],"summary":"Race Recommendations Discover","description":"Stream web discovery for a user-named race and attach on success.\n\nOn success: streams the underlying discovery events plus a final\n``attached`` event with the resulting ``user_event``. On failure:\ncreates an ``EventCandidate`` for manual review and emits a\n``not_found`` terminal event.","operationId":"race_recommendations_discover_onboarding_race_recommendations_discover_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscoverRecommendationRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/route-send/garmin/start":{"post":{"tags":["route-send"],"summary":"Start Garmin Route Send","description":"Start a public web Garmin route-send OAuth attempt.","operationId":"start_garmin_route_send_route_send_garmin_start_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteSendStartRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteSendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/route-send/garmin/callback":{"post":{"tags":["route-send"],"summary":"Complete Garmin Route Send Callback","description":"Complete the static callback handoff from looking-glass.","operationId":"complete_garmin_route_send_callback_route_send_garmin_callback_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteSendCallbackRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteSendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/route-send/garmin/links/resolve":{"post":{"tags":["route-send"],"summary":"Resolve Garmin Route Send Link","description":"Resolve a durable Garmin link without trusting the cookie alone.","operationId":"resolve_garmin_route_send_link_route_send_garmin_links_resolve_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteSendLinkResolveRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteSendLinkResolveResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/route-send/garmin/sessions/{session_id}":{"get":{"tags":["route-send"],"summary":"Get Garmin Route Send Status","description":"Return the browser-safe status for a route-send session.","operationId":"get_garmin_route_send_status_route_send_garmin_sessions__session_id__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteSendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/route-send/garmin/sessions/{session_id}/send":{"post":{"tags":["route-send"],"summary":"Retry Garmin Route Send","description":"Retry course creation after callback exchange has succeeded.","operationId":"retry_garmin_route_send_route_send_garmin_sessions__session_id__send_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteSendResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/route-suggestion-pools":{"get":{"tags":["route-suggestion-pools"],"summary":"Get Route Suggestion Pool","description":"Return the cached pool for ``key``; 404 on miss or expiry.","operationId":"get_route_suggestion_pool_route_suggestion_pools_get","parameters":[{"name":"key","in":"query","required":true,"schema":{"type":"string","minLength":1,"title":"Key"}},{"name":"x-revalidate-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Revalidate-Secret"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Route Suggestion Pool Route Suggestion Pools Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["route-suggestion-pools"],"summary":"Put Route Suggestion Pool","description":"Upsert the pool under its cache key with native Valkey expiry.","operationId":"put_route_suggestion_pool_route_suggestion_pools_post","parameters":[{"name":"x-revalidate-secret","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Revalidate-Secret"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RouteSuggestionPoolWrite"}}}},"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/routes/session":{"get":{"tags":["routes"],"summary":"Bootstrap Browser Route Session","description":"Create or refresh the current browser's public Routes session.","operationId":"bootstrap_browser_route_session_routes_session_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteSessionResponse"}}}}}}},"/routes/sessions":{"get":{"tags":["routes"],"summary":"List Browser Route Sessions","description":"List route sessions visible to the current browser.","operationId":"list_browser_route_sessions_routes_sessions_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteSessionListResponse"}}}}}}},"/routes/sessions/{session_id}":{"get":{"tags":["routes"],"summary":"Get Browser Route Session","description":"Return one browser route session if the cookie owns it.","operationId":"get_browser_route_session_routes_sessions__session_id__get","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteSessionSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/routes/sessions/{session_id}/heartbeat":{"post":{"tags":["routes"],"summary":"Heartbeat Browser Route Session","description":"Unsafe no-op used by the browser to keep the session warm.","operationId":"heartbeat_browser_route_session_routes_sessions__session_id__heartbeat_post","parameters":[{"name":"session_id","in":"path","required":true,"schema":{"type":"string","title":"Session Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteSessionSummary"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/routes":{"get":{"tags":["routes"],"summary":"List Browser Route Runs","description":"List route runs visible to the current browser session.","operationId":"list_browser_route_runs_routes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteRunListResponse"}}}}}},"post":{"tags":["routes"],"summary":"Create Browser Route Run","description":"Start a browser-owned public route run and stream it live (SSE).\n\nThe Cheshire run is decoupled from this socket — a detached consumer drives\nit to a durable terminal even if the browser backgrounds. ``X-Route-Run-Id``\nflushes with the headers; reconnect/replay is ``GET /routes/{run_id}/events``\nwith ``Last-Event-ID``.\n\nAdmission comes before the run row (#1909): a refused ask costs one Valkey\nincrement, not a persisted run nobody will ever read.","operationId":"create_browser_route_run_routes_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/routes/{run_id}":{"get":{"tags":["routes"],"summary":"Get Browser Route Run","description":"Read a public route run: owner sees everything, others see the slate.\n\nOwner (matching session cookie) → full run, exactly as before. Anyone else\n→ a read-only projection of a terminal, successful run (#1549), so a shared\nlink renders the persisted slate instead of recomputing it. A non-owner\nopen of a non-terminal, errored, or expired run stays 404. Refine and\nlive-tail (``/continue``, ``/events``) remain owner-only.","operationId":"get_browser_route_run_routes__run_id__get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteRunResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/routes/{run_id}/events":{"get":{"tags":["routes"],"summary":"Stream Browser Route Run Events","description":"Reconnect/replay a route run as SSE: durable snapshot + live tail.\n\nResumes after ``Last-Event-ID`` (the Cheshire seq the browser last saw): a\nfinished run returns its persisted events and closes; a live run replays\nwhat's persisted then tails Cheshire's buffer. Read-only and rate-limited on\nthe loose ``safe`` scope so mobile resume is never blocked.","operationId":"stream_browser_route_run_events_routes__run_id__events_get","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/routes/{run_id}/continue":{"post":{"tags":["routes"],"summary":"Continue Browser Route Run","description":"Iterate from a prior run: a fresh Cheshire run linked by provenance (SSE).\n\nCheshire #1138 runs are stateless (no server-side route session to resume),\nso a continuation is a new run seeded with the parent's lineage; the prompt\ncarries the refinement. Streams live exactly like create — a refinement is a\nwhole route search, so it is admitted and charged like one (#1909).","operationId":"continue_browser_route_run_routes__run_id__continue_post","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteContinueRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/routes/{run_id}/publish":{"post":{"tags":["routes"],"summary":"Publish Browser Route Run","description":"Publish one browser-owned route option as a public route page.","operationId":"publish_browser_route_run_routes__run_id__publish_post","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRoutePublishRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRoutePublishResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/routes/{run_id}/analytics":{"post":{"tags":["routes"],"summary":"Record Browser Route Analytics","description":"Retain a sanitized browser route funnel/outcome event.","operationId":"record_browser_route_analytics_routes__run_id__analytics_post","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteAnalyticsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteAnalyticsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/routes/{run_id}/report":{"post":{"tags":["routes"],"summary":"Report Browser Route Run","description":"Record \"this route is wrong\" against a served run (#1598).\n\nThe reason bucket triages; the reported candidate's seq into the run's\ndurable ``events`` is what makes the report *replayable* —\n``cheshire route-reports export`` turns it into an eval-fixture candidate\nwithout anyone reconstructing the geometry by hand, which is exactly the\nwork the Seaport Village incident (#1589) needed done manually.","operationId":"report_browser_route_run_routes__run_id__report_post","parameters":[{"name":"run_id","in":"path","required":true,"schema":{"type":"string","title":"Run Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteReportRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BrowserRouteReportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/session":{"post":{"tags":["terra"],"summary":"Generate Widget Session","description":"Generate a Terra widget session for user authentication.\n\nThe User row is not required (and not created) at this point — the\nTerra ``auth`` webhook handler creates it once the user actually\ncompletes the provider connection.\n\nWe pass a **fresh, never-reused** ``reference_id`` (not the Firebase\nUID) so Terra never sees the same reference twice across reinstalls —\nthat's what stops the duplicate-account / orphaned-data bleeding\n(#979, #1094): no ambiguous state, ``user_reauth`` never fires. The\nFirebase UID and the device timezone (from the ``X-Device-Timezone``\nheader, PR #529) are persisted on a ``TerraSession`` row keyed by the\nwidget session id Terra returns. The ``auth`` webhook reads the row by\n``widget_session_id`` to recover the identity and canonicalize the\nreference to the imperfect-api user id.","operationId":"generate_widget_session_session_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WidgetSessionRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Generate Widget Session Session Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/session/sdk-token":{"post":{"tags":["terra"],"summary":"Generate Sdk Token","description":"Generate a Terra SDK auth token for mobile-only sources (Health Connect).","operationId":"generate_sdk_token_session_sdk_token_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Generate Sdk Token Session Sdk Token Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/users/{user_id}/active_event":{"get":{"tags":["user-events"],"summary":"Get Active User Event","description":"Get the user's closest-by-date active or planned event.\n\nBack-compat surface for old mobile builds — always returns exactly one\nevent. New clients should call ``GET /user_events`` for the full\ncalendar.\n\nThe \"no active event\" case is an **expected, handled outcome**, not a\nfault: the mobile onboarding probe (``auth_controller`` →\n``_hasActiveUserEvent``) calls this on every launch and reads a 404 as\n\"no goal yet\" — the normal state after cancelling/completing a goal, or\nfor a brand-new user. We therefore *return* the 404 instead of *raising*\n``HTTPException``: Logfire's FastAPI instrumentation records every\nexception raised inside an endpoint as a span ``exception`` event\n(``is_exception=true``), so a raised 404 here floods the error views with\nfalse positives that drown real failures (#1498). Returning the response\nleaves the wire contract identical (same 404, same body) without emitting\nan exception event. Same intent as the outbound-404 policy in\n``cheshire/telemetry.py`` (#1478) — keep an expected 404 out of the error\nviews — but a different mechanism: that hook re-stamps the span *level* on\nan httpx response, this one avoids recording the exception at all. The\n*other* ``HTTPException(404)`` sites in this module signal genuinely-missing\nresources and stay as raises, so a real 404 is still distinguishable in\ntelemetry (those carry an ``exception`` event; this one does not).","operationId":"get_active_user_event_users__user_id__active_event_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get Active User Event Users  User Id  Active Event Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_events":{"get":{"tags":["user-events"],"summary":"List User Events","description":"Return the user's calendar ordered closest-by-date first.\n\nDefaults to ``planned`` + ``active`` so the new mobile calendar UI can\nrender upcoming races without filtering completed/cancelled history.\nCustom status filters use the same calendar ordering so the contract\nis uniform across queries.","operationId":"list_user_events_user_events_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"$ref":"#/components/schemas/UserEventStatus"}},{"type":"null"}],"description":"Filter by status. Defaults to planned + active.","title":"Status"},"description":"Filter by status. Defaults to planned + active."},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response List User Events User Events Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["user-events"],"summary":"Create User Event","description":"Create or update a user's association with an event.\n\n- **Catalog attach** (``event_id`` provided): looks up the existing\n  ``Event`` and attaches via ``UserEvent.attach``.\n- **Prefilled attach** (no ``event_id``): materializes an\n  ``Event(data_quality=prefilled)`` from the prefilled payload,\n  attaches, and dispatches a ``DiscoverySession(prefilled_confirm)``\n  so the discovery worker enriches it asynchronously.\n\n``replace_existing`` (default ``True``) preserves the legacy\nsingle-goal flow that old mobile builds depend on — every other\nplanned/active row is cancelled before the new one is inserted.\nCalendar-aware clients send ``False`` to append.","operationId":"create_user_event_user_events_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserEventCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Create User Event User Events Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_events/history":{"get":{"tags":["user-events"],"summary":"Get User Event History","description":"Get all user events (planned, active, completed, cancelled).","operationId":"get_user_event_history_user_events_history_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true},"title":"Response Get User Event History User Events History Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_events/{user_event_id}":{"patch":{"tags":["user-events"],"summary":"Update User Event","description":"Update post-goal questionnaire fields on a user event.\n\n``goal_summary`` is recomputed from ``motivation`` on save (see\n``UserEvent.async_save``), so a PATCH that changes the meaning returns a\nfresh label. A ``race_tier`` change that shifts the taper schedules a\nbackground replan, matching the ``set_event_goal_meaning`` tool path.","operationId":"update_user_event_user_events__user_event_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_event_id","in":"path","required":true,"schema":{"type":"string","title":"User Event Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserEventUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Update User Event User Events  User Event Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["user-events"],"summary":"Delete User Event","description":"Soft-cancel a user event (status=cancelled).\n\nThe row stays in Postgres to preserve history — same shape as the\n``cancel_event`` tool path. Idempotent on already-cancelled rows\n(returns 404 because there's nothing active to cancel).","operationId":"delete_user_event_user_events__user_event_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_event_id","in":"path","required":true,"schema":{"type":"string","title":"User Event Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/UserEventCancelRequest"},{"type":"null"}],"title":"Req"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Delete User Event User Events  User Event Id  Delete"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_states/week":{"get":{"tags":["user-states"],"summary":"Get Week View","description":"Assembly of the rolling plan view.\n\nReturns past boards (up to 6 days), today's board, and intents\nfor future days (up to 6 days). If no ``WeeklyPlan`` exists for the\nuser, creates a ``trigger=\"pending\"`` placeholder and enqueues a\nbackground generation — the response comes back immediately with\n``plan_trigger=\"pending\"`` so the client can poll until the real plan\nis ready. Concurrent calls while pending do not re-enqueue: the\nplaceholder short-circuits the ``current_plan is None`` check. The local\nplan lifecycle always runs; a runtime flag may substitute a fully validated\nCheshire weekly view only during response assembly.","operationId":"get_week_view_user_states_week_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"latitude","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"}},{"name":"longitude","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"}},{"name":"timezone","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"}},{"name":"now_iso","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Now Iso"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}},{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}},{"name":"x-units","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Units"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WeekViewResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_states":{"get":{"tags":["user-states"],"summary":"Get User States","operationId":"get_user_states_user_states_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"latitude","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"}},{"name":"longitude","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"}},{"name":"timezone","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"}},{"name":"now_iso","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Now Iso"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"x-app-version","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-App-Version"}},{"name":"x-home-trigger","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Home-Trigger"}},{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}},{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}},{"name":"x-units","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Units"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get User States User States Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["user-states"],"summary":"Post User States","operationId":"post_user_states_user_states_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"x-app-version","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-App-Version"}},{"name":"x-home-trigger","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Home-Trigger"}},{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}},{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}},{"name":"x-units","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Units"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StateCreateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Post User States User States Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_states/stream":{"post":{"tags":["user-states"],"summary":"Stream User States","description":"Stream board generation via SSE.\n\nAccepts ``application/json`` (legacy, text-only) or ``multipart/form-data``\n(new, supports any combination of ``audio``, ``image``, ``document``, and\n``feedback`` fields). When audio is provided, the stream emits\n``transcribing`` → ``transcript`` before board partials. When an image or\nPDF is provided, the stream emits ``analyzing`` immediately so the UI can\nshow progress before the agent starts.\n\nDelivers partial board fields progressively as the agent generates them.\nIf the client disconnects mid-stream on a text-only turn, generation\ncompletes in the background and a push notification is sent. Multimodal\nturns skip recovery (the upload would have to be redone) and surface the\ndisconnect to the client instead.","operationId":"stream_user_states_user_states_stream_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}},{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}},{"name":"x-units","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Units"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_states/boards/{board_id}/activities/{activity_id}/details":{"get":{"tags":["user-states"],"summary":"Get Activity Details","description":"Generate or return cached activity details (steps, technique_notes).\n\nFirst request triggers a lightweight agent call (~2-3s).\nSubsequent requests return cached details instantly.","operationId":"get_activity_details_user_states_boards__board_id__activities__activity_id__details_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"board_id","in":"path","required":true,"schema":{"type":"string","title":"Board Id"}},{"name":"activity_id","in":"path","required":true,"schema":{"type":"string","title":"Activity Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"accept-language","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Accept-Language"}},{"name":"x-device-timezone","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Device-Timezone"}},{"name":"x-units","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Units"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Activity"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_states/activity/resolve":{"post":{"tags":["user-states"],"summary":"Resolve Activity","description":"Resolve an activity (complete or skip) within a board.\nUpdates the plan status and marks the activity as completed or skipped.","operationId":"resolve_activity_user_states_activity_resolve_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityResolveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Resolve Activity User States Activity Resolve Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/user_states/activity/complete":{"post":{"tags":["user-states"],"summary":"Complete Activity","description":"Deprecated: use POST /user_states/activity/resolve instead.","operationId":"complete_activity_user_states_activity_complete_post","deprecated":true,"security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityResolveRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Complete Activity User States Activity Complete Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/users/me":{"get":{"tags":["users"],"summary":"Get Current User","operationId":"get_current_user_users_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Get Current User Users Me Get"}}}}},"security":[{"HTTPBearer":[]}]}},"/users/{user_id}":{"get":{"tags":["users"],"summary":"Get User","operationId":"get_user_users__user_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get User Users  User Id  Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/users":{"post":{"tags":["users"],"summary":"Create User","operationId":"create_user_users_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCreateRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Response Create User Users Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/notifications/token":{"post":{"tags":["users"],"summary":"Register Fcm Token","operationId":"register_fcm_token_notifications_token_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}},{"name":"x-app-version","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-App-Version"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FCMTokenRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"string"},"title":"Response Register Fcm Token Notifications Token Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/users/{user_id}/profile":{"get":{"tags":["users"],"summary":"Get User Profile","operationId":"get_user_profile_users__user_id__profile_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Get User Profile Users  User Id  Profile Get"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/users_profiles/{profile_id}":{"patch":{"tags":["users"],"summary":"Patch User Profile","operationId":"patch_user_profile_users_profiles__profile_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"profile_id","in":"path","required":true,"schema":{"type":"string","title":"Profile Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfileUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Patch User Profile Users Profiles  Profile Id  Patch"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/notifications/{notification_id}/response":{"post":{"tags":["users"],"summary":"Respond To Notification","description":"Receive a user's response to an interactive push notification.\n\nAccepts button taps (action=button_1/2/3) or text replies (action=reply).\nThe response is treated as board feedback: it gets persisted as a\nBoardMessage and triggers a board regeneration so the coach can react.","operationId":"respond_to_notification_notifications__notification_id__response_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"notification_id","in":"path","required":true,"schema":{"type":"string","title":"Notification Id"}},{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationResponseRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationResponseResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/users/{user_id}/send-silent":{"post":{"tags":["users"],"summary":"Send Silent Push Debug","description":"Trigger the widget-refresh silent push for the calling user.\n\n``current_user`` binds the path ``user_id`` and 403s on a mismatch, so a\ncaller can only refresh their own widget. Before that dependency existed\nhere the endpoint was reachable unauthenticated for any ``user_id``.","operationId":"send_silent_push_debug_users__user_id__send_silent_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Send Silent Push Debug Users  User Id  Send Silent Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/account":{"delete":{"tags":["users"],"summary":"Delete Account Endpoint","description":"Request account deletion.\n\nRequired for Apple App Store Review Guideline 5.1.1.\nRecords the deletion request and disconnects health providers.","operationId":"delete_account_endpoint_account_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"user_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDeleteRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountDeleteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/whatsapp_webhooks":{"post":{"tags":["whatsapp"],"summary":"Whatsapp Webhook","description":"Parse the WhatsApp multipart, build a channel-blind ``InboundMessage``,\nand dispatch to the generic core.\n\nMultipart contract (#810, #814): ``payload`` is the JSON-encoded\n``WhatsAppWebhookRequest`` body; ``audio``, ``image``, and ``document``\nare mutually-exclusive optional file fields. At least one of\n``text``/``audio``/``image``/``document`` must be present.\n\nReturns 200 after the durable turn is enqueued; the coach run or invite\nsend happens in the channel worker.","operationId":"whatsapp_webhook_whatsapp_webhooks_post","parameters":[{"name":"x-whatsapp-bridge-token","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Whatsapp-Bridge-Token"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_whatsapp_webhook_whatsapp_webhooks_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Response Whatsapp Webhook Whatsapp Webhooks Post"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/":{"get":{"summary":"Root","description":"Root endpoint.","operationId":"root__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"type":"object","title":"Response Root  Get"}}}}}}}},"components":{"schemas":{"AccountDeleteRequest":{"properties":{"reason":{"$ref":"#/components/schemas/AccountDeletionReason"},"reason_details":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Reason Details","description":"Optional free-text elaboration on the deletion reason"}},"type":"object","required":["reason"],"title":"AccountDeleteRequest","description":"Request schema for account deletion."},"AccountDeleteResponse":{"properties":{"message":{"type":"string","title":"Message"},"deleted_at":{"type":"string","format":"date-time","title":"Deleted At"}},"type":"object","required":["message","deleted_at"],"title":"AccountDeleteResponse","description":"Response schema for account deletion."},"AccountDeletionReason":{"type":"string","enum":["no_longer_using","privacy_concerns","found_alternative","too_many_notifications","not_working","other"],"title":"AccountDeletionReason","description":"Reasons for account deletion (Apple App Store 5.1.1 compliance)."},"Activity":{"properties":{"id":{"type":"string","title":"Id"},"type":{"type":"string","enum":["hydration","nutrition","movement","rest","mental","preparation","facilities"],"title":"Type","description":"Category of the activity"},"status":{"type":"string","enum":["pending","completed","skipped"],"title":"Status","description":"Execution status","default":"pending"},"params":{"$ref":"#/components/schemas/ActivityParams"},"title":{"type":"string","title":"Title","description":"Short title. E.g. 'Hydration'."},"description":{"type":"string","title":"Description","description":"Actionable description with specific amounts in the user's unit system. E.g. 'Drink 500ml water with electrolytes' (metric) / 'Drink 16oz water with electrolytes' (imperial)."},"headline":{"type":"string","title":"Headline","description":"Imperative + quantified, 3-5 words, in the user's unit system. E.g. 'Toma 500ml Agua', 'Run 5km Easy' (metric) / 'Drink 16oz Water', 'Run 3mi Easy' (imperial). Must include a specific number/amount.","default":""},"short_description":{"type":"string","title":"Short Description","description":"Brief context, 2-4 words. E.g. 'Pre-run Hydration'. Never repeat headline words.","default":""},"sentiment":{"type":"string","enum":["positive","caution","urgent","neutral"],"title":"Sentiment","description":"urgent: immediate health needs (dehydration, overtraining). caution: needs attention (poor sleep, high stress). positive: on track. neutral: informational.","default":"neutral"},"requires_completion":{"type":"boolean","title":"Requires Completion","description":"false ONLY for final sleep/rest items in the plan. These passive items must be LAST in the list.","default":true},"steps":{"anyOf":[{"items":{"$ref":"#/components/schemas/ActivityStep"},"type":"array"},{"type":"null"}],"title":"Steps","description":"Detailed step-by-step instructions for executing the activity"},"technique_notes":{"anyOf":[{"items":{"$ref":"#/components/schemas/TechniqueNote"},"type":"array"},{"type":"null"}],"title":"Technique Notes","description":"Technique tips and guidance for proper execution"},"estimated_duration_minutes":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Estimated Duration Minutes","description":"Total estimated duration in minutes"}},"type":"object","required":["type","params","title","description"],"title":"Activity","description":"A discrete unit of action in the user's plan."},"ActivityParams":{"properties":{"target_value":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Target Value","description":"Numeric target (e.g., 500, 5, 30)"},"unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Unit","description":"Unit of measure in the user's system (e.g., ml or oz, km or mi, min)"},"item":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Item","description":"Specific item (e.g., water, zone 2 run, protein)"}},"type":"object","title":"ActivityParams","description":"Structured parameters for activity execution and validation."},"ActivityResolveRequest":{"properties":{"board_id":{"type":"string","title":"Board Id"},"activity_id":{"type":"string","title":"Activity Id"},"action":{"type":"string","enum":["complete","skip"],"title":"Action","default":"complete"},"now_iso":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Now Iso"}},"type":"object","required":["board_id","activity_id"],"title":"ActivityResolveRequest","description":"Request schema for resolving an activity (complete or skip)."},"ActivityStep":{"properties":{"order":{"type":"integer","title":"Order","description":"Step sequence number (1, 2, 3...)"},"title":{"type":"string","title":"Title","description":"Short step title, e.g. 'Leg Swings', 'Main Set'"},"instruction":{"type":"string","title":"Instruction","description":"Clear instruction of what to do, e.g. 'Swing each leg forward and back 10 times'"},"duration_seconds":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Seconds","description":"Duration in seconds if time-based"},"repetitions":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Repetitions","description":"Number of repetitions if rep-based"},"tip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tip","description":"Optional technique tip, e.g. 'Keep your core engaged'"}},"type":"object","required":["order","title","instruction"],"title":"ActivityStep","description":"A micro-instruction within an activity for detailed guidance."},"AttestRegisterRequest":{"properties":{"key_id":{"type":"string","title":"Key Id"},"attestation_object":{"type":"string","title":"Attestation Object"},"nonce":{"type":"string","title":"Nonce"}},"type":"object","required":["key_id","attestation_object","nonce"],"title":"AttestRegisterRequest","description":"Body for ``POST /attest/register``."},"AttestRegisterResponse":{"properties":{"ok":{"type":"boolean","title":"Ok","default":true}},"type":"object","title":"AttestRegisterResponse","description":"Response for ``POST /attest/register``."},"Body_onboarding_intake_onboarding_intake_post":{"properties":{"session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"audio":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"Audio"}},"type":"object","title":"Body_onboarding_intake_onboarding_intake_post"},"Body_race_recommendations_onboarding_race_recommendations_post":{"properties":{"session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id"},"text":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Text"},"audio":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"Audio"}},"type":"object","title":"Body_race_recommendations_onboarding_race_recommendations_post"},"Body_stream_goal_turn_goals_turn_stream_post":{"properties":{"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"audio":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"Audio"},"locale":{"anyOf":[{"type":"string","maxLength":64,"minLength":2},{"type":"null"}],"title":"Locale"},"units":{"anyOf":[{"type":"string","enum":["metric","imperial"]},{"type":"null"}],"title":"Units"},"delivery":{"anyOf":[{"type":"string","enum":["text","voice"]},{"type":"null"}],"title":"Delivery"}},"type":"object","title":"Body_stream_goal_turn_goals_turn_stream_post"},"Body_whatsapp_webhook_whatsapp_webhooks_post":{"properties":{"payload":{"type":"string","title":"Payload"},"audio":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"Audio"},"image":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"Image"},"document":{"anyOf":[{"type":"string","contentMediaType":"application/octet-stream"},{"type":"null"}],"title":"Document"}},"type":"object","required":["payload"],"title":"Body_whatsapp_webhook_whatsapp_webhooks_post"},"BrainLanePayload":{"properties":{"model":{"type":"string","title":"Model"},"provider":{"type":"string","title":"Provider"},"speed":{"type":"string","title":"Speed"}},"type":"object","required":["model","provider","speed"],"title":"BrainLanePayload","description":"Which model a service answers goal turns on: the tape's lane (#2317).\n\nRead off the model the boundary actually built\n(:meth:`~cheshire.model_clients.ModelLane.of`), never derived a second time\nfrom settings, so what a recording claims and what answered it cannot drift."},"BrowserRouteAnalyticsRequest":{"properties":{"event_type":{"type":"string","maxLength":80,"minLength":1,"pattern":"^[a-z0-9_.-]+$","title":"Event Type"},"outcome":{"anyOf":[{"type":"string","enum":["viewed","selected","rejected","continued","published","shared","downloaded","abandoned","error"]},{"type":"null"}],"title":"Outcome"},"option_id":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}],"title":"Option Id"},"route_id":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}],"title":"Route Id"},"reason_code":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}],"title":"Reason Code"},"metadata":{"additionalProperties":true,"type":"object","title":"Metadata"}},"additionalProperties":false,"type":"object","required":["event_type"],"title":"BrowserRouteAnalyticsRequest","description":"Privacy-conscious browser funnel or route outcome event."},"BrowserRouteAnalyticsResponse":{"properties":{"accepted":{"type":"boolean","title":"Accepted"},"run":{"$ref":"#/components/schemas/BrowserRouteRunSummary"}},"type":"object","required":["accepted","run"],"title":"BrowserRouteAnalyticsResponse","description":"Acknowledgement for a retained browser analytics event."},"BrowserRouteContinueRequest":{"properties":{"prompt":{"type":"string","maxLength":2000,"minLength":1,"title":"Prompt"},"browser_location":{"anyOf":[{"$ref":"#/components/schemas/PublicRouteBrowserLocation"},{"type":"null"}]},"locale":{"type":"string","maxLength":35,"minLength":2,"title":"Locale","default":"en-US"},"units":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Units"},"timezone":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Timezone"},"provenance":{"anyOf":[{"$ref":"#/components/schemas/PublicRouteProvenance"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["prompt"],"title":"BrowserRouteContinueRequest","description":"Continue or iterate a browser-owned public route run."},"BrowserRouteCreateRequest":{"properties":{"prompt":{"type":"string","maxLength":2000,"minLength":1,"title":"Prompt"},"browser_location":{"anyOf":[{"$ref":"#/components/schemas/PublicRouteBrowserLocation"},{"type":"null"}]},"locale":{"type":"string","maxLength":35,"minLength":2,"title":"Locale","default":"en-US"},"units":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Units"},"timezone":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Timezone"},"provenance":{"anyOf":[{"$ref":"#/components/schemas/PublicRouteProvenance"},{"type":"null"}]}},"additionalProperties":false,"type":"object","required":["prompt"],"title":"BrowserRouteCreateRequest","description":"Start a public route run from an anonymous browser session."},"BrowserRouteEvent":{"properties":{"seq":{"type":"integer","minimum":0,"title":"Seq"},"type":{"type":"string","title":"Type"},"ts":{"type":"string","format":"date-time","title":"Ts"},"payload":{"additionalProperties":true,"type":"object","title":"Payload"}},"type":"object","required":["seq","type","ts","payload"],"title":"BrowserRouteEvent","description":"Browser-safe event persisted from a Cheshire public route stream.\n\n``seq`` is Cheshire's 0-based buffer index (#1138) — also the SSE\n``Last-Event-ID`` a reconnect resumes from."},"BrowserRouteLocationSummary":{"properties":{"lat_approx":{"anyOf":[{"type":"number","maximum":90.0,"minimum":-90.0},{"type":"null"}],"title":"Lat Approx"},"lng_approx":{"anyOf":[{"type":"number","maximum":180.0,"minimum":-180.0},{"type":"null"}],"title":"Lng Approx"},"accuracy_m":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Accuracy M"},"captured_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Captured At"}},"type":"object","title":"BrowserRouteLocationSummary","description":"Approximate browser-shared location retained for history/analytics."},"BrowserRoutePublishRequest":{"properties":{"share_id":{"anyOf":[{"type":"string","pattern":"[A-Za-z0-9][A-Za-z0-9_-]{15,63}"},{"type":"null"}],"title":"Share Id"},"option_id":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}],"title":"Option Id"},"label":{"anyOf":[{"type":"string","maxLength":80},{"type":"null"}],"title":"Label"},"route_id":{"anyOf":[{"type":"string","maxLength":160},{"type":"null"}],"title":"Route Id"}},"additionalProperties":false,"type":"object","title":"BrowserRoutePublishRequest","description":"Request a shareable route page for one browser-visible option."},"BrowserRoutePublishResponse":{"properties":{"status":{"type":"string","const":"published","title":"Status"},"run":{"$ref":"#/components/schemas/BrowserRouteRunSummary"},"route_page":{"$ref":"#/components/schemas/BrowserRoutePublishedPage"}},"type":"object","required":["status","run","route_page"],"title":"BrowserRoutePublishResponse","description":"Published route-page outcome for the current browser session."},"BrowserRoutePublishedPage":{"properties":{"share_id":{"type":"string","pattern":"[A-Za-z0-9][A-Za-z0-9_-]{15,63}","title":"Share Id"},"slug":{"type":"string","pattern":"[a-z0-9][a-z0-9-]{1,63}","title":"Slug"},"page_url":{"type":"string","title":"Page Url"},"page_path":{"type":"string","title":"Page Path"},"manifest_url":{"type":"string","title":"Manifest Url"},"manifest_path":{"type":"string","title":"Manifest Path"},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"option_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Option Id"},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label"},"route_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Route Id"},"published_at":{"type":"string","format":"date-time","title":"Published At"}},"type":"object","required":["share_id","slug","page_url","page_path","manifest_url","manifest_path","published_at"],"title":"BrowserRoutePublishedPage","description":"Browser-safe public route-page data for a route option."},"BrowserRouteReportRequest":{"properties":{"reason":{"type":"string","enum":["not_what_i_asked","wrong_distance","wrong_place","unsafe_or_impassable","description_wrong","other"],"title":"Reason","default":"other"},"label":{"anyOf":[{"type":"string","maxLength":80,"minLength":1},{"type":"null"}],"title":"Label"},"route_path":{"anyOf":[{"type":"string","maxLength":240,"minLength":1},{"type":"null"}],"title":"Route Path"},"note":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"title":"Note"},"locale":{"anyOf":[{"type":"string","maxLength":35,"minLength":2},{"type":"null"}],"title":"Locale"},"theme":{"anyOf":[{"type":"string","enum":["light","dark"]},{"type":"null"}],"title":"Theme"}},"additionalProperties":false,"type":"object","title":"BrowserRouteReportRequest","description":"Report one served route (or a whole slate) as wrong (#1598).\n\nThe candidate is addressed by what the browser can actually see: ``label``\n(\"A\"/\"B\"/\"C\") or ``route_path`` (``/r/{share_id}/{slug}``). The typed\ncandidate beat carries no option/route id, so those are the only stable\nhandles. Both omitted = the whole slate is the complaint."},"BrowserRouteReportResponse":{"properties":{"status":{"type":"string","const":"recorded","title":"Status"},"report_id":{"type":"string","title":"Report Id"},"run_id":{"type":"string","title":"Run Id"},"reason":{"type":"string","enum":["not_what_i_asked","wrong_distance","wrong_place","unsafe_or_impassable","description_wrong","other"],"title":"Reason"},"candidate_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Candidate Label"},"first_report":{"type":"boolean","title":"First Report"}},"type":"object","required":["status","report_id","run_id","reason","first_report"],"title":"BrowserRouteReportResponse","description":"Receipt for a recorded route report.\n\nStructural only — no prose. Localized copy (\"thanks, we'll take a look\")\nbelongs in the client's own per-locale catalog, exactly like the typed route\nbeats (#1329): the backend never ships user-facing strings for this surface."},"BrowserRouteRunListResponse":{"properties":{"runs":{"items":{"$ref":"#/components/schemas/BrowserRouteRunSummary"},"type":"array","title":"Runs"}},"type":"object","required":["runs"],"title":"BrowserRouteRunListResponse","description":"Current-browser public route history."},"BrowserRouteRunResponse":{"properties":{"run_id":{"type":"string","title":"Run Id"},"browser_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Browser Session Id"},"status":{"type":"string","title":"Status"},"terminal_event_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Terminal Event Type"},"parent_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Run Id"},"root_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Root Run Id"},"prompt_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Preview"},"viewer_locale":{"type":"string","title":"Viewer Locale"},"generation_locale":{"type":"string","title":"Generation Locale"},"units":{"type":"string","title":"Units"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"location":{"$ref":"#/components/schemas/BrowserRouteLocationSummary"},"candidate_summaries":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Candidate Summaries"},"source_route_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Hash"},"source_route_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Slug"},"source_public_route_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Public Route Id"},"source_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Session Id"},"published_route_pages":{"items":{"$ref":"#/components/schemas/BrowserRoutePublishedPage"},"type":"array","title":"Published Route Pages"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"is_owner":{"type":"boolean","title":"Is Owner","default":true},"can_refine":{"type":"boolean","title":"Can Refine","default":true},"events":{"items":{"$ref":"#/components/schemas/BrowserRouteEvent"},"type":"array","title":"Events"}},"type":"object","required":["run_id","browser_session_id","status","root_run_id","prompt_preview","viewer_locale","generation_locale","units","location","created_at","updated_at"],"title":"BrowserRouteRunResponse","description":"Browser-safe route run state including collected stream events."},"BrowserRouteRunSummary":{"properties":{"run_id":{"type":"string","title":"Run Id"},"browser_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Browser Session Id"},"status":{"type":"string","title":"Status"},"terminal_event_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Terminal Event Type"},"parent_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Parent Run Id"},"root_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Root Run Id"},"prompt_preview":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt Preview"},"viewer_locale":{"type":"string","title":"Viewer Locale"},"generation_locale":{"type":"string","title":"Generation Locale"},"units":{"type":"string","title":"Units"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"location":{"$ref":"#/components/schemas/BrowserRouteLocationSummary"},"candidate_summaries":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Candidate Summaries"},"source_route_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Hash"},"source_route_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Slug"},"source_public_route_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Public Route Id"},"source_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Session Id"},"published_route_pages":{"items":{"$ref":"#/components/schemas/BrowserRoutePublishedPage"},"type":"array","title":"Published Route Pages"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"},"completed_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Completed At"},"is_owner":{"type":"boolean","title":"Is Owner","default":true},"can_refine":{"type":"boolean","title":"Can Refine","default":true}},"type":"object","required":["run_id","browser_session_id","status","root_run_id","prompt_preview","viewer_locale","generation_locale","units","location","created_at","updated_at"],"title":"BrowserRouteRunSummary","description":"Browser-safe route run metadata for history and resume.\n\n``is_owner`` / ``can_refine`` default ``True`` because every serializer\nexcept the shared read (#1549) is owner-scoped; the non-owner projection\nclears them and nulls the owner-only fields below (session linkage,\nprovenance, composer prompt, published pages), which is why those three\nare ``str | None`` even though the owner path always populates them."},"BrowserRouteSessionListResponse":{"properties":{"sessions":{"items":{"$ref":"#/components/schemas/BrowserRouteSessionSummary"},"type":"array","title":"Sessions"}},"type":"object","required":["sessions"],"title":"BrowserRouteSessionListResponse","description":"List response for sessions visible to the current browser."},"BrowserRouteSessionResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_seen_at":{"type":"string","format":"date-time","title":"Last Seen At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"source_route_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Hash"},"source_route_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Slug"},"source_public_route_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Public Route Id"},"source_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Session Id"},"csrf_token":{"type":"string","minLength":1,"title":"Csrf Token"}},"type":"object","required":["session_id","created_at","last_seen_at","expires_at","csrf_token"],"title":"BrowserRouteSessionResponse","description":"Bootstrap response containing the in-memory CSRF token."},"BrowserRouteSessionSummary":{"properties":{"session_id":{"type":"string","title":"Session Id"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"last_seen_at":{"type":"string","format":"date-time","title":"Last Seen At"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"source_route_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Hash"},"source_route_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Slug"},"source_public_route_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Public Route Id"},"source_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Session Id"}},"type":"object","required":["session_id","created_at","last_seen_at","expires_at"],"title":"BrowserRouteSessionSummary","description":"Browser-safe metadata for the current public Routes session."},"ChallengeResponse":{"properties":{"nonce":{"type":"string","title":"Nonce"}},"type":"object","required":["nonce"],"title":"ChallengeResponse","description":"Register nonce minted by ``POST /attest/challenge``."},"Channel":{"type":"string","enum":["whatsapp","heylo"],"title":"Channel","description":"Messaging channel discriminator on the generic channel-associations core.\n\nOne value per integrated bridge. Adding a channel = new enum value + new\n:class:`cheshire.channels.bridge.BridgeClient` impl + new thin\nwebhook route. No new backend tables — every channel reuses\n``channel_associations`` / ``channel_association_requests``."},"ChannelAssociationConfirmRequest":{"properties":{"token":{"type":"string","minLength":1,"title":"Token"}},"type":"object","required":["token"],"title":"ChannelAssociationConfirmRequest","description":"Body of the mobile-side confirm call after tapping the deep link.\n\nChannel-blind by #858: the token alone identifies which channel and\nwhich channel-user the request was minted for, so the body shape stays\na single field across all channels."},"ChannelMessageReferenceCaptureRequest":{"properties":{"user_id":{"type":"string","minLength":1,"title":"User Id"},"channel":{"$ref":"#/components/schemas/Channel"},"platform_message_id":{"type":"string","minLength":1,"title":"Platform Message Id"},"board_message_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Board Message Id"},"route_session_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Route Session Id"},"share_id":{"anyOf":[{"type":"string","pattern":"[A-Za-z0-9][A-Za-z0-9_-]{15,63}"},{"type":"null"}],"title":"Share Id"},"source_route_page_id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Source Route Page Id"}},"type":"object","required":["user_id","channel","platform_message_id"],"title":"ChannelMessageReferenceCaptureRequest","description":"Bind one outbound platform message id to route identity."},"ChannelMessageReferenceCaptureResponse":{"properties":{"id":{"type":"string","title":"Id"},"user_id":{"type":"string","title":"User Id"},"channel":{"$ref":"#/components/schemas/Channel"},"platform_message_id":{"type":"string","title":"Platform Message Id"},"board_message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Board Message Id"},"route_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Route Session Id"},"share_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Id"},"source_route_page_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Page Id"}},"type":"object","required":["id","user_id","channel","platform_message_id"],"title":"ChannelMessageReferenceCaptureResponse","description":"Stored route reference binding."},"DiscoverRecommendationRequest":{"properties":{"name":{"type":"string","title":"Name"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"approximate_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Approximate Date"},"sport_type":{"anyOf":[{"$ref":"#/components/schemas/EventType"},{"type":"null"}]},"discipline_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Discipline Name"},"distance_km":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Distance Km"}},"type":"object","required":["name"],"title":"DiscoverRecommendationRequest","description":"Payload to run web discovery for a missing event named by the user."},"EventSearchRequest":{"properties":{"query":{"type":"string","title":"Query"},"force_refresh":{"type":"boolean","title":"Force Refresh","default":false}},"type":"object","required":["query"],"title":"EventSearchRequest"},"EventType":{"type":"string","enum":["trail_running","road_running","cycling_road","cycling_mtb","triathlon","swimming","generic"],"title":"EventType","description":"Supported event types."},"EventURLSearchRequest":{"properties":{"url":{"type":"string","title":"Url"},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"}},"type":"object","required":["url"],"title":"EventURLSearchRequest"},"FCMTokenRequest":{"properties":{"token":{"type":"string","title":"Token"},"device_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Device Id"},"platform":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Platform"}},"type":"object","required":["token"],"title":"FCMTokenRequest","description":"Request schema for adding an FCM token."},"Gender":{"type":"string","enum":["male","female","other"],"title":"Gender"},"GoalAnchorPayload":{"properties":{"kind":{"type":"string","enum":["event","route"],"title":"Kind"},"id":{"type":"string","title":"Id"},"name":{"type":"string","title":"Name"},"date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Date"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"website_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Website Url"},"short_link":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Short Link"}},"type":"object","required":["kind","id","name"],"title":"GoalAnchorPayload","description":"The event a goal is pinned to, resolved into what a client draws.\n\nAdditive and optional: a goal that anchors nowhere — most of them — carries\n``null`` here and the page looks exactly as it did before this existed.\n\nThis is the whole reason the coach never writes a URL. The reply is plain\nchat text that ships to any channel as-is, so a link in it would be a link\nthe model chose and nothing validated; here the host resolves the id the\nconversation anchored to and hands over the official name, date, place and\nsite. The surface renders the card, and the coach's words stay words.\n\n``website_url`` points at the organizer's own site rather than an\nimperfect.co event page: the athlete is being handed the place where they\nactually register. It resolves server-side, so retargeting it later is one\nline here and no change to any client."},"GoalBrowserSessionResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"},"csrf_token":{"type":"string","minLength":1,"title":"Csrf Token"},"brain_lane":{"anyOf":[{"$ref":"#/components/schemas/BrainLanePayload"},{"type":"null"}]}},"type":"object","required":["session_id","expires_at","csrf_token"],"title":"GoalBrowserSessionResponse","description":"What ``GET /goals/session`` hands the page back.\n\nDeliberately thinner than the Routes equivalent: the goals page has no\nsession list to render and no route ancestry to carry, so it gets the two\nthings it actually acts on — the CSRF token every turn must echo, and when\nthe session stops being valid so it knows to bootstrap again. The session\nid rides along because it is what an operator matches a stored conversation\non; it authorizes nothing on its own (the cookie does)."},"GoalCheckpoint":{"properties":{"claim":{"type":"string","minLength":1,"title":"Claim","description":"The unverified claim this checkpoint protects."},"measure":{"type":"string","minLength":1,"title":"Measure","description":"What must be observably true."},"by":{"$ref":"#/components/schemas/GoalDate"},"on_miss":{"type":"string","minLength":1,"title":"On Miss","description":"What the goal reverts to, stated openly."}},"additionalProperties":false,"type":"object","required":["claim","measure","by","on_miss"],"title":"GoalCheckpoint","description":"A checkpoint for something the coach cannot verify today.\n\nThe checkpoint substitutes for verification (the anonymous page has no\ndata to check), so it must be athlete-visible and carry its consequence:\nwhat must be true, by when, and what the goal reverts to when it isn't.\nExecuting a fallback never satisfies a checkpoint — the body only adapts\nto work that happened."},"GoalComingFrom":{"properties":{"event":{"type":"string","minLength":1,"title":"Event","description":"The race or attempt, named as the document names it — or, when the document tells of none, the athlete's starting point in the same handful of words: the longest they have gone in one go, what their weeks already hold. The thing itself, never a sentence introducing it."},"felt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Felt","description":"How it felt — the athlete's words, verbatim."},"lesson":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lesson","description":"What it taught — their words, verbatim."}},"additionalProperties":false,"type":"object","required":["event"],"title":"GoalComingFrom","description":"What this goal is built on: the race behind them, or where they start.\n\nAn athlete with a race behind them is coming from that race and what it\ntaught. A first-timer is coming from a starting point instead — the longest\nthey have run in one go, what their weeks already hold — and their document\nstates it just as plainly (#2013). A card only a past race could fill left\ntheirs blank, on the one section of the page that shows the athlete it meets\nthem where their journey starts.\n\n**The quotes are optional, and that is what makes both shapes fit.** ``felt``\nand ``lesson`` are the athlete's words, verbatim-checked when present —\nnobody quotes how their current mileage felt, so a starting point stands on\n``event`` alone, held to the document by the grounding judge like every\nother sentence the model writes. Bench round 1 (#1984) had already shown\nthat requiring both quotes drops a real card whose body quoted the athlete\nonce; requiring one drops every first-timer's."},"GoalConversationResponse":{"properties":{"status":{"anyOf":[{"type":"string","enum":["exploring","proposing","settled"]},{"type":"null"}],"title":"Status"},"document":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Document"},"replies":{"items":{"type":"string"},"type":"array","title":"Replies"},"goal_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Goal Id"},"anchor":{"anyOf":[{"$ref":"#/components/schemas/GoalAnchorPayload"},{"type":"null"}]},"picture":{"anyOf":[{"$ref":"#/components/schemas/GoalPicture"},{"type":"null"}]}},"type":"object","title":"GoalConversationResponse","description":"What a reload needs to redraw the conversation without burning a turn.\n\nEvery field is empty on a browser that has not spoken yet — a page opening\nfor the first time is not an error, so this answers 200 with nothing rather\nthan 404 with a reason nobody needs.\n\n``goal_id`` appears once the conversation settles, and it is the only way\nthe page can name the artifact it produced; a goal nothing can address is a\ngoal nothing can publish.\n\n``picture`` is the same object the accepted turn streamed, for the latest\naccepted document — the poll is the only news a page gets while a voice\ncall runs its turns server-side, so the picture has to be readable here and\nnot only on the stream."},"GoalDate":{"properties":{"date":{"type":"string","format":"date","title":"Date"},"precision":{"type":"string","enum":["day","month","quarter","season"],"title":"Precision","default":"day"}},"additionalProperties":false,"type":"object","required":["date"],"title":"GoalDate","description":"A dated commitment at its honest resolution.\n\n``precision`` is the band: the date is normalized to the first day of its\nperiod (month → the 1st, quarter → the quarter's first month, season →\nthe quarter carrying the season's start), so a coarse band can never\nlicense a specific day the source hasn't published — the same\nnormalization the plan rim applies to its events."},"GoalDetailResponse":{"properties":{"goal_id":{"type":"string","title":"Goal Id"},"name":{"type":"string","title":"Name"},"kind":{"type":"string","enum":["destination","practice"],"title":"Kind"},"status":{"type":"string","enum":["exploring","proposing","settled"],"title":"Status"},"sport":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sport"},"set_at":{"type":"string","format":"date-time","title":"Set At"},"target":{"$ref":"#/components/schemas/GoalDate"},"anchor":{"anyOf":[{"$ref":"#/components/schemas/GoalAnchorPayload"},{"type":"null"}]},"factors":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Factors"},"page":{"anyOf":[{"$ref":"#/components/schemas/GoalPage"},{"type":"null"}]},"checkpoints":{"items":{"$ref":"#/components/schemas/GoalCheckpoint"},"type":"array","title":"Checkpoints"},"fallbacks":{"items":{"$ref":"#/components/schemas/GoalFallback"},"type":"array","title":"Fallbacks"},"share_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Share Id"},"slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Slug"},"published_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Published At"}},"type":"object","required":["goal_id","name","kind","status","set_at","target"],"title":"GoalDetailResponse","description":"The owner's goal detail page, in one read (#1984).\n\n**The opposite contract to** :class:`GoalSharePage`. That one is the promise\nat a distance, stripped to what a stranger may see; this one is the athlete\nlooking at their own goal, so the why, the checkpoints and the fallbacks —\nthe three things the share page most deliberately withholds — are the point\nof it. Two models rather than one with a flag, because \"what a stranger sees\"\nand \"what the owner sees\" are different contracts that must not be one edit\naway from each other.\n\n**Rim passthrough is the rim's own types**, not a parallel spelling of them.\n``target``, ``factors``, ``checkpoints`` and ``fallbacks`` are the models the\ndocument validates against (:mod:`cheshire.goals.models`), serialized as they\nstand: a mirrored DTO here would be a second contract to keep in step, and\nthe field names looking-glass renders would drift from the ones the coach\nwrites. ``page`` is the same deal for the projected prose\n(:class:`cheshire.goals.agents.goal_page.GoalPage`).\n\n``page`` is ``null`` for a goal whose projection refused or has not run —\nevery other section still draws (:mod:`cheshire.goals.agents.goal_page` says why). A goal\nsettled before #1983 carries no ``factors`` either, for the same reason: the\nrim did not have them, and nothing invents them after the fact.\n\n``share_id``/``slug``/``published_at`` are ``StoredGoal.live_share`` read-only\n— all three together or none, the same guarantee that property already\nholds, not a fourth spelling of it. This is how the owner's page learns a\ngoal is already live without ever calling publish just to find out:\npublishing is the athlete's deliberate act (wonderland goals.md, \"The share\npage\"), never a side effect of reading a page."},"GoalFactor":{"properties":{"id":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Id"},"name":{"type":"string","minLength":1,"title":"Name","description":"The axis, in the athlete's own words."},"scale":{"type":"string","minLength":1,"title":"Scale","description":"The units the positions live on — the ones the athlete uses."},"standing":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Standing","description":"Where the athlete stands on this scale today; omit until they have said."},"asks":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Asks","description":"What the goal asks, on the same scale; omit until the goal has been sized."},"limit":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Limit","description":"The honest ceiling on this axis, only when one exists."},"better":{"type":"string","enum":["more","less"],"title":"Better","description":"Which way is better on this scale; omit where more is better.","default":"more"},"leans":{"anyOf":[{"type":"string","enum":["fully","hard","some","barely"]},{"type":"null"}],"title":"Leans","description":"How hard this goal leans on this axis, across every goal you size: fully where the goal is made of it, hard, some, or barely where it only touches it."},"already_there":{"type":"boolean","title":"Already There","description":"Where the athlete stands already covers what the goal asks.","default":false},"read":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Read","description":"One plain line: where the athlete stands against what the goal asks."}},"additionalProperties":false,"type":"object","required":["name","scale"],"title":"GoalFactor","description":"One axis of the goal's picture: what the goal asks vs what the athlete brings.\n\nThe picture is the settled goal's at-a-glance snapshot (wonderland\ngoals.md, \"The goal's picture\"): five to seven factors chosen per goal,\nsettled *with* the athlete as part of settling the goal, each named in the\nathlete's own words. Positions live on the factor's own ``scale`` — the\nunits the athlete talks in — never a normalized score, which is why no two\ngoals draw the same shape and why the fields are plain numbers rather than\na bounded range.\n\n``asks`` derives from the goal itself (what a 50K honestly demands);\n``standing`` reads from the foundation the goal is built on — what the\nathlete said or the data showed, never a value invented to fill an axis.\nThat provenance split is judged over the transcript (the eval slate and the\nfactors bench), not here: this type owns only the shape.\n\n**An axis and a position land separately**, which is why both positions are\noptional. The two arrive at different moments — where the athlete stands\ncomes off their own telling, what the goal asks off the sizing the coach\nworks out with them — and a shape that demanded both at once left the model\none honest move, to keep the whole factor off the picture until the last\nof them arrived. So the picture stayed empty through most of a conversation\nand then arrived at once, and the first factor written early carried a zero\nfor an ask nobody had worked out. An axis may stand on the picture named and\nunpositioned; a position may not appear before it is known. The gate is what\nholds the other end: a settled goal's picture is whole\n(:func:`~cheshire.goals.generation.turn_findings`), so \"not yet\" can never\nbecome \"never\".\n\n``limit`` exists only when an honest ceiling does (the hours a week\nactually holds, the volume an old injury caps). ``already_there`` marks a\nfactor the athlete already covers: it stays on the picture with its\nheadroom visible instead of disappearing as \"done\", so bringing more than\nthe goal demands still shows while another factor trails.\n\n``better`` is which way the axis runs, and it is the coach's to state\nbecause nothing else in the document can (#2221). The scale is the\nathlete's own words, and those words do not carry the answer: the same\n``minutes`` is the morning a life offers on one axis and a finish time on\nthe next, so a reader of the units alone — a table or a model — has to\nguess. The coach is the one reader that heard which. Left unsaid the axis\nruns the ordinary way, which is what every factor settled before this\nfield existed keeps meaning, and :attr:`already_there` is the check on it:\nthe two are one judgment written twice, and the turn gate refuses a\nsettled picture whose caption and geometry disagree.\n\nThat is not the slot #2124 refused. The axis's *frame* stays host\narithmetic, because a ceiling nobody can honestly know is exactly the\ninvented value the product bans; a direction is not a measurement, it is\nsomething the conversation already established — the coach states the\nsame judgment in :attr:`already_there` and in :attr:`read` — and the\nconsistency check is what makes it checkable rather than merely trusted.\n\n``leans`` is how hard the goal leans on this axis, judged across every\ngoal a coach sizes (#2261): a full-distance triathlon is made of its\naerobic engine, a first 10K barely touches it. wonderland's goals.md makes\nthat the shape of the goal's own figure, so the host folds the word into\nthe served ``span`` and the ask reaches further out on an axis the goal is\nmade of than on one it barely touches. It is the same kind of judgment\nthe ask already is — about the goal, never about where the athlete stands\n— which is why it is not the slot #2124 refused either, and why the frame\nstill holds still while the athlete trains. Unstated is honest for a\ndocument written before the field existed and draws as ``fully``, which is\nbyte-identical to what those documents always drew; a settled document\nwritten after it carries the word on every axis (the turn gate holds that\nend). A word rather than a number, because a rung is precision a coach has\nand a decimal is not, and a whole-document rewrite that nudged a number\nwould redraw a settled goal. No arithmetic partner checks it the way the\npositions check :attr:`better`; the post-hoc reader in the goals bench is\nwhat stands in for one."},"GoalFallback":{"properties":{"when":{"type":"string","minLength":1,"title":"When","description":"The athlete's named obstacle."},"then":{"type":"string","minLength":1,"title":"Then","description":"The athlete's own replacement."},"athlete_quote":{"type":"string","minLength":1,"title":"Athlete Quote","description":"Their words, verbatim."}},"additionalProperties":false,"type":"object","required":["when","then","athlete_quote"],"title":"GoalFallback","description":"An if-then rule in the athlete's own words (never the coach's).\n\nThe obstacle and the replacement both come from the athlete — that\nauthorship is what makes the rule bind — and ``athlete_quote`` preserves\nthe words they used so the document can always show the rule's origin."},"GoalPage":{"properties":{"why":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Why","description":"Why this goal matters, in the athlete's own words — the quote from the document that carries their reason, not the loudest one in it."},"journey":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Journey","description":"One plain line on where the athlete is coming from and what this goal is the next step of: the races behind them, the step up ahead, where they are today."},"sport":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sport","description":"The athlete's own word for the sport this goal is in, whenever the document makes it plain; nothing only when it genuinely does not."},"week":{"items":{"$ref":"#/components/schemas/GoalWeekCommitment"},"type":"array","title":"Week","description":"Everything the week keeps, one entry each — the parts that are not the goal's own sport included."},"life":{"items":{"type":"string"},"type":"array","title":"Life","description":"One short line each for the life the work has to fit: the days and hours actually available, the travel, the sleep, the dates already fixed, whatever the athlete called non-negotiable. Their constraints, never advice."},"history":{"anyOf":[{"$ref":"#/components/schemas/GoalComingFrom"},{"type":"null"}],"description":"The one thing this goal is built on: the past race or attempt the document tells the story of, or — when it tells of none — the starting point it says the athlete sets out from. Nothing when the document says neither."},"honest_subtitle":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Honest Subtitle","description":"One line on what keeps this goal honest: where the coach's read differs from the athlete's target, or what the goal is being held to."}},"additionalProperties":false,"type":"object","title":"GoalPage","description":"The prose sections of one settled goal's page.\n\nEvery field is optional in the sense that matters: a section the document\ndoes not carry is absent rather than filled, because a slot is minting\npressure (#1850) and this projection has no athlete left in the loop to\ncorrect an invention. The page reads fine short.\n\n**The field descriptions are the spec the model works from.** They are what\nthe projection prompt deliberately does not restate — one place says what a\nsection is, and it is the type the answer has to satisfy."},"GoalPicture":{"properties":{"version_id":{"type":"string","title":"Version Id"},"status":{"type":"string","enum":["exploring","proposing","settled"],"title":"Status"},"factors":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Factors"}},"type":"object","required":["version_id","status"],"title":"GoalPicture","description":"The goal's picture as one payload, for the two carriers that send it.\n\nThe picture draws itself while the conversation runs, so the page needs the\nfactors as *data* rather than as document text nobody outside cheshire\nshould be parsing. One payload serves both carriers — the accepted turn's\nstream event and the conversation read's ``picture`` — and both derive it\nfrom the same accepted document, which is what makes them unable to\ndisagree.\n\n**Full state, never a delta.** Every payload carries the complete factor\nlist of its document version, in rim order, so a page that reloads and\nstarts from the conversation read draws the identical picture the stream\ndrew. Motion is the consumer's diff between consecutive payloads, keyed on\na factor's ``name``; ``version_id`` is the gate, so the same version seen\ntwice moves nothing.\n\n``factors`` is the rim's own type exactly, the passthrough posture\n:class:`GoalDetailResponse` takes and for the same reason: a mirrored DTO\nhere would be a second contract to keep in step with the document the coach\nwrites. Empty is normal — the picture forms as the conversation does.\nSerialized, each factor also carries its served ``span``\n(:func:`_served_factor`), so every carrier hands a consumer the axis scale\nwith the positions that live on it."},"GoalSharePage":{"properties":{"share_id":{"type":"string","title":"Share Id"},"slug":{"type":"string","title":"Slug"},"name":{"type":"string","title":"Name"},"kind":{"type":"string","enum":["destination","practice"],"title":"Kind"},"target_date":{"type":"string","format":"date","title":"Target Date"},"target_precision":{"type":"string","enum":["day","month","quarter","season"],"title":"Target Precision"},"sport":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sport"},"locale":{"type":"string","title":"Locale"},"factors":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Factors"},"revisions":{"items":{"$ref":"#/components/schemas/GoalShareRevision"},"type":"array","title":"Revisions"}},"additionalProperties":false,"type":"object","required":["share_id","slug","name","kind","target_date","target_precision","locale"],"title":"GoalSharePage","description":"The published goal, as a stranger sees it.\n\n**The promise, at a distance — and its picture.** The goal's name, when it\nis, how precisely that date is known, what kind of goal it is, the sport,\nthe dated history of it changing, and the goal's picture: wonderland\ngoals.md makes the picture the page's highlight and what the link preview\ncarries, both sides published, what the goal asks and where the athlete\nstands, gap and all. ``factors`` is the owner-side shape exactly\n(:class:`~cheshire.goals.models.GoalFactor`): positions on the goal's own\nscale, never the readings behind them — a body-read factor publishes as a\nposition, and the sleep or recovery data underneath it never leaves.\n\nWhat is absent is still the point. The document body, the checkpoints and\nfallbacks, the athlete's reasons — the why and what chose the event stay\ntheirs to tell (wonderland #45) — and every session id, none of it is here.\nSharing a goal must feel safe; it must never feel like exposing something\ninternal. Three layers keep it that way: this model is `extra=\"forbid\"` so\nan added field cannot ride along silently, the validator below walks the\nserialized payload for forbidden keys and internal markers, and the row is\nprojected field by named field rather than dumped.\n\n**The field list is settled** by wonderland goals.md's share-page section\n(the human review the earlier note here waited on). Trimming a field is\ncheap; un-leaking one is not."},"GoalShareRevision":{"properties":{"published_at":{"type":"string","format":"date-time","title":"Published At"},"name":{"type":"string","title":"Name"},"target_date":{"type":"string","format":"date","title":"Target Date"},"target_precision":{"type":"string","enum":["day","month","quarter","season"],"title":"Target Precision"}},"additionalProperties":false,"type":"object","required":["published_at","name","target_date","target_precision"],"title":"GoalShareRevision","description":"One dated publication of this goal — the promise, at that date.\n\nwonderland asks that revisions are dated *visibly*: an athlete who moved\ntheir race should not look like they always meant December. What a revision\nshows is what the promise was, never the document it came from."},"GoalVoiceCallRequest":{"properties":{"sdp":{"type":"string","minLength":1,"title":"Sdp"},"locale":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Locale"},"units":{"anyOf":[{"type":"string","enum":["metric","imperial"]},{"type":"null"}],"title":"Units"}},"type":"object","required":["sdp"],"title":"GoalVoiceCallRequest","description":"One browser's offer, plus the surface settings a goal turn takes."},"GoalVoiceCallResponse":{"properties":{"session_id":{"type":"string","title":"Session Id"},"sdp":{"type":"string","title":"Sdp"}},"type":"object","required":["session_id","sdp"],"title":"GoalVoiceCallResponse","description":"The SDP answer and the id of the call it belongs to. Nothing else."},"GoalVoiceProbeResponse":{"properties":{"available":{"type":"boolean","title":"Available","default":true}},"type":"object","title":"GoalVoiceProbeResponse","description":"What a caller allowed through the voice gate learns: that it is there.\n\nDeliberately says nothing else. The model slug belongs to a confidential\nalpha, and a probe is a show-or-hide-the-button signal, not a place to\ndescribe the machinery behind it."},"GoalWeekCommitment":{"properties":{"name":{"type":"string","minLength":1,"title":"Name","description":"The commitment, in two or three words."},"detail":{"type":"string","minLength":1,"title":"Detail","description":"What it is, as the document states it."}},"additionalProperties":false,"type":"object","required":["name","detail"],"title":"GoalWeekCommitment","description":"One thing the athlete's week keeps.\n\nNamed and described separately because the page draws them that way: the\nname is the commitment itself, short enough to read at a glance, and the\ndetail is what the document says it costs — how often, how long, on which\ndays. Both come off the document's own account of the week."},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthProbeResponse":{"properties":{"decision":{"$ref":"#/components/schemas/ProbeDecision"},"custom_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Token"},"recovered":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Recovered"}},"type":"object","required":["decision"],"title":"HealthProbeResponse","description":"Response for ``POST /auth/health-probe``."},"HealthProviderAppleRegisterRequest":{"properties":{"device_uuid":{"type":"string","minLength":1,"title":"Device Uuid","description":"Opaque device identifier persisted in iCloud-synced Keychain on the mobile side. Used as the Apple Health provider_user_id."}},"type":"object","required":["device_uuid"],"title":"HealthProviderAppleRegisterRequest","description":"Request to register Apple Health."},"HealthProviderCallbackRequest":{"properties":{"code":{"type":"string","title":"Code","description":"Authorization code from OAuth callback"},"state":{"type":"string","title":"State","description":"State parameter from callback"}},"type":"object","required":["code","state"],"title":"HealthProviderCallbackRequest","description":"Request with OAuth callback data."},"HealthProviderCallbackResponse":{"properties":{"provider":{"$ref":"#/components/schemas/HealthProviderType"},"provider_user_id":{"type":"string","title":"Provider User Id"},"permissions":{"items":{"type":"string"},"type":"array","title":"Permissions"},"status":{"$ref":"#/components/schemas/HealthProviderConnectionStatus"},"result":{"type":"string","enum":["connected","already_connected","recovered"],"title":"Result","default":"connected"},"custom_token":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Custom Token"}},"type":"object","required":["provider","provider_user_id","permissions","status"],"title":"HealthProviderCallbackResponse","description":"Response after a provider connect/callback or Apple Health register.\n\n``result`` indicates the branch that ran:\n\n- ``connected``: a new ``HealthProviderConnection`` was created.\n- ``already_connected``: the caller already owns this provider identity.\n- ``recovered``: the provider identity maps to a different imperfect\n  user (typical after a reinstall/new device). ``custom_token`` is set\n  so the mobile client can ``signInWithCustomToken`` and adopt the\n  existing user's Firebase UID."},"HealthProviderConnectRequest":{"properties":{"redirect_uri":{"type":"string","title":"Redirect Uri","description":"Deep link URI where the app will receive the OAuth callback"}},"type":"object","required":["redirect_uri"],"title":"HealthProviderConnectRequest","description":"Request to initiate OAuth connection with a health provider."},"HealthProviderConnectResponse":{"properties":{"authorization_url":{"type":"string","title":"Authorization Url","description":"URL to redirect user for authorization"},"state":{"type":"string","title":"State","description":"State parameter to validate callback"}},"type":"object","required":["authorization_url","state"],"title":"HealthProviderConnectResponse","description":"Response with authorization URL for OAuth flow."},"HealthProviderConnectionResponse":{"properties":{"id":{"type":"string","title":"Id"},"provider":{"$ref":"#/components/schemas/HealthProviderType"},"provider_user_id":{"type":"string","title":"Provider User Id"},"brand":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Brand"},"permissions":{"items":{"type":"string"},"type":"array","title":"Permissions"},"status":{"$ref":"#/components/schemas/HealthProviderConnectionStatus"},"last_sync_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Sync At"},"created_at":{"type":"string","format":"date-time","title":"Created At"},"updated_at":{"type":"string","format":"date-time","title":"Updated At"}},"type":"object","required":["id","provider","provider_user_id","permissions","status","created_at","updated_at"],"title":"HealthProviderConnectionResponse","description":"Response schema for a health provider connection."},"HealthProviderConnectionStatus":{"type":"string","enum":["active","expired","revoked","error"],"title":"HealthProviderConnectionStatus","description":"Status of a health provider connection."},"HealthProviderListResponse":{"properties":{"connections":{"items":{"$ref":"#/components/schemas/HealthProviderConnectionResponse"},"type":"array","title":"Connections"}},"type":"object","required":["connections"],"title":"HealthProviderListResponse","description":"Response with list of user's health provider connections."},"HealthProviderType":{"type":"string","enum":["garmin","whoop","coros","terra","apple_health"],"title":"HealthProviderType","description":"Health data providers (OAuth connections)."},"HealthResponse":{"properties":{"status":{"type":"string","const":"healthy","title":"Status","description":"Health status"}},"type":"object","required":["status"],"title":"HealthResponse","description":"Response for the liveness health check."},"NotificationResponseRequest":{"properties":{"action":{"type":"string","title":"Action","description":"Action identifier: 'reply' for text input, or 'button_1'/'button_2'/'button_3' for button taps."},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"The button label that was tapped (null for text replies)."},"text":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}],"title":"Text","description":"User's text reply (null for button taps)."}},"type":"object","required":["action"],"title":"NotificationResponseRequest","description":"Request schema for responding to an interactive notification."},"NotificationResponseResponse":{"properties":{"id":{"type":"string","title":"Id"},"notification_id":{"type":"string","title":"Notification Id"},"action":{"type":"string","title":"Action"},"acknowledged":{"type":"boolean","title":"Acknowledged","default":true}},"type":"object","required":["id","notification_id","action"],"title":"NotificationResponseResponse","description":"Response schema after storing a notification response."},"OnboardingInsight":{"properties":{"kind":{"type":"string","enum":["hrv","sleep","sleep_worst_night","steps","workouts","resting_heart_rate","active_energy"],"title":"Kind"},"sentiment":{"type":"string","enum":["positive","caution","urgent","neutral"],"title":"Sentiment"},"value_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Value Ms"},"avg_hours":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Avg Hours"},"hours":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Hours"},"date":{"anyOf":[{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},{"type":"null"}],"title":"Date"},"avg":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Avg"},"count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Count"},"bpm":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Bpm"},"avg_kcal":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Avg Kcal"}},"type":"object","required":["kind","sentiment"],"title":"OnboardingInsight","description":"A single onboarding insight card.\n\nMirrors the seven cards the mobile teaser extracts from HealthKit, but\ncomputed server-side from connected provider data (Garmin direct, or\nCoros via Terra). The client maps ``kind`` to the localized label,\nicon, and subtitle copy.\n\nOnly the fields relevant to ``kind`` are populated; the rest are ``None``."},"OnboardingInsightsResponse":{"properties":{"insights":{"items":{"$ref":"#/components/schemas/OnboardingInsight"},"type":"array","title":"Insights"}},"type":"object","required":["insights"],"title":"OnboardingInsightsResponse","description":"Response for GET /onboarding/{provider}/insights."},"ProbeChallengeResponse":{"properties":{"nonce":{"type":"string","title":"Nonce"},"expires_at":{"type":"string","format":"date-time","title":"Expires At"}},"type":"object","required":["nonce","expires_at"],"title":"ProbeChallengeResponse","description":"Probe nonce minted by ``POST /auth/health-probe/challenge``."},"ProbeDecision":{"type":"string","enum":["confident","ambiguous","none","insufficient_signal"],"title":"ProbeDecision","description":"The closed set of Apple Health probe outcomes.\n\nProduced by the pure scorer in :mod:`cheshire.resources.auth` and returned\nverbatim by the endpoint, so the endpoint, its tests, and any future\nre-scoring tool share one vocabulary."},"ProviderType":{"type":"string","enum":["google","apple"],"title":"ProviderType","description":"Identity/auth providers."},"PublicRouteBrowserLocation":{"properties":{"lat":{"type":"number","maximum":90.0,"minimum":-90.0,"title":"Lat"},"lng":{"type":"number","maximum":180.0,"minimum":-180.0,"title":"Lng"},"accuracy_m":{"anyOf":[{"type":"number","minimum":0.0},{"type":"null"}],"title":"Accuracy M"},"captured_at":{"type":"string","format":"date-time","title":"Captured At"},"label":{"anyOf":[{"type":"string","maxLength":240},{"type":"null"}],"title":"Label"}},"type":"object","required":["lat","lng","captured_at"],"title":"PublicRouteBrowserLocation","description":"GPS fix explicitly shared by the browser for one route run.\n\nSerializes 1:1 into Cheshire's ``CurrentLocation`` (same field names)."},"PublicRouteProvenance":{"properties":{"source_route_hash":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Hash"},"source_route_slug":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Route Slug"},"source_public_route_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Public Route Id"},"source_session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Session Id"},"source_run_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Source Run Id"}},"type":"object","title":"PublicRouteProvenance","description":"Read-only ancestry for route iterations and forks.\n\nimperfect-api lineage state only — it is **not** sent to Cheshire (the\n``/routes`` contract is ``extra=\"forbid\"``); it is persisted on the run\nso a continuation can record where it came from."},"RaceTier":{"type":"string","enum":["A","B","C"],"title":"RaceTier","description":"Athlete-assigned priority for a race, anchoring taper severity.\n\nValues are UPPERCASE by product decision (the coach surfaces them as\n``A``/``B``/``C`` to the athlete):\n\n- ``A``: season goal — full taper + recovery.\n- ``B``: secondary — light volume reduction, used to test strategy.\n- ``C``: training tool — no taper; replaces a workout in the plan."},"RouteSendCallbackRequest":{"properties":{"state":{"type":"string","minLength":1,"title":"State"},"code":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Code"},"error":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Error"}},"type":"object","required":["state"],"title":"RouteSendCallbackRequest","description":"Callback handoff from the static looking-glass Garmin page."},"RouteSendConsentRequest":{"properties":{"accepted":{"type":"boolean","title":"Accepted"},"terms_version":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Terms Version"},"terms_content_hash":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Terms Content Hash"},"privacy_version":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Privacy Version"},"privacy_content_hash":{"anyOf":[{"type":"string","minLength":1},{"type":"null"}],"title":"Privacy Content Hash"},"ts":{"type":"string","format":"date-time","title":"Ts"},"locale":{"type":"string","maxLength":35,"minLength":2,"title":"Locale"}},"type":"object","required":["accepted","ts","locale"],"title":"RouteSendConsentRequest","description":"Legal acceptance receipt carried by the route-send CTA."},"RouteSendLinkResolveRequest":{"properties":{"returning_authorizer_token":{"type":"string","maxLength":256,"minLength":16,"title":"Returning Authorizer Token"}},"type":"object","required":["returning_authorizer_token"],"title":"RouteSendLinkResolveRequest","description":"Resolve a durable route-send Garmin link from an opaque pointer."},"RouteSendLinkResolveResponse":{"properties":{"status":{"$ref":"#/components/schemas/RouteSendLinkResolveStatus"},"linked":{"type":"boolean","title":"Linked"},"retry_after_seconds":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Retry After Seconds"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["status","linked"],"title":"RouteSendLinkResolveResponse","description":"Browser-safe result of resolving a durable Garmin route-send link."},"RouteSendLinkResolveStatus":{"type":"string","enum":["linked","authorization_required","rate_limited","retry"],"title":"RouteSendLinkResolveStatus","description":"Browser-safe status for a returning Garmin link lookup."},"RouteSendResponse":{"properties":{"status":{"$ref":"#/components/schemas/RouteSendStatus"},"session_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Session Id"},"authorization_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization Url"},"route_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Route Url"},"gpx_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Gpx Url"},"garmin_course_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Garmin Course Url"},"garmin_course_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Garmin Course Id"},"success_redirect_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Success Redirect Url"},"response_surface":{"$ref":"#/components/schemas/RouteSendResponseSurface","default":"imperfect_owned"},"returning_authorizer_token":{"anyOf":[{"type":"string","maxLength":256,"minLength":16},{"type":"null"}],"title":"Returning Authorizer Token"},"missing_source_fields":{"items":{"type":"string"},"type":"array","title":"Missing Source Fields"},"historical_data_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Historical Data Status"},"historical_data_permissions":{"items":{"type":"string"},"type":"array","title":"Historical Data Permissions"},"retry_after_seconds":{"anyOf":[{"type":"integer","minimum":0},{"type":"null"}],"title":"Retry After Seconds"},"detail":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Detail"}},"type":"object","required":["status"],"title":"RouteSendResponse","description":"Browser-safe route-send state returned by public endpoints."},"RouteSendResponseSurface":{"type":"string","enum":["garmin_connect_course","imperfect_owned"],"title":"RouteSendResponseSurface","description":"Browser destination surface for the returned route-send state."},"RouteSendStartRequest":{"properties":{"share_id":{"type":"string","pattern":"[A-Za-z0-9][A-Za-z0-9_-]{15,63}","title":"Share Id"},"route_slug":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","title":"Route Slug"},"firebase_uid":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Firebase Uid"},"returning_authorizer_token":{"anyOf":[{"type":"string","maxLength":256,"minLength":16},{"type":"null"}],"title":"Returning Authorizer Token"},"consent":{"anyOf":[{"$ref":"#/components/schemas/RouteSendConsentRequest"},{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Consent"}},"type":"object","required":["share_id","route_slug"],"title":"RouteSendStartRequest","description":"Start a public Garmin route-send OAuth attempt."},"RouteSendStatus":{"type":"string","enum":["authorization_required","course_ready","already_sent","cancelled","expired","route_not_found","route_not_ready","missing_course_import","provider_identity_collision","alice_error","rate_limited","retry","poll_needed","session_not_found"],"title":"RouteSendStatus","description":"Browser-safe route-send UX states."},"RouteSuggestionPoolWrite":{"properties":{"key":{"type":"string","minLength":1,"title":"Key"},"cards":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Cards"},"model":{"type":"string","title":"Model"},"ttl_seconds":{"type":"integer","exclusiveMinimum":0,"title":"Ttl Seconds"}},"type":"object","required":["key","cards","model","ttl_seconds"],"title":"RouteSuggestionPoolWrite","description":"Upsert payload — mirrors looking-glass's ``RouteSuggestionStore.put``."},"StateCreateRequest":{"properties":{"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"now_iso":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Now Iso"},"feedback":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Feedback"},"notification_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notification Id"},"notification_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notification Action","description":"'reply' (default) for text/voice input, or 'button_1'/'button_2'/'button_3' for quick-reply chip taps."},"notification_label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notification Label","description":"The chip label that was tapped (null for replies)."}},"type":"object","title":"StateCreateRequest","description":"Request schema for creating/updating user state."},"TechniqueNote":{"properties":{"focus":{"type":"string","title":"Focus","description":"Area of focus, e.g. 'Breathing', 'Posture', 'Cadence', 'Zone 2'"},"instruction":{"type":"string","title":"Instruction","description":"Clear technique instruction, e.g. 'Breathe in through nose for 3 steps, out through mouth for 2'"},"why":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Why","description":"Optional explanation of why this matters, e.g. 'Helps maintain aerobic zone'"}},"type":"object","required":["focus","instruction"],"title":"TechniqueNote","description":"Technique guidance for an activity."},"UserCreateRequest":{"properties":{"name":{"type":"string","title":"Name"},"latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Latitude"},"longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Longitude"},"timezone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Timezone"},"provider":{"anyOf":[{"$ref":"#/components/schemas/ProviderType"},{"type":"null"}]}},"type":"object","required":["name"],"title":"UserCreateRequest"},"UserEventCancelRequest":{"properties":{"reason":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Reason"}},"type":"object","title":"UserEventCancelRequest","description":"Optional body for ``DELETE /user_events/{id}``."},"UserEventCreateRequest":{"properties":{"event_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Event Id"},"discipline_name":{"type":"string","title":"Discipline Name"},"distance_km":{"type":"number","title":"Distance Km"},"sport_type":{"type":"string","title":"Sport Type"},"elevation_gain_m":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Elevation Gain M"},"start_time":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Start Time"},"intent":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Intent"},"replace_existing":{"type":"boolean","title":"Replace Existing","default":true},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City"},"country":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Country"},"approximate_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Approximate Date"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["discipline_name","distance_km","sport_type"],"title":"UserEventCreateRequest","description":"Request schema for creating/updating a user event.\n\nTwo payload shapes share this endpoint:\n\n- **Catalog attach** (``event_id`` provided): the user picked a\n  race we already have indexed. Backend looks up the ``Event`` and\n  attaches via ``UserEvent.attach``.\n- **Prefilled attach** (``event_id`` omitted, ``name`` + ``city`` +\n  ``country`` + ``approximate_date`` provided): the user picked a\n  non-catalog recommendation. Backend materializes an\n  ``Event(data_quality=prefilled)``, attaches, and enqueues\n  ``confirm_prefilled_event`` to enrich it via web discovery."},"UserEventStatus":{"type":"string","enum":["planned","active","completed","cancelled"],"title":"UserEventStatus","description":"User-event lifecycle status."},"UserEventUpdateRequest":{"properties":{"race_experience":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Race Experience"},"target_time":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Target Time"},"motivation":{"anyOf":[{"type":"string","maxLength":1000},{"type":"null"}],"title":"Motivation"},"race_tier":{"anyOf":[{"$ref":"#/components/schemas/RaceTier"},{"type":"null"}]}},"type":"object","title":"UserEventUpdateRequest","description":"Request schema for updating post-goal questionnaire fields.\n\n``race_tier`` is client-settable (validated to the ``RaceTier`` enum).\n``goal_summary`` is intentionally absent — it is server-computed from\n``motivation`` and must never be accepted from the client (#1497)."},"UserProfileUpdateRequest":{"properties":{"age":{"anyOf":[{"type":"integer","minimum":1},{"type":"null"}],"title":"Age"},"height":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Height"},"weight":{"anyOf":[{"type":"number","minimum":1.0},{"type":"null"}],"title":"Weight"},"height_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Height Unit"},"weight_unit":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Weight Unit"},"gender":{"anyOf":[{"$ref":"#/components/schemas/Gender"},{"type":"null"}]},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"nickname":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Nickname"}},"type":"object","title":"UserProfileUpdateRequest"},"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"},"WeekViewBoardDay":{"properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","title":"Date"},"type":{"type":"string","enum":["past","today"],"title":"Type"},"board":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Board"}},"type":"object","required":["date","type","board"],"title":"WeekViewBoardDay","description":"One historical or current day in the rolling week response."},"WeekViewIntentDay":{"properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","title":"Date"},"type":{"type":"string","const":"intent","title":"Type"},"intent":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Intent"}},"type":"object","required":["date","type","intent"],"title":"WeekViewIntentDay","description":"One future intent day in the rolling week response."},"WeekViewResponse":{"properties":{"days":{"items":{"oneOf":[{"$ref":"#/components/schemas/WeekViewBoardDay"},{"$ref":"#/components/schemas/WeekViewIntentDay"}],"discriminator":{"propertyName":"type","mapping":{"intent":"#/components/schemas/WeekViewIntentDay","past":"#/components/schemas/WeekViewBoardDay","today":"#/components/schemas/WeekViewBoardDay"}}},"type":"array","title":"Days"},"plan_generated_at":{"type":"string","title":"Plan Generated At","description":"ISO timestamp from Cheshire when its envelope supplies one; otherwise the active legacy plan timestamp."},"plan_trigger":{"type":"string","title":"Plan Trigger","description":"Legacy plan lifecycle trigger; unchanged by read source."},"phase":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phase","description":"Phase from the selected Cheshire or legacy plan source."},"plan_source":{"type":"string","enum":["legacy","cheshire"],"title":"Plan Source","description":"cheshire only when its complete view passed every safety gate; legacy for disabled, shadow, out-of-cohort, or fallback reads."}},"type":"object","required":["days","plan_generated_at","plan_trigger","phase","plan_source"],"title":"WeekViewResponse","description":"Rolling board history and future plan intents for the mobile client."},"WidgetSessionRequest":{"properties":{"reference_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Reference Id"},"language":{"type":"string","title":"Language","default":"en"},"auth_success_redirect_url":{"type":"string","title":"Auth Success Redirect Url"},"auth_failure_redirect_url":{"type":"string","title":"Auth Failure Redirect Url"}},"type":"object","required":["auth_success_redirect_url","auth_failure_redirect_url"],"title":"WidgetSessionRequest","description":"Request schema for generating a Terra widget session."}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}}}