{"openapi":"3.1.0","info":{"title":"Prediction Pilot API","version":"0.1.0"},"paths":{"/api/copilot/chat":{"post":{"summary":"Chat","operationId":"chat_api_copilot_chat_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/chat/cancel":{"post":{"summary":"Cancel Chat","description":"Release the account's in-flight slot immediately.\n\nThe streaming generator's GeneratorExit cleanup also discards this slot,\nbut ASGI only delivers the exit at the next yield — between the user\nclicking Stop and the server's next chunk to write, the slot stays held.\nIf the user then sends a new message within that window they get 429\n'another chat is still streaming'. This endpoint short-circuits that\nrace by releasing the slot synchronously when the client knows it's\naborting.","operationId":"cancel_chat_api_copilot_chat_cancel_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/anonymous":{"post":{"summary":"Anonymous Chat","description":"Anonymous SSE tease. Rate-limited per IP via slowapi. Global daily\ncost cap enforced inside `stream_anonymous_tease`. Always returns 200\n(degrades in-band) except for explicit 422 on empty body.","operationId":"anonymous_chat_api_copilot_anonymous_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/conversations":{"get":{"summary":"List Conversations","operationId":"list_conversations_api_copilot_conversations_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/models":{"get":{"summary":"Get Models","description":"Return the model picker options for the top-bar dropdown. Single source\nof truth — the frontend renders whatever this returns, so adding/removing\nmodels is a server-only change.","operationId":"get_models_api_copilot_models_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/conversations/{conv_id}/messages":{"get":{"summary":"Get Messages","operationId":"get_messages_api_copilot_conversations__conv_id__messages_get","parameters":[{"name":"conv_id","in":"path","required":true,"schema":{"type":"string","title":"Conv Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/conversations/{conv_id}":{"delete":{"summary":"Delete Conversation","description":"Remove a conversation and all its messages. Auth-scoped to the caller.","operationId":"delete_conversation_api_copilot_conversations__conv_id__delete","parameters":[{"name":"conv_id","in":"path","required":true,"schema":{"type":"string","title":"Conv Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/credits":{"get":{"summary":"Get Credits","description":"Current token balance, subscription + cap state, and overage MTD for the UI.\n\nCopilot is purely metered (credits) — there is no parallel message-count\ngate. Trial users get the full COPILOT_TRIAL_TOKENS grant and can spend\nit on as many or as few turns as the model's actual usage allows.","operationId":"get_credits_api_copilot_credits_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/credits/usage":{"get":{"summary":"Get Credits Usage","description":"Detailed credit usage breakdown for the user-facing \"Usage\" surface.\n\nReturns total spend, per-model breakdown of chat turns, and a 30-day\ndaily timeline. Reflects interactive chat spend; nightly grid searches and\nemail digests remain platform-absorbed. (Scheduled strategy runs are now\nmetered — 2026-09-03 — but are surfaced via the strategies UI + the paused\nbanner rather than this chat-usage breakdown.)","operationId":"get_credits_usage_api_copilot_credits_usage_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/caps":{"post":{"summary":"Update Caps","description":"Update the user's monthly overage caps (soft and/or hard).","operationId":"update_caps_api_copilot_caps_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/rules":{"get":{"summary":"List Notification Rules","operationId":"list_notification_rules_api_copilot_rules_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Create Notification Rule","description":"Compile a natural-language alert request into a stored rule.","operationId":"create_notification_rule_api_copilot_rules_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/rules/{rule_id}":{"patch":{"summary":"Patch Notification Rule","operationId":"patch_notification_rule_api_copilot_rules__rule_id__patch","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Notification Rule","operationId":"delete_notification_rule_api_copilot_rules__rule_id__delete","parameters":[{"name":"rule_id","in":"path","required":true,"schema":{"type":"string","title":"Rule Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/notifications":{"get":{"summary":"List Notifications Endpoint","operationId":"list_notifications_endpoint_api_copilot_notifications_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/notifications/{notif_id}":{"patch":{"summary":"Mark Notification Read","operationId":"mark_notification_read_api_copilot_notifications__notif_id__patch","parameters":[{"name":"notif_id","in":"path","required":true,"schema":{"type":"string","title":"Notif Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/notifications/read-all":{"post":{"summary":"Mark All Notifications Read","description":"Bell-open action: atomically clear every unread notification for\nthis account. Returns the count of items marked. The frontend uses\nthis instead of N parallel per-item PATCHes — partial failure of\nthe old fan-out pattern left the unread badge persisting after the\nuser thought they'd cleared it.","operationId":"mark_all_notifications_read_api_copilot_notifications_read_all_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/push/public-key":{"get":{"summary":"Get Push Public Key","description":"Return the VAPID public key for the frontend to subscribe with.\n\nReturns empty string if push isn't configured server-side — frontend should\nskip the subscribe flow in that case.","operationId":"get_push_public_key_api_copilot_push_public_key_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/push/subscribe":{"post":{"summary":"Subscribe Push","description":"Register a Web Push subscription for this account.","operationId":"subscribe_push_api_copilot_push_subscribe_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/push/unsubscribe":{"post":{"summary":"Unsubscribe Push","description":"Remove a subscription by endpoint.","operationId":"unsubscribe_push_api_copilot_push_unsubscribe_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/push/test":{"post":{"summary":"Test Push","description":"Send a test push to all of this account's subscriptions. Diagnostic only.","operationId":"test_push_api_copilot_push_test_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/memory":{"get":{"summary":"List Memories","operationId":"list_memories_api_copilot_memory_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Create Memory","operationId":"create_memory_api_copilot_memory_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/memory/{memory_id}":{"delete":{"summary":"Delete Memory","operationId":"delete_memory_api_copilot_memory__memory_id__delete","parameters":[{"name":"memory_id","in":"path","required":true,"schema":{"type":"string","title":"Memory Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Patch Memory","operationId":"patch_memory_api_copilot_memory__memory_id__patch","parameters":[{"name":"memory_id","in":"path","required":true,"schema":{"type":"string","title":"Memory Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/proposals/{proposal_id}/execute":{"post":{"summary":"Execute Proposal","description":"Execute a previously-proposed trade. User-initiated only.","operationId":"execute_proposal_api_copilot_proposals__proposal_id__execute_post","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/dashboard":{"get":{"summary":"Get Dashboard","description":"Snapshot, featured market, settling-soon, suggestions for the home view.\n\nLegacy bundled payload — kept for back-compat with older frontend builds\nand scheduled audits. New clients should call /dashboard/fast +\n/dashboard/slow in parallel and render progressively.","operationId":"get_dashboard_api_copilot_dashboard_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/dashboard/fast":{"get":{"summary":"Get Dashboard Fast","description":"Above-the-fold dashboard payload (pure DB, sub-50ms). Snapshot strip,\nsettling-soon positions, suggestions, recent proposals, profitable\nstrategies. Paired with /dashboard/slow for progressive load.","operationId":"get_dashboard_fast_api_copilot_dashboard_fast_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/dashboard/slow":{"get":{"summary":"Get Dashboard Slow","description":"Below-the-fold dashboard payload (cached EV scans, arbitrage,\nforecast edges, AI focus, featured market). Paired with /dashboard/fast\nfor progressive load.","operationId":"get_dashboard_slow_api_copilot_dashboard_slow_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/system/status":{"get":{"summary":"Get System Status","description":"Health snapshot for the system-status widget in chat.","operationId":"get_system_status_api_copilot_system_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/strategies":{"get":{"summary":"List Copilot Strategies","description":"List all saved copilot strategies for this account.","operationId":"list_copilot_strategies_api_copilot_strategies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Create Copilot Strategy","description":"Save a strategy. Body: {name, natural_language, criteria, mode, cadence}.\n\nAfter save, immediately runs the strategy once so the user sees\nmatches without waiting for the next cadence tick (which could be\nup to an hour away on the default 'hourly' cadence). The first run\npopulates last_findings_json so the strategy's thread renders\nactual matches on first visit instead of a blank \"not yet run\"\nstate.","operationId":"create_copilot_strategy_api_copilot_strategies_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/strategies/templates":{"get":{"summary":"Get Strategy Templates","description":"Pre-built starting points so a new user gets running in one click.","operationId":"get_strategy_templates_api_copilot_strategies_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/copilot/strategies/from-template":{"post":{"summary":"Create Strategy From Template","description":"Create a strategy from a named template.","operationId":"create_strategy_from_template_api_copilot_strategies_from_template_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/strategies/{strategy_id}/detail":{"get":{"summary":"Get Strategy Detail Endpoint","description":"Return the same payload `_tool_get_strategy_detail` produces — used\nby the frontend to render the StrategyDetailCard inline on first-visit\nto a strategy thread WITHOUT invoking the AI. The AI route still\nexists (via the tool) for when the user asks about the strategy in\nchat; this is the read-only direct path for \"land on the thread, see\nthe card\" UX.","operationId":"get_strategy_detail_endpoint_api_copilot_strategies__strategy_id__detail_get","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/strategies/{strategy_id}/open":{"post":{"summary":"Open Strategy Thread","description":"Return the conversation_id for this strategy's persistent thread.\nLazy-creates the conversation if absent (older strategies pre-dating the\nv109 migration won't have one yet). Used when the sidebar clicks a\nstrategy and needs to navigate into its thread.\n\nReturns {conversation_id, has_messages}. The frontend uses has_messages\nto decide whether to auto-fire the get_strategy_detail prompt on entry.","operationId":"open_strategy_thread_api_copilot_strategies__strategy_id__open_post","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/strategies/{strategy_id}":{"patch":{"summary":"Patch Copilot Strategy","description":"Toggle enabled, switch mode/cadence, or rename. Body keys: enabled, mode, cadence, name.","operationId":"patch_copilot_strategy_api_copilot_strategies__strategy_id__patch","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Copilot Strategy","operationId":"delete_copilot_strategy_api_copilot_strategies__strategy_id__delete","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/strategies/{strategy_id}/report":{"get":{"summary":"Get Strategy Report","description":"Paper-trading report card for a strategy's alerts in the lookback\nwindow. Joins memorialized strategy_fire_event matches against\nsettled_markets and returns hit rate, P&L per $10 contract, and a\nlist of losing alerts for \"review the misses\".\n\nThe single proof-of-value surface that closes the trial-user\njourney loop: alerts → outcomes → confidence to convert.","operationId":"get_strategy_report_api_copilot_strategies__strategy_id__report_get","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}},{"name":"lookback_days","in":"query","required":false,"schema":{"type":"integer","default":7,"title":"Lookback Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/strategies/{strategy_id}/run":{"post":{"summary":"Run Copilot Strategy","description":"Force an immediate evaluation. Useful for \"Run now\" buttons in the UI.","operationId":"run_copilot_strategy_api_copilot_strategies__strategy_id__run_post","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/proposals/{proposal_id}":{"get":{"summary":"Get Proposal","description":"Return a proposal's current state — used by the chat UI after click.","operationId":"get_proposal_api_copilot_proposals__proposal_id__get","parameters":[{"name":"proposal_id","in":"path","required":true,"schema":{"type":"string","title":"Proposal Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/copilot/credits/checkout":{"post":{"summary":"Credits Checkout","description":"Create a Stripe Checkout session for the $14/mo copilot subscription.","operationId":"credits_checkout_api_copilot_credits_checkout_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/live":{"get":{"summary":"Live Check","description":"Liveness probe — is the process still functional?\n\nDistinct from /health (readiness). This endpoint does NOT touch the\ndatabase because a DB blip is not a reason to restart the container.\nWe only fail here when the process is in a state Railway should\nrecycle it from: specifically, when the connection pool has been\nfully exhausted for more than 60 seconds (an in-process leak we\ncan't recover from without a restart).\n\nTrack B of docs/architecture-hardening-plan.md — the 2026-07-13\n5-hour outage would have self-healed in ≤60s if Railway had been\nprobing /live instead of /health.","operationId":"live_check_live_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/health":{"get":{"summary":"Health Check","description":"Lightweight health endpoint for load balancers — no auth required.\n\nReturns 503 if any critical dependency is unhealthy so Railway will\nrecycle the container instead of routing traffic to a half-broken pod.\nA 200 with `{\"database\": false}` was treated as healthy by every load\nbalancer we've ever seen, masking real outages.\n\nKept intentionally lean: only two probes, both cheap.\n  - _LEDGER_OK is a boolean checked at import time; no query.\n  - check_db_health() runs a single `SELECT 1` against the pool.\nWe removed the `SELECT COUNT(*) FROM market_snapshots` probe on\n2026-07-08 after the audit — that full-scan COUNT was seconds-slow\nunder load and blocked the worker slot while running, turning brief\nDB pressure into UNHEALTHY blips. If you want a data-freshness\nsignal, add a dedicated endpoint (not on the health critical path).","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/health/ledger":{"get":{"summary":"Ledger Health","description":"Compare ledger balance against Kalshi API — detect drift.","operationId":"ledger_health_api_health_ledger_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/engine/status":{"get":{"summary":"Engine Status","description":"Check if the trading engine is running for this account.","operationId":"engine_status_api_engine_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/engine/restart":{"post":{"summary":"Engine Restart","description":"Restart the trading engine for this account.","operationId":"engine_restart_api_engine_restart_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/engine/pause":{"post":{"summary":"Engine Pause","description":"Toggle trading pause. Persisted in DB config — survives deploys and engine restarts.","operationId":"engine_pause_api_engine_pause_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/status":{"get":{"summary":"Get Status","description":"Current bot state. tz = browser timezone offset in minutes. since = YYYY-MM-DD date filter.","operationId":"get_status_api_status_get","parameters":[{"name":"tz","in":"query","required":false,"schema":{"type":"integer","default":0,"title":"Tz"}},{"name":"since","in":"query","required":false,"schema":{"type":"string","title":"Since"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/scan":{"get":{"summary":"Scan Markets","description":"Scan market_snapshots with user's default config filters. Returns opportunities.","operationId":"scan_markets_api_scan_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/strategies":{"get":{"summary":"List Strategies","description":"List all strategies for this account.","operationId":"list_strategies_api_strategies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Create Strategy","description":"Create a new named strategy. Enforces plan limits.","operationId":"create_strategy_api_strategies_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/strategies/{strategy_id}":{"patch":{"summary":"Update Strategy","description":"Update a strategy's name or config.","operationId":"update_strategy_api_strategies__strategy_id__patch","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete Strategy","description":"Delete a strategy.","operationId":"delete_strategy_api_strategies__strategy_id__delete","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/scan/{strategy_id}":{"get":{"summary":"Scan With Strategy","description":"Scan markets using a specific strategy's config.","operationId":"scan_with_strategy_api_scan__strategy_id__get","parameters":[{"name":"strategy_id","in":"path","required":true,"schema":{"type":"string","title":"Strategy Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/backtest/status":{"get":{"summary":"Backtest Status","description":"Check backtest data readiness.","operationId":"backtest_status_api_backtest_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/backtest":{"post":{"summary":"Run Backtest Endpoint","description":"Run a backtest with the given strategy config.\nBody: { config: {strategy fields}, lookbackDays: 90 }","operationId":"run_backtest_endpoint_api_backtest_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/backtest/suggest":{"post":{"summary":"Backtest Suggest","description":"AI-powered analysis of backtest results with config suggestions.","operationId":"backtest_suggest_api_backtest_suggest_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/strategy/generate":{"post":{"summary":"Generate Strategy Endpoint","description":"AI generates a complete strategy from historical data. Pro only.","operationId":"generate_strategy_endpoint_api_strategy_generate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/whoami":{"get":{"summary":"Admin Whoami","description":"Surface the resolved role to the admin UI so it can hide\nadmin-only tabs and actions from the growth contractor.","operationId":"admin_whoami_api_admin_whoami_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/users":{"get":{"summary":"Admin List Users","description":"List active users with account, subscription, copilot, strategy, and\nscan-activity context. Bounded by `limit` (default 200, max 1000) —\nwithout it the query is unbounded and times out as the user table\ngrows.\n\nAll per-user aggregates are folded into the main query via JOINs\nagainst GROUP BY CTEs. The previous shape ran 3 extra queries per\nrow (scan count, last scan, overage MTD) which was an N+1 of size\nO(active_users × 3) — 18s under load.","operationId":"admin_list_users_api_admin_users_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/missing-capabilities":{"get":{"summary":"Admin Missing Capabilities","description":"Top missing capabilities the assistant tried to log over the last\n`days`. Aggregates by lowercased description so duplicate phrasings\ncollapse into one row with a count. Use this to decide what to\nbuild next rather than guessing from intuition.","operationId":"admin_missing_capabilities_api_admin_missing_capabilities_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/backtest-smoke":{"post":{"summary":"Admin Backtest Smoke","description":"Run ONE backtest with default-ish config; return elapsed time.\n\nPurpose: validate the 2026-07-06 backtester query optimization\n(commit e5fdeb2) without waiting for the daily 09:00 UTC discovery\ncron. Answers the question \"did we get the candle-load query\nunder the 60s statement_timeout, or do we need worker isolation?\"\n\nAuth: reuses EMERGENCY_RESTART_TOKEN via Authorization: Bearer\n(same secret; no new env var to configure). Bypasses the general\nauth_middleware (added to the bypass list in that middleware).\nRate-limited via alert_throttle so it can't be run in a hot loop.","operationId":"admin_backtest_smoke_api_admin_backtest_smoke_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/emergency-worker-restart":{"post":{"summary":"Admin Emergency Worker Restart","description":"Force this uvicorn worker to exit(0); the parent respawns it fresh.\n\nPurpose: give the AI-ops assistant a bounded restart primitive for the\n\"leader stalled, pool clean, HTTP flapping\" failure signature that hit\non 2026-07-04. A full Railway service restart is manual + heavy; this\nendpoint restarts ONE worker at a time.\n\nAuth: `Authorization: Bearer $EMERGENCY_RESTART_TOKEN`. The token must\nbe configured via env var on Railway. If unset, the endpoint refuses\nall requests (empty-string comparison would otherwise let an empty\ntoken match).\n\nRate limit: 1 per 5 min, enforced via alert_throttle (Postgres-backed,\nshared across workers). Prevents an AI-ops loop from restart-thrashing\nthe service.\n\nScope:\n  - \"any\" (default): whichever worker Railway routes to, exits.\n  - \"leader\": only exits if this worker holds the leader advisory\n    lock — lets the AI target the leader specifically after a stall.\n\nExit is delayed ~500 ms via a background thread so the HTTP response\ncan flush before the process dies.","operationId":"admin_emergency_worker_restart_api_admin_emergency_worker_restart_post","parameters":[{"name":"scope","in":"query","required":false,"schema":{"type":"string","default":"any","title":"Scope"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/db/kill-idle":{"post":{"summary":"Admin Kill Idle In Tx","description":"Terminate Postgres connections that have been idle-in-transaction\nlonger than `min_age_seconds`. The emergency-cleanup we needed at\n2026-06-23 evening when 17 stuck connections were holding work_mem\nand locks. Going through this endpoint means no external psql\nrequired next time.","operationId":"admin_kill_idle_in_tx_api_admin_db_kill_idle_post","parameters":[{"name":"min_age_seconds","in":"query","required":false,"schema":{"type":"integer","default":60,"title":"Min Age Seconds"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/copilot-smoke-report":{"get":{"summary":"Admin Copilot Smoke Report","description":"Nightly copilot smoke results — what passed/failed in each\nscenario over the last `days` days.\n\nSurface of the autonomous improvement loop: the next session's\nClaude reads this report, identifies regressions (assertions\nthat started failing), and proposes fixes. Without persistence\nthe loop has no memory.\n\nResponse shape per scenario:\n  {scenario_id, total_runs, pass_count, fail_count,\n   recent_failures: [{started_at, failed_assertions, transcript}, ...]}","operationId":"admin_copilot_smoke_report_api_admin_copilot_smoke_report_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":7,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/copilot-smoke/run":{"post":{"summary":"Admin Copilot Smoke Run","description":"Fire an immediate copilot smoke run from inside the container,\nbypassing the 9 UTC schedule. Used to verify the harness right after\nSMOKE_TEST_TOKEN lands on Railway (or before/after a big change),\nwithout waiting for the nightly cron.\n\nReturns the per-scenario pass/fail summary inline so the admin can\nspot regressions immediately; the full transcripts land in\n``copilot_smoke_runs`` either way and are readable via the report\nendpoint.","operationId":"admin_copilot_smoke_run_api_admin_copilot_smoke_run_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/pool-stats":{"get":{"summary":"Admin Pool Stats","description":"Track C of docs/architecture-hardening-plan.md — dedicated\npool-health diagnostic. Same data as the `postgres` block in\n/api/admin/health/runtime but flat and including the classified\nsignal (green/yellow/red) for at-a-glance triage.\n\nUsed by both the admin UI and the nightly cron that pages on\nnon-green states.","operationId":"admin_pool_stats_api_admin_pool_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/health/runtime":{"get":{"summary":"Admin Runtime Health","description":"RSS, GC stats, Anthropic singleton state, Postgres pool state.\nThe leak-diagnosis endpoint — answers 'is the singleton actually\ncached' and 'is the pool actually wired' in one curl instead of 30\nminutes of source reading.","operationId":"admin_runtime_health_api_admin_health_runtime_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/health/billing":{"get":{"summary":"Admin Billing Health","description":"Is the copilot checkout actually able to run? Verifies the Stripe price\nIDs the checkout hard-requires are configured — a missing one makes every\nsubscribe attempt 422 and reads as converted=0. One curl to rule the\nsilent-config-break in or out.","operationId":"admin_billing_health_api_admin_health_billing_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/retention/run":{"post":{"summary":"Admin Retention Run","description":"Manually fire the retention prune. Used when Railway service has no\ninteractive shell — admin can hit this via curl to claw back disk\nimmediately rather than waiting for the next scheduled 08:00 UTC fire.\n\nGoes through the same `run_scheduled_prune` wrapper as the scheduled\njob, so it acquires the distributed lock and writes a cron_runs audit\nrow — multiple admins (or a deploy race) can't double-trigger.","operationId":"admin_retention_run_api_admin_retention_run_post","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/candle-coverage":{"get":{"summary":"Admin Candle Coverage","description":"Coverage report for the candle backfill. Defaults to the\nbacktest-meaningful threshold (vol_24h >= 1000) rather than the\nraw eligibility floor (vol >= 50), because the latter inflates\n\"missing\" with thinly-traded markets we don't actually backtest.\nPass ?min_volume=50 to see the full backfill universe.\n\nAlso includes throughput stats from recent successful cron cycles\nand an ETA so admins can tell at a glance whether the backfill is\nhealthy vs wedged vs caught up.","operationId":"admin_candle_coverage_api_admin_candle_coverage_get","parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","default":90,"title":"Days"}},{"name":"min_volume","in":"query","required":false,"schema":{"type":"integer","default":1000,"title":"Min Volume"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/ingest-health":{"get":{"summary":"Admin Ingest Health","description":"Heartbeat status of market_ingest. Surfaces the same info the\nin-process watchdog uses, so an admin can see at a glance whether\nthe loop is alive and whether the watchdog had to restart it.","operationId":"admin_ingest_health_api_admin_ingest_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/recent-anomalies":{"get":{"summary":"Admin Recent Anomalies","description":"Last N notable WARNING+ log events (pool reclaims, exhaustion,\ndead-conn recoveries, ingest-stale), newest first — one-curl triage\nof \"what's been going wrong\" without scrolling raw logs. In-memory\nper process; resets on restart (Railway retention is the archive).","operationId":"admin_recent_anomalies_api_admin_recent_anomalies_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":50,"title":"Limit"}},{"name":"min_level","in":"query","required":false,"schema":{"type":"string","default":"WARNING","title":"Min Level"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/health/all":{"get":{"summary":"Admin Health All","description":"One-call aggregate of every read-only diagnostic: pool state,\nruntime (RSS/GC/singleton), ingest heartbeat, and recent anomalies.\nEach block is captured independently so one failing probe still\nreturns the others (partial > nothing during an incident).","operationId":"admin_health_all_api_admin_health_all_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/users/{account_id}/plan":{"patch":{"summary":"Admin Update Plan","description":"Change a user's subscription plan.","operationId":"admin_update_plan_api_admin_users__account_id__plan_patch","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/test-emails":{"post":{"summary":"Admin Test Emails","description":"Send all email types to a test address. Admin only.","operationId":"admin_test_emails_api_admin_test_emails_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/resend-copilot-welcome":{"post":{"summary":"Admin Resend Copilot Welcome","description":"Resend the copilot subscription welcome email to a specific address\nor account. Built for the very first paying customer whose welcome\ndidn't fire on subscription_create because the email wiring was missing\nat the time. Admin only. Accepts {email} or {account_id}.","operationId":"admin_resend_copilot_welcome_api_admin_resend_copilot_welcome_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/verify-pnl":{"get":{"summary":"Admin Verify Pnl","description":"Compare Kalshi positions API realized_pnl vs our stored settlements. Admin only.","operationId":"admin_verify_pnl_api_admin_verify_pnl_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/data-ranges":{"get":{"summary":"Admin Data Ranges","description":"Diagnostic: oldest/newest/count per historical-market table.","operationId":"admin_data_ranges_api_admin_data_ranges_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/prune-historical":{"post":{"summary":"Admin Prune Historical","description":"Prune retention-managed tables. `retention_days` controls the 90-day\nbucket (market_candles, settled_markets, copilot_messages, terminal-state\ncopilot_trade_proposals). The 30-day (copilot_notifications, activity_events)\nand 7-day (cron_runs) windows are fixed. User account data is never touched.\nUse `dry_run=true` to count without deleting.","operationId":"admin_prune_historical_api_admin_prune_historical_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/recompute-win-rates":{"post":{"summary":"Admin Recompute Win Rates","description":"Recompute per-bucket empirical NO-resolution rates from settled_markets.\n\nReplaces the static EMPIRICAL_WIN_RATES table (paper-derived) with our\nown ledger calibration. Designed to run weekly via cron; this endpoint\nexists for manual recalibration after a large settlement batch lands.","operationId":"admin_recompute_win_rates_api_admin_recompute_win_rates_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/backfill-settled-window":{"post":{"summary":"Admin Backfill Settled Window","description":"Force a one-shot backfill of settled markets within `days_back`\ndays. Targets the gap that the regular _backfill_recent_settlements\n(days_back=2) doesn't reach. Idempotent — Kalshi tickers already in\nsettled_markets get UPSERTed, not duplicated. Runs in a background\nthread because pagination across 30 days of historical Kalshi data\ntakes longer than the FastAPI request timeout. Poll the same path\nvia GET to observe progress.","operationId":"admin_backfill_settled_window_api_admin_backfill_settled_window_post","parameters":[{"name":"days_back","in":"query","required":false,"schema":{"type":"integer","default":30,"title":"Days Back"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"summary":"Admin Backfill Settled Window Status","description":"Observe the in-flight or last-completed one-shot settled backfill.","operationId":"admin_backfill_settled_window_status_api_admin_backfill_settled_window_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/discover-profitable-strategies":{"get":{"summary":"Admin Discovery Status","description":"Observe in-flight or last-completed manual discovery.","operationId":"admin_discovery_status_api_admin_discover_profitable_strategies_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Admin Discover Profitable Strategies","description":"Manually trigger the profitable-strategy discovery grid. Runs in a\nbackground thread because the discovery takes 7-25 min. Poll the GET\nsibling to watch progress.","operationId":"admin_discover_profitable_strategies_api_admin_discover_profitable_strategies_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/emergency-truncate":{"post":{"summary":"Admin Emergency Truncate","description":"Emergency disk-recovery: TRUNCATE the reconstructible market-data tables.\n\nUnlike DELETE, TRUNCATE actually returns disk to the OS on Postgres (and\nrebuilds the SQLite page file). Both market_candles and market_snapshots\nare fully reconstructible from Kalshi's API by the daily ingest jobs, so\ntruncating them is safe. User-account tables are NEVER touched.\n\nBody: {\"confirm\": true, \"tables\": [\"market_candles\", \"market_snapshots\", \"settled_markets\"]}\nDefault if `tables` is omitted: market_candles only (the largest table).","operationId":"admin_emergency_truncate_api_admin_emergency_truncate_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/resync-positions":{"post":{"summary":"Admin Resync Positions","description":"Replace the local positions table for this account with Kalshi's\ncurrent truth. Use when the local table is stale (e.g. closes\nhappened outside the bot's sight, so the SELL-fill cleanup path\nnever ran). Reads from Kalshi's /portfolio/positions endpoint.","operationId":"admin_resync_positions_api_admin_resync_positions_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/resync-settlements":{"post":{"summary":"Admin Resync Settlements","description":"Delete all settlements and re-sync from Kalshi. Fixes P&L calculation bugs.","operationId":"admin_resync_settlements_api_admin_resync_settlements_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/import-csv-pnl":{"post":{"summary":"Admin Import Csv Pnl","description":"Import P&L corrections from Kalshi CSV transaction export. Admin only.\nBody: { \"trades\": [{\"ticker\": \"...\", \"pnl_cents\": 123}, ...] }\nUpdates net_pnl_cents for existing settlements.","operationId":"admin_import_csv_pnl_api_admin_import_csv_pnl_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/send-digest":{"post":{"summary":"Admin Send Digest","description":"Send weekly digest to a specific email. Admin only.","operationId":"admin_send_digest_api_admin_send_digest_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/generate-blog":{"post":{"summary":"Admin Generate Blog","description":"Generate weekly blog post from backtest data. Admin only. Runs in background.","operationId":"admin_generate_blog_api_admin_generate_blog_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/generate-social":{"post":{"summary":"Admin Generate Social","description":"Generate daily social posts from market data. Admin only.","operationId":"admin_generate_social_api_admin_generate_social_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/social-posts":{"get":{"summary":"Admin List Social Posts","description":"List pending social posts for review. Admin only.","operationId":"admin_list_social_posts_api_admin_social_posts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/social-posts/{post_id}":{"patch":{"summary":"Admin Update Social Post","description":"Approve/reject a social post. Admin only.","operationId":"admin_update_social_post_api_admin_social_posts__post_id__patch","parameters":[{"name":"post_id","in":"path","required":true,"schema":{"type":"integer","title":"Post Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/test":{"get":{"summary":"Admin Test","description":"Bare test endpoint for admin debugging.","operationId":"admin_test_api_admin_test_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/grant-monthly-copilot":{"post":{"summary":"Admin Grant Monthly Copilot","description":"Force-grant the current monthly copilot allotment to an account.\n\nBody: {\"account_id\": \"acc_…\", \"reason\": \"<note for audit>\"}.\nRefuses if no active copilot_subscriptions row. Idempotent: derives the\nstripe_event_id from (account_id, current_period_end) so a re-run lands\non the same key and the credits ledger dedups.","operationId":"admin_grant_monthly_copilot_api_admin_grant_monthly_copilot_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/forgive-overage":{"post":{"summary":"Admin Forgive Overage","description":"Wipe all overage events for an account up to a cutoff timestamp,\nAND offset Stripe-reported meter events with negative meter events so\nthe upcoming invoice doesn't bill them either.\n\nBody: {\"account_id\": \"acc_…\", \"through\": \"<ISO timestamp, defaults to now>\"}.\nUse case: webhook silently skipped the monthly grant, the user incurred\noverage they shouldn't have. Make-whole both locally (admin UI shows\n$0.00 MTD) and on Stripe (next invoice doesn't bill the wiped amount).","operationId":"admin_forgive_overage_api_admin_forgive_overage_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/backfill-sub-prices":{"post":{"summary":"Admin Backfill Sub Prices","description":"Pull each copilot_subscriptions row's flat-fee price AND\ncurrent_period_end from Stripe and write them back to the local row.\n\nOne-shot fix-up for rows that pre-date migration v140 OR were affected\nby the epoch-0 timestamp bug (current_period_end stored as 1970-01-01,\nwhich rendered as 'resets 12/31/1969' in the admin UI). Idempotent —\nre-running just re-confirms each row. Safe to call any time.","operationId":"admin_backfill_sub_prices_api_admin_backfill_sub_prices_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/users/{account_id}":{"get":{"summary":"Admin User Detail","description":"Full per-account detail for the admin user drawer.","operationId":"admin_user_detail_api_admin_users__account_id__get","parameters":[{"name":"account_id","in":"path","required":true,"schema":{"type":"string","title":"Account Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/analytics/summary":{"get":{"summary":"Admin Analytics Summary","description":"High-signal funnel + attribution + activity summary for the\nAnalytics tab. Both admin and growth roles.","operationId":"admin_analytics_summary_api_admin_analytics_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/ops/health":{"get":{"summary":"Admin Ops Health","description":"Per-cron health summary for the Operations status strip.\nAdmin-only — Operations tab is admin-only.","operationId":"admin_ops_health_api_admin_ops_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/analytics/snapshot":{"post":{"summary":"Admin Snapshot Now","description":"Manually trigger today's economics snapshot. Used to seed the\ntable once before the daily 9 UTC cron has fired.","operationId":"admin_snapshot_now_api_admin_analytics_snapshot_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/blog/posts":{"get":{"summary":"Admin Blog List","description":"List blog posts, optionally filtered by status.","operationId":"admin_blog_list_api_admin_blog_posts_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"summary":"Admin Blog Create","description":"Create a new post (defaults to draft). Returns the new slug.","operationId":"admin_blog_create_api_admin_blog_posts_post","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/blog/posts/{slug}":{"get":{"summary":"Admin Blog Get","description":"Fetch one post including its markdown source for editing.","operationId":"admin_blog_get_api_admin_blog_posts__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Admin Blog Update","description":"Auto-save / edit. Send partial fields; HTML is re-rendered when\nmarkdown_source is in the body.","operationId":"admin_blog_update_api_admin_blog_posts__slug__patch","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Admin Blog Archive","description":"Soft-delete via status='archived'. Hard delete intentionally\nnot exposed — archived posts disappear from listings but their URL\nkeeps resolving for anyone who bookmarked it.","operationId":"admin_blog_archive_api_admin_blog_posts__slug__delete","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/blog/posts/{slug}/publish":{"post":{"summary":"Admin Blog Publish","description":"Publish now or schedule. Growth posts auto-delay to 24h floor.","operationId":"admin_blog_publish_api_admin_blog_posts__slug__publish_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/blog/posts/{slug}/preview":{"post":{"summary":"Admin Blog Preview","description":"Render the markdown without saving. Used for live preview pane.","operationId":"admin_blog_preview_api_admin_blog_posts__slug__preview_post","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/feedback":{"post":{"summary":"Submit Feedback Route","description":"Capture a feedback submission from the floating in-app button.\n\nAny signed-in user can submit. Body: {category: 'bug'|'idea'|'other',\nbody: '...', page_url: '...'} — user_agent is taken from the request\nheader. The page_url and user_agent help reproduce bugs without us\nhaving to ask follow-up questions.","operationId":"submit_feedback_route_api_feedback_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/feedback":{"get":{"summary":"Admin List Feedback","description":"List feedback for the admin review panel. Optional filters.\nShared surface — growth contractor can view to spot UX issues.","operationId":"admin_list_feedback_api_admin_feedback_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"}},{"name":"category","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Category"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":200,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/feedback/{feedback_id}":{"patch":{"summary":"Admin Update Feedback","description":"Update status and/or admin_notes on a feedback row.\nShared surface — growth contractor can triage feedback too.","operationId":"admin_update_feedback_api_admin_feedback__feedback_id__patch","parameters":[{"name":"feedback_id","in":"path","required":true,"schema":{"type":"string","title":"Feedback Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/query":{"post":{"summary":"Admin Query","description":"Run a read-only SQL query. Admin only. For debugging.","operationId":"admin_query_api_admin_query_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/seed-historical":{"post":{"summary":"Admin Seed Historical","description":"Trigger background seed of historical market data. Admin only.","operationId":"admin_seed_historical_api_admin_seed_historical_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/probe-candles":{"get":{"summary":"Admin Probe Candles","description":"Diagnostic: hit Kalshi's candle endpoints directly with multiple\npath variants and return what each one says. Admin only.\n\nUse to figure out the correct URL pattern when our standard fetch\nis returning empty across the board. Pass ?ticker=<some_ticker>\nthat's known to have candle data.","operationId":"admin_probe_candles_api_admin_probe_candles_get","parameters":[{"name":"ticker","in":"query","required":false,"schema":{"type":"string","default":"","title":"Ticker"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/admin/seed-candles-only":{"post":{"summary":"Admin Seed Candles Only","description":"Skip the markets phase and run candles against existing\nsettled_markets. Use when the markets phase wedged but you have\nenough rows to backtest against. Admin only.\n\nForces the worker into the candles phase regardless of current\nseed_status; safe because candles fetching is idempotent\n(per-ticker existence check before each Kalshi call).","operationId":"admin_seed_candles_only_api_admin_seed_candles_only_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/seed-status":{"get":{"summary":"Admin Seed Status","description":"Check historical data seed progress. Admin only. State is\nDB-backed so this survives container restarts.","operationId":"admin_seed_status_api_admin_seed_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/admin/stats":{"get":{"summary":"Admin Stats","description":"Platform-wide stats — operational + copilot economics.\n\nShared surface — both admin and growth contractor can read this for\nthe Overview + Economics tabs. Aggregate only (no per-account data).\n\nAll cents fields are integers; convert to dollars in the UI.\n\nEconomics model:\n  - Revenue: MRR = paid_subs × $14, plus overage MTD already charged\n  - Cost: Anthropic raw token cost at ~$3.01 per 1M credits at the\n    observed Sonnet token-class mix (70% cache-read, 10% uncached\n    input, 15% output, 5% cache-write). Plus Stripe fees at\n    $0.30 + 2.9% per paid sub.\n  - Net = revenue − cost − fees","operationId":"admin_stats_api_admin_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/strategy/assist":{"post":{"summary":"Strategy Assist","description":"AI-powered strategy assistant. Translates natural language to filter config.","operationId":"strategy_assist_api_strategy_assist_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/analysis":{"get":{"summary":"Get Analysis","description":"AI-powered account analysis. Observational insights, never advice.","operationId":"get_analysis_api_analysis_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/strategy/templates":{"get":{"summary":"Strategy Templates","description":"Return available strategy templates (cold start configurations).","operationId":"strategy_templates_api_strategy_templates_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/stats/featured":{"get":{"summary":"Public Featured Markets","description":"Live highlighted markets for the landing page's 'live now' strip.\n\nReturns up to 4 high-volume open markets, one per category where\npossible (sports / crypto / weather / financials), so the strip\nshows category breadth at a glance.\n\nPublic read; no PII; no per-user data.","operationId":"public_featured_markets_api_stats_featured_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/stats/public":{"get":{"summary":"Public Stats","description":"Public platform stats for social proof. Cached for 5 minutes.","operationId":"public_stats_api_stats_public_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/blog/weekly":{"get":{"summary":"List Weekly Blogs","description":"Public: list published weekly blog posts.","operationId":"list_weekly_blogs_api_blog_weekly_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/subscribe":{"post":{"summary":"Subscribe Email","description":"Public email capture for landing page / blog visitors.","operationId":"subscribe_email_api_subscribe_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/preview/assist":{"post":{"summary":"Preview Assist","description":"Public AI strategy assistant for landing page. No auth required.","operationId":"preview_assist_api_preview_assist_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/market-intel":{"get":{"summary":"Get Market Intel","description":"Market intelligence for strategy decisions. Pure data, no advice.","operationId":"get_market_intel_api_market_intel_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/insights":{"get":{"summary":"Get Insights","description":"Trade insights — category breakdown, time-of-day, edge analysis.","operationId":"get_insights_api_insights_get","parameters":[{"name":"since","in":"query","required":false,"schema":{"type":"string","title":"Since"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/activity":{"get":{"summary":"Get Activity","description":"Recent activity events for the dashboard feed.","operationId":"get_activity_api_activity_get","parameters":[{"name":"event_type","in":"query","required":false,"schema":{"type":"string","title":"Event Type"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":50,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/trades":{"get":{"summary":"Get Trades","description":"Paginated, filtered trade history with cost/exit/P&L per trade.\n\nstatus: all | open | settled | pending | cancelled\nfrom / to: YYYY-MM-DD date range on placed_at\nsearch: substring match on ticker or title","operationId":"get_trades_api_trades_get","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","default":"all","title":"Status"}},{"name":"from","in":"query","required":false,"schema":{"type":"string","title":"From"}},{"name":"to","in":"query","required":false,"schema":{"type":"string","title":"To"}},{"name":"search","in":"query","required":false,"schema":{"type":"string","title":"Search"}},{"name":"page","in":"query","required":false,"schema":{"type":"integer","minimum":1,"default":1,"title":"Page"}},{"name":"per_page","in":"query","required":false,"schema":{"type":"integer","maximum":200,"minimum":1,"default":25,"title":"Per Page"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ingest":{"post":{"summary":"Force Ingest","description":"Force a market data refresh. Returns auth status immediately, runs ingest in background.","operationId":"force_ingest_api_ingest_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/sync":{"post":{"summary":"Sync Portfolio","description":"Sync fills, settlements, and balance from Kalshi API using stored credentials.","operationId":"sync_portfolio_api_sync_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/rebuild-history":{"get":{"summary":"Rebuild History","description":"Rebuild balance_history.json from Kalshi fills + settlements API (authoritative source).","operationId":"rebuild_history_api_rebuild_history_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/config":{"get":{"summary":"Get Config","description":"Current strategy configuration.","operationId":"get_config_api_config_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"patch":{"summary":"Update Config","description":"Update strategy config in the ledger DB (takes effect on next scan cycle).","operationId":"update_config_api_config_patch","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Updates"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/snapshots":{"post":{"summary":"Create Snapshot","description":"Create a shareable P&L snapshot token (auth required).\n\nFreezes the computed P&L data at creation time so share links are stable.\nLive snapshots get refreshed on access if new settlements exist.","operationId":"create_snapshot_api_snapshots_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/snapshots/simulation":{"post":{"summary":"Create Simulation Snapshot","description":"Create a shareable simulation snapshot.\nBody: { \"result\": {...backtest result}, \"config\": {...strategy config} }","operationId":"create_simulation_snapshot_api_snapshots_simulation_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/share/{token}":{"get":{"summary":"Get Share","description":"Public endpoint — returns frozen P&L snapshot. No auth required.\n\nNon-live: always serves the frozen data from creation time.\nLive: refreshes from settlements if newer data exists, then re-freezes.","operationId":"get_share_api_share__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/me":{"get":{"summary":"Get Me","description":"Return account identity and setup status for the authenticated user.\n\nClients use needs_setup=true to show the onboarding screen instead of the dashboard.","operationId":"get_me_api_me_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/me/attribution":{"post":{"summary":"Set Attribution","description":"Store sign-up attribution data (UTM params, referrer) for the account.\n\nWhen `referral_code` is present in the payload, also resolve it to a\nreferrer account via `record_referral_attribution` — writes a row to\nthe `referrals` table so the upcoming first-paid-invoice webhook can\naward bonus credits to both sides.","operationId":"set_attribution_api_me_attribution_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/referral/code":{"get":{"summary":"Get Referral Code","description":"Return (and lazily allocate) the caller's referral code + share URL.","operationId":"get_referral_code_api_referral_code_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/referral/stats":{"get":{"summary":"Get Referral Stats Endpoint","description":"Counters powering the Refer-a-Friend modal: invited / converted /\nbonus tokens earned. All as the referrer.","operationId":"get_referral_stats_endpoint_api_referral_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/accounts":{"get":{"summary":"List Accounts Endpoint","description":"List all tenant accounts. Admin only.","operationId":"list_accounts_endpoint_api_accounts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Create Account Endpoint","description":"Create a new tenant account.\n\nBody: { \"name\": \"My Account\", \"mode\": \"demo\" | \"live\" }\nReturns: { \"account_id\": \"acc_...\", \"api_key\": \"...\", \"message\": \"...\" }","operationId":"create_account_endpoint_api_accounts_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/accounts/{target_account_id}/credentials":{"post":{"summary":"Store Account Credentials","description":"Store encrypted Kalshi API credentials for a tenant account.\n\nBody: { \"kalshi_api_key\": \"...\", \"kalshi_pem\": \"-----BEGIN RSA PRIVATE KEY-----\\n...\" }","operationId":"store_account_credentials_api_accounts__target_account_id__credentials_post","parameters":[{"name":"target_account_id","in":"path","required":true,"schema":{"type":"string","title":"Target Account Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/unsubscribe":{"post":{"summary":"Unsubscribe Emails","description":"Unsubscribe from marketing emails. Works with or without auth.","operationId":"unsubscribe_emails_api_unsubscribe_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/credentials/status":{"get":{"summary":"Credentials Status","description":"Check if Kalshi credentials are connected.","operationId":"credentials_status_api_credentials_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/credentials":{"delete":{"summary":"Delete Credentials","description":"Disconnect Kalshi account by removing stored credentials.","operationId":"delete_credentials_api_credentials_delete","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/accounts/{target_account_id}/api-keys":{"post":{"summary":"Create Account Api Key","description":"Generate a new dashboard API key for an account.\n\nBody (optional): { \"label\": \"my-key-label\" }\nReturns: { \"api_key\": \"...\" } — shown only once.","operationId":"create_account_api_key_api_accounts__target_account_id__api_keys_post","parameters":[{"name":"target_account_id","in":"path","required":true,"schema":{"type":"string","title":"Target Account Id"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true,"default":{},"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/webhooks/clerk":{"post":{"summary":"Clerk Webhook","description":"Handle Clerk user lifecycle events.\n\nuser.created → ensure account exists in ledger\nuser.deleted → deactivate account","operationId":"clerk_webhook_webhooks_clerk_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/webhooks/stripe":{"post":{"summary":"Stripe Webhook","description":"Handle Stripe subscription lifecycle events.","operationId":"stripe_webhook_webhooks_stripe_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/billing/status":{"get":{"summary":"Billing Status","description":"Return current subscription plan and status for the account.","operationId":"billing_status_api_billing_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/billing/checkout":{"post":{"summary":"Billing Checkout","description":"Create a Stripe Checkout session for plan upgrade.\n\nBody: { \"plan\": \"pro\" | \"enterprise\", \"success_url\": \"...\", \"cancel_url\": \"...\" }\nReturns: { \"url\": \"https://checkout.stripe.com/...\" }","operationId":"billing_checkout_api_billing_checkout_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/billing/portal":{"post":{"summary":"Billing Portal","description":"Create a Stripe Customer Portal session for subscription management.\n\nBody (optional): { \"return_url\": \"...\" }\nReturns: { \"url\": \"https://billing.stripe.com/...\" }","operationId":"billing_portal_api_billing_portal_post","requestBody":{"content":{"application/json":{"schema":{"additionalProperties":true,"type":"object","title":"Body","default":{}}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/og-image.png":{"get":{"summary":"Serve Og Image","description":"Open Graph social sharing image (legacy generic fallback).","operationId":"serve_og_image_og_image_png_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/favicon.ico":{"get":{"summary":"Serve Favicon Ico","description":"Historical fallback path. Multi-resolution ICO so Google's\ncrawler, legacy clients, and Outlook all get the right icon.","operationId":"serve_favicon_ico_favicon_ico_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Serve Favicon Ico","description":"Historical fallback path. Multi-resolution ICO so Google's\ncrawler, legacy clients, and Outlook all get the right icon.","operationId":"serve_favicon_ico_favicon_ico_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/favicon.svg":{"get":{"summary":"Serve Favicon Svg","operationId":"serve_favicon_svg_favicon_svg_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Serve Favicon Svg","operationId":"serve_favicon_svg_favicon_svg_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/favicon-16x16.png":{"get":{"summary":"Serve Favicon 16","operationId":"serve_favicon_16_favicon_16x16_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Serve Favicon 16","operationId":"serve_favicon_16_favicon_16x16_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/favicon-32x32.png":{"get":{"summary":"Serve Favicon 32","operationId":"serve_favicon_32_favicon_32x32_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Serve Favicon 32","operationId":"serve_favicon_32_favicon_32x32_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/favicon-48x48.png":{"get":{"summary":"Serve Favicon 48","operationId":"serve_favicon_48_favicon_48x48_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Serve Favicon 48","operationId":"serve_favicon_48_favicon_48x48_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/favicon-192x192.png":{"get":{"summary":"Serve Favicon 192","operationId":"serve_favicon_192_favicon_192x192_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Serve Favicon 192","operationId":"serve_favicon_192_favicon_192x192_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/favicon-512x512.png":{"get":{"summary":"Serve Favicon 512","operationId":"serve_favicon_512_favicon_512x512_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Serve Favicon 512","operationId":"serve_favicon_512_favicon_512x512_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/apple-touch-icon.png":{"get":{"summary":"Serve Apple Touch Icon Png","operationId":"serve_apple_touch_icon_png_apple_touch_icon_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Serve Apple Touch Icon Png","operationId":"serve_apple_touch_icon_png_apple_touch_icon_png_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/apple-touch-icon.svg":{"get":{"summary":"Serve Apple Touch Icon Svg","operationId":"serve_apple_touch_icon_svg_apple_touch_icon_svg_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Serve Apple Touch Icon Svg","operationId":"serve_apple_touch_icon_svg_apple_touch_icon_svg_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/site.webmanifest":{"get":{"summary":"Serve Webmanifest","description":"PWA manifest. Google reads `icons` from here as an additional\nsignal for the search-result favicon.","operationId":"serve_webmanifest_site_webmanifest_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"head":{"summary":"Serve Webmanifest","description":"PWA manifest. Google reads `icons` from here as an additional\nsignal for the search-result favicon.","operationId":"serve_webmanifest_site_webmanifest_head","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/og/{slug}.png":{"get":{"summary":"Serve Per Page Og","description":"Per-page OG image rendered with PIL. Caches in-memory after\nfirst request via the font cache; the PNG itself is small enough\n(~25-30KB) that re-rendering is faster than disk caching.\n\nUnknown slugs fall back to the static generic og-image.png\nso social previews never break for a missing slug.","operationId":"serve_per_page_og_og__slug__png_get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/og/share/{token}":{"get":{"summary":"Serve Share Og Image","description":"Dynamic OG image for a share link — shows P&L, win rate, trade count.","operationId":"serve_share_og_image_og_share__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/":{"get":{"summary":"Serve Landing","description":"Marketing landing page at root.","operationId":"serve_landing__get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/v1":{"get":{"summary":"Serve Landing V1","description":"Archived previous homepage (homepage-v1 tag) — unlisted route\nfor side-by-side comparison while iterating on the new landing.\nSafe to remove once we're confident in the new design.","operationId":"serve_landing_v1_v1_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/app":{"get":{"summary":"Serve App","description":"Dashboard app (behind auth).","operationId":"serve_app_app_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/share/{token}":{"get":{"summary":"Serve Share Page","description":"Public share page with dynamic OG tags for social previews.","operationId":"serve_share_page_share__token__get","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string","title":"Token"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/admin":{"get":{"summary":"Serve Admin","description":"Admin portal.","operationId":"serve_admin_admin_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/robots.txt":{"get":{"summary":"Serve Robots","operationId":"serve_robots_robots_txt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/pp-analytics.js":{"get":{"summary":"Serve Analytics Js","description":"Shared analytics bootstrap (PostHog + UTM capture) loaded by\nevery static HTML page outside the SPA. Without this, blog and\nmarketing pages get no tracking — a user landing on a blog post\nvia Reddit and then signing up loses all source attribution.","operationId":"serve_analytics_js_pp_analytics_js_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/sitemap.xml":{"get":{"summary":"Serve Sitemap","description":"Dynamic sitemap. Reads the blog_posts table at request time\nso AI-generated weekly posts land in Googlebot's index without\na manual XML edit (which was the previous failure mode — static\ndashboard/public/sitemap.xml went stale for days after each\nweekly publish).","operationId":"serve_sitemap_sitemap_xml_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/blog":{"get":{"summary":"Serve Blog Index","description":"Blog index page.","operationId":"serve_blog_index_blog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/blog/weekly/{slug}":{"get":{"summary":"Serve Weekly Blog","description":"Auto-generated weekly strategy blog post (served from DB).","operationId":"serve_weekly_blog_blog_weekly__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/blog/{slug}":{"get":{"summary":"Serve Blog Post","description":"Individual blog post.","operationId":"serve_blog_post_blog__slug__get","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string","title":"Slug"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/faq":{"get":{"summary":"Serve Faq","description":"FAQ page with FAQPage JSON-LD — the format LLM crawlers cite from.","operationId":"serve_faq_faq_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/markets":{"get":{"summary":"Serve Markets Index","description":"Index of all programmatic /markets/{category} guides.\nServer-rendered HTML — no DB query needed for the listing\nitself, but uses get_db so it's consistent with the per-page\nrenderers.","operationId":"serve_markets_index_markets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/markets/{category}":{"get":{"summary":"Serve Markets Category","description":"Per-category landing page. Live-data section queries\nmarket_snapshots; falls back to graceful empty state.","operationId":"serve_markets_category_markets__category__get","parameters":[{"name":"category","in":"path","required":true,"schema":{"type":"string","title":"Category"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/markets/{category}/{subcategory}":{"get":{"summary":"Serve Markets Subcategory","description":"Sub-page: /markets/sports/nba, /markets/weather/temperature, etc.","operationId":"serve_markets_subcategory_markets__category___subcategory__get","parameters":[{"name":"category","in":"path","required":true,"schema":{"type":"string","title":"Category"}},{"name":"subcategory","in":"path","required":true,"schema":{"type":"string","title":"Subcategory"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/how-it-works":{"get":{"summary":"Serve How It Works","description":"Long-form walkthrough of how Prediction Pilot works.","operationId":"serve_how_it_works_how_it_works_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/pricing":{"get":{"summary":"Serve Pricing","description":"Pricing page with Product+Offer JSON-LD.","operationId":"serve_pricing_pricing_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/changelog":{"get":{"summary":"Serve Changelog","description":"Changelog of recent shipments. LLMs cite the freshest dated\ncontent when answering 'what's new with X' — this catches that\nintent and gives a stable URL to maintain.","operationId":"serve_changelog_changelog_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/llms.txt":{"get":{"summary":"Serve Llms Txt","description":"LLM-crawler guidance — Anthropic-proposed convention.","operationId":"serve_llms_txt_llms_txt_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/terms":{"get":{"summary":"Serve Terms","operationId":"serve_terms_terms_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/privacy":{"get":{"summary":"Serve Privacy","operationId":"serve_privacy_privacy_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/unsubscribe":{"get":{"summary":"Serve Unsubscribe","operationId":"serve_unsubscribe_unsubscribe_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}}},"components":{"schemas":{"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"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"}}}}