{
  "openapi": "3.0.3",
  "info": {
    "title": "Finica API",
    "version": "1.0.0",
    "description": "Finica public and internal API endpoints. Korean + US macro economic indicators, news, calendar, geopolitical events, and AI agent tools.",
    "contact": {
      "name": "Finica by rovn",
      "url": "https://finica.io"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://finica.io",
      "description": "Production"
    }
  ],
  "paths": {
    "/api/dashboard": {
      "get": {
        "operationId": "getDashboard",
        "summary": "Dashboard summary",
        "description": "Returns all Tier 1 macro indicators with current values, sparkline data, and change percentages. Uses KV cache with ISR (60s revalidation).",
        "tags": ["Public"],
        "responses": {
          "200": {
            "description": "Dashboard data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "indicators": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": { "type": "string", "example": "sp500" },
                          "value": { "type": "number", "example": 5234.56 },
                          "change": { "type": "number", "example": 1.23 },
                          "sparkline": { "type": "array", "items": { "type": "number" } }
                        }
                      }
                    },
                    "updatedAt": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Failed to fetch live data"
          }
        }
      }
    },
    "/api/indicators/{id}": {
      "get": {
        "operationId": "getIndicator",
        "summary": "Indicator detail + time series",
        "description": "Returns historical data points and OHLC candles for a specific indicator. Supports FRED, ECOS, Yahoo Finance, Coinbase, CNN Fear & Greed, and more. Includes AI hints with related indicators and interpretation.",
        "tags": ["Public"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "Indicator ID (e.g. sp500, btc, vix, fed-rate, usd-krw, kospi, cpi, us10y, etc.)"
          }
        ],
        "responses": {
          "200": {
            "description": "Indicator data with time series and OHLC",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": { "type": "string" },
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "date": { "type": "string" },
                          "value": { "type": "number" }
                        }
                      }
                    },
                    "ohlc": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "date": { "type": "string" },
                          "open": { "type": "number" },
                          "high": { "type": "number" },
                          "low": { "type": "number" },
                          "close": { "type": "number" }
                        }
                      }
                    },
                    "updatedAt": { "type": "string", "format": "date-time" },
                    "_hints": {
                      "type": "object",
                      "properties": {
                        "category": { "type": "string" },
                        "related": { "type": "array", "items": { "type": "object" } },
                        "interpretation": { "type": "string" },
                        "affects": { "type": "array", "items": { "type": "string" } }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": { "description": "Unknown indicator ID" },
          "500": { "description": "Failed to fetch data" }
        }
      }
    },
    "/api/fear-greed": {
      "get": {
        "operationId": "getFearGreed",
        "summary": "CNN Fear & Greed Index",
        "description": "Returns current Fear & Greed score, previous close, 30-day history, and 7 sub-component scores. ISR revalidation every 5 minutes.",
        "tags": ["Public"],
        "responses": {
          "200": {
            "description": "Fear & Greed data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "score": { "type": "integer", "example": 42 },
                    "previousClose": { "type": "integer", "example": 45 },
                    "historical": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "date": { "type": "string" },
                          "value": { "type": "integer" }
                        }
                      }
                    },
                    "components": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": { "type": "string" },
                          "nameKo": { "type": "string" },
                          "value": { "type": "integer" }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "500": { "description": "Internal server error" }
        }
      }
    },
    "/api/calendar": {
      "get": {
        "operationId": "getCalendar",
        "summary": "Economic calendar",
        "description": "Returns upcoming and recent economic events from ForexFactory and Finnhub, enriched with FRED actual values. Supports US and KR events.",
        "tags": ["Public"],
        "parameters": [
          {
            "name": "minImportance",
            "in": "query",
            "schema": { "type": "integer", "default": 2, "enum": [1, 2, 3] },
            "description": "Minimum importance filter (1=low, 2=medium, 3=high)"
          },
          {
            "name": "refresh",
            "in": "query",
            "schema": { "type": "string", "enum": ["0", "1"] },
            "description": "Set to 1 to bypass cache"
          },
          {
            "name": "debug",
            "in": "query",
            "schema": { "type": "string", "enum": ["0", "1"] },
            "description": "Set to 1 to include debug info"
          }
        ],
        "responses": {
          "200": {
            "description": "Calendar events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": { "type": "string" },
                          "date": { "type": "string" },
                          "nameKo": { "type": "string" },
                          "nameEn": { "type": "string" },
                          "importance": { "type": "integer" },
                          "previous": { "type": "string" },
                          "forecast": { "type": "string" },
                          "actual": { "type": "string" },
                          "country": { "type": "string", "enum": ["US", "KR"] }
                        }
                      }
                    },
                    "updatedAt": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "500": { "description": "Failed to fetch calendar data" }
        }
      }
    },
    "/api/news": {
      "get": {
        "operationId": "getNews",
        "summary": "News feed",
        "description": "Returns aggregated economic news from RSS sources. Supports filtering by category and source.",
        "tags": ["Public"],
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Category filter (e.g. markets, economy, crypto, general)"
          },
          {
            "name": "source",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Source filter (source ID)"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "default": 50, "maximum": 100 },
            "description": "Max number of items to return"
          }
        ],
        "responses": {
          "200": {
            "description": "News items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "items": { "type": "array", "items": { "type": "object" } },
                    "count": { "type": "integer" }
                  }
                }
              }
            }
          },
          "500": { "description": "Failed to fetch news" }
        }
      }
    },
    "/api/briefing": {
      "get": {
        "operationId": "getBriefing",
        "summary": "AI market briefing",
        "description": "Returns cached AI-generated market briefing with big movers, next event, and scenario analysis. Generated by cron, falls back to template.",
        "tags": ["Public"],
        "parameters": [
          {
            "name": "locale",
            "in": "query",
            "schema": { "type": "string", "enum": ["ko", "en"], "default": "ko" },
            "description": "Language"
          }
        ],
        "responses": {
          "200": {
            "description": "Briefing data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "bigMovers": { "type": "array", "items": { "type": "object" } },
                    "nextEvent": { "type": "object", "nullable": true },
                    "bullets": { "type": "array", "items": { "type": "string" } },
                    "scenario": { "type": "object", "nullable": true },
                    "generatedAt": { "type": "string", "format": "date-time" },
                    "source": { "type": "string" },
                    "session": { "type": "string" }
                  }
                }
              }
            }
          },
          "503": { "description": "No data available" },
          "500": { "description": "Failed to fetch data" }
        }
      }
    },
    "/api/realtime": {
      "get": {
        "operationId": "getRealtime",
        "summary": "Real-time price stream (SSE)",
        "description": "Server-Sent Events endpoint for real-time price updates via Finnhub WebSocket. Relays trades for SPY, QQQ, AAPL, BTC, ETH.",
        "tags": ["Public"],
        "responses": {
          "200": {
            "description": "SSE stream of price updates",
            "content": {
              "text/event-stream": {
                "schema": { "type": "string" }
              }
            }
          }
        }
      }
    },
    "/api/event-impacts": {
      "get": {
        "operationId": "getEventImpacts",
        "summary": "Event impact analysis",
        "description": "Returns historical market reactions to economic events (e.g. CPI, NFP). Shows mean/median/stddev of asset changes after surprise releases.",
        "tags": ["Public"],
        "parameters": [
          {
            "name": "eventType",
            "in": "query",
            "required": true,
            "schema": { "type": "string" },
            "description": "Event type (e.g. CPI, NFP, FOMC, PPI, GDP)"
          },
          {
            "name": "direction",
            "in": "query",
            "schema": { "type": "string", "default": "any" },
            "description": "Surprise direction filter (up, down, any)"
          }
        ],
        "responses": {
          "200": {
            "description": "Event impact data with reactions and recent events"
          },
          "400": { "description": "Missing or unknown eventType" },
          "500": { "description": "Failed to fetch event impact data" }
        }
      }
    },
    "/api/event-impacts/stats": {
      "get": {
        "operationId": "getEventImpactStats",
        "summary": "Event impact statistics summary",
        "description": "Returns aggregate counts and all tracked event-indicator pairs with sample counts and multipliers.",
        "tags": ["Public"],
        "responses": {
          "200": {
            "description": "Summary statistics"
          },
          "500": { "description": "Failed to fetch stats" }
        }
      }
    },
    "/api/world/events": {
      "get": {
        "operationId": "getWorldEvents",
        "summary": "Geopolitical events",
        "description": "Returns geopolitical events from the world_events table. Supports filtering by event type, severity, date range, and pagination.",
        "tags": ["Public"],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "schema": { "type": "string", "enum": ["military", "trade", "energy", "political", "technology", "humanitarian"] },
            "description": "Event type filter"
          },
          {
            "name": "severity_min",
            "in": "query",
            "schema": { "type": "integer", "default": 0 },
            "description": "Minimum severity threshold (0-100)"
          },
          {
            "name": "days",
            "in": "query",
            "schema": { "type": "integer", "default": 7, "maximum": 90 },
            "description": "How many days back to look"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": { "type": "integer", "default": 30, "maximum": 100 },
            "description": "Max results"
          },
          {
            "name": "offset",
            "in": "query",
            "schema": { "type": "integer", "default": 0 },
            "description": "Pagination offset"
          }
        ],
        "responses": {
          "200": {
            "description": "Geopolitical events with pagination info"
          },
          "400": { "description": "Invalid event type" },
          "500": { "description": "Failed to fetch events" }
        }
      }
    },
    "/api/world/force-map": {
      "get": {
        "operationId": "getForceMap",
        "summary": "Geopolitical force map",
        "description": "Returns active geopolitical issues with escalation/restraint forces, strength scores, and balance percentages.",
        "tags": ["Public"],
        "parameters": [
          {
            "name": "issue",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Specific issue ID (e.g. us-cn)"
          }
        ],
        "responses": {
          "200": {
            "description": "Force map issues with forces"
          },
          "500": { "description": "Failed to fetch force map" }
        }
      }
    },
    "/api/comments": {
      "get": {
        "operationId": "getComments",
        "summary": "List comments",
        "description": "Returns comments, optionally filtered by target type and ID.",
        "tags": ["Public"],
        "parameters": [
          {
            "name": "targetType",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Comment target type (e.g. news, indicator)"
          },
          {
            "name": "targetId",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Comment target ID"
          }
        ],
        "responses": {
          "200": { "description": "List of comments" }
        }
      },
      "post": {
        "operationId": "createComment",
        "summary": "Create comment",
        "description": "Creates a new comment. Requires authentication.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "targetType": { "type": "string" },
                  "targetId": { "type": "string" },
                  "targetLabel": { "type": "string" },
                  "content": { "type": "string", "maxLength": 2000 }
                },
                "required": ["content"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Created comment" },
          "401": { "description": "Unauthorized" },
          "400": { "description": "Invalid content" }
        }
      }
    },
    "/api/comments/{id}": {
      "delete": {
        "operationId": "deleteComment",
        "summary": "Delete comment (admin)",
        "description": "Deletes a comment by ID. Admin only.",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": { "description": "Deleted" },
          "403": { "description": "Forbidden" },
          "404": { "description": "Not found" }
        }
      }
    },
    "/api/user/preferences": {
      "get": {
        "operationId": "getUserPreferences",
        "summary": "Get card preferences",
        "description": "Returns the user's dashboard card configuration (visible cards, wide cards).",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "User card config" },
          "401": { "description": "Unauthorized" }
        }
      },
      "put": {
        "operationId": "saveUserPreferences",
        "summary": "Save card preferences",
        "description": "Saves dashboard card configuration for the logged-in user.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "visible": { "type": "array", "items": { "type": "string" } },
                  "wide": { "type": "array", "items": { "type": "string" } }
                },
                "required": ["visible", "wide"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Saved" },
          "401": { "description": "Unauthorized" },
          "400": { "description": "Invalid config" }
        }
      }
    },
    "/api/user/drawings": {
      "get": {
        "operationId": "getUserDrawings",
        "summary": "Get chart drawings",
        "description": "Returns saved chart drawings (lines, trendlines) for a specific indicator.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "parameters": [
          {
            "name": "indicator",
            "in": "query",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": { "description": "Drawings array" },
          "401": { "description": "Unauthorized" },
          "400": { "description": "Missing indicator param" }
        }
      },
      "put": {
        "operationId": "saveUserDrawings",
        "summary": "Save chart drawings",
        "description": "Persists chart drawings for a specific indicator per user.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "parameters": [
          {
            "name": "indicator",
            "in": "query",
            "required": true,
            "schema": { "type": "string" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "drawings": { "type": "array", "items": { "type": "object" } }
                },
                "required": ["drawings"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Saved" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/user/alerts": {
      "get": {
        "operationId": "getUserAlerts",
        "summary": "Get alert rules",
        "description": "Returns the user's macro alert configuration.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Alert rules" },
          "401": { "description": "Unauthorized" }
        }
      },
      "put": {
        "operationId": "saveUserAlerts",
        "summary": "Save alert rules",
        "description": "Saves alert rules. Each rule specifies an indicator, condition (> or <), threshold, and enabled status.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "alerts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": { "type": "string" },
                        "indicator": { "type": "string" },
                        "condition": { "type": "string", "enum": [">", "<"] },
                        "threshold": { "type": "number" },
                        "enabled": { "type": "boolean" }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Saved" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/user/portfolio": {
      "get": {
        "operationId": "getUserPortfolio",
        "summary": "Get portfolio holdings",
        "description": "Returns the user's saved portfolio holdings.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Portfolio holdings" },
          "401": { "description": "Unauthorized" }
        }
      },
      "put": {
        "operationId": "saveUserPortfolio",
        "summary": "Save portfolio holdings",
        "description": "Saves portfolio holdings with name, category, and weight.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "holdings": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": { "type": "string" },
                        "category": { "type": "string", "enum": ["us-equity", "kr-equity", "crypto", "bond", "commodity", "cash", "realestate"] },
                        "weight": { "type": "number" }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Saved" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/portfolio/risk": {
      "post": {
        "operationId": "computePortfolioRisk",
        "summary": "Portfolio risk analysis",
        "description": "Computes quantitative risk metrics (VaR, CVaR, Sharpe, Sortino, Beta, correlation matrix) for a given portfolio using historical price data.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "holdings": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": { "type": "string" },
                        "category": { "type": "string" }
                      }
                    }
                  }
                },
                "required": ["holdings"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Risk report" },
          "401": { "description": "Unauthorized" },
          "400": { "description": "No holdings provided" },
          "500": { "description": "Risk computation failed" }
        }
      }
    },
    "/api/billing/status": {
      "get": {
        "operationId": "getBillingStatus",
        "summary": "Subscription status",
        "description": "Returns current user's subscription plan, premium status, and expiry date.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Subscription status" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/billing/subscribe": {
      "post": {
        "operationId": "subscribe",
        "summary": "Subscribe to Pro",
        "description": "Processes payment via PortOne billing key and activates Pro subscription (monthly 9,900 KRW or yearly 99,000 KRW).",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "billingKey": { "type": "string" },
                  "plan": { "type": "string", "enum": ["monthly", "yearly"] }
                },
                "required": ["billingKey", "plan"]
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Subscription activated" },
          "401": { "description": "Unauthorized" },
          "400": { "description": "Invalid request or payment failed" },
          "503": { "description": "Payment not configured" }
        }
      }
    },
    "/api/billing/cancel": {
      "post": {
        "operationId": "cancelSubscription",
        "summary": "Cancel subscription",
        "description": "Cancels the user's premium subscription. Revokes PortOne billing key. Premium access remains until current period ends.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Cancelled" },
          "401": { "description": "Unauthorized" },
          "400": { "description": "No active subscription" },
          "500": { "description": "Cancellation failed" }
        }
      }
    },
    "/api/billing/webhook": {
      "post": {
        "operationId": "billingWebhook",
        "summary": "PortOne payment webhook",
        "description": "Handles recurring payment events from PortOne (Transaction.Paid, Transaction.Failed). Verifies HMAC-SHA256 signature.",
        "tags": ["Webhook"],
        "responses": {
          "200": { "description": "Processed" },
          "401": { "description": "Invalid or missing signature" },
          "503": { "description": "Webhook not configured" }
        }
      }
    },
    "/api/push/subscribe": {
      "post": {
        "operationId": "pushSubscribe",
        "summary": "Save push subscription",
        "description": "Saves a Web Push subscription endpoint for the authenticated user.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Saved" },
          "401": { "description": "Unauthorized" },
          "400": { "description": "Invalid subscription" }
        }
      },
      "delete": {
        "operationId": "pushUnsubscribe",
        "summary": "Remove push subscription",
        "description": "Removes the user's Web Push subscription.",
        "tags": ["Auth Required"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Removed" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/auth/track": {
      "post": {
        "operationId": "trackVisit",
        "summary": "Track page visit",
        "description": "Records a page view (anonymous) and user visit (authenticated). Used for analytics.",
        "tags": ["Internal"],
        "responses": {
          "200": { "description": "Tracked" },
          "500": { "description": "Error" }
        }
      }
    },
    "/api/mcp": {
      "get": {
        "operationId": "mcpTool",
        "summary": "AI agent tool endpoint",
        "description": "MCP-compatible JSON API for AI agent consumption. Returns structured macro data via ?tool= query parameter. Rate limited to 30 req/min.",
        "tags": ["AI Agent"],
        "parameters": [
          {
            "name": "tool",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": ["dashboard", "macro-analysis", "geopolitical-risk", "sectors", "portfolio-stress"]
            },
            "description": "Tool to invoke"
          },
          {
            "name": "sector",
            "in": "query",
            "schema": { "type": "string", "enum": ["ai", "energy", "space"] },
            "description": "Sector filter (for tool=sectors)"
          },
          {
            "name": "scenario",
            "in": "query",
            "schema": { "type": "string" },
            "description": "Scenario ID (for tool=portfolio-stress, e.g. covid-2020, rate-hike-2022, gfc-2008)"
          }
        ],
        "responses": {
          "200": {
            "description": "Tool response with _hints metadata"
          },
          "400": { "description": "Missing or invalid tool parameter" },
          "429": { "description": "Rate limit exceeded" },
          "500": { "description": "Internal error" }
        }
      }
    },
    "/api/admin/users": {
      "get": {
        "operationId": "adminGetUsers",
        "summary": "List users (admin)",
        "description": "Returns all registered users. Admin only.",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "User list" },
          "403": { "description": "Forbidden" }
        }
      },
      "patch": {
        "operationId": "adminUpdateUser",
        "summary": "Update user (admin)",
        "description": "Change user role or block status. Admin only.",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Updated" },
          "403": { "description": "Forbidden" },
          "400": { "description": "Invalid request" }
        }
      }
    },
    "/api/admin/stats": {
      "get": {
        "operationId": "adminGetStats",
        "summary": "Admin statistics",
        "description": "Returns user counts, daily signups, daily logins, and top pages. Admin only.",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Stats data" },
          "403": { "description": "Forbidden" }
        }
      }
    },
    "/api/admin/status": {
      "get": {
        "operationId": "adminGetStatus",
        "summary": "System status (admin)",
        "description": "Returns environment variable status, DB connection, and indicator data health. Admin only.",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Status data" },
          "403": { "description": "Forbidden" }
        }
      }
    },
    "/api/admin/ops": {
      "get": {
        "operationId": "adminGetOps",
        "summary": "Operations dashboard (admin)",
        "description": "Returns subscriber counts, premium users, cron status, world events summary. Admin only.",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Operations data" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/admin/migrate-kv": {
      "post": {
        "operationId": "adminMigrateKv",
        "summary": "Migrate KV to Postgres (admin)",
        "description": "One-time migration of users and page views from Vercel KV to Postgres. Admin only.",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Migration result" },
          "403": { "description": "Forbidden" }
        }
      }
    },
    "/api/admin/force-map": {
      "get": {
        "operationId": "adminGetForceMap",
        "summary": "List all force map issues (admin)",
        "description": "Returns all geopolitical issues with forces, including inactive ones. Admin only.",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "All issues with forces" },
          "403": { "description": "Forbidden" }
        }
      },
      "post": {
        "operationId": "adminCreateIssue",
        "summary": "Create force map issue (admin)",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Created" },
          "403": { "description": "Forbidden" },
          "400": { "description": "Missing required fields" }
        }
      },
      "put": {
        "operationId": "adminUpdateIssue",
        "summary": "Update force map issue (admin)",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Updated" },
          "403": { "description": "Forbidden" }
        }
      }
    },
    "/api/admin/force-map/forces": {
      "post": {
        "operationId": "adminAddForce",
        "summary": "Add force to issue (admin)",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Added" },
          "403": { "description": "Forbidden" }
        }
      },
      "put": {
        "operationId": "adminUpdateForce",
        "summary": "Update force (admin)",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Updated" },
          "403": { "description": "Forbidden" }
        }
      },
      "delete": {
        "operationId": "adminDeleteForce",
        "summary": "Delete force (admin)",
        "tags": ["Admin"],
        "security": [{ "session": [] }],
        "responses": {
          "200": { "description": "Deleted" },
          "403": { "description": "Forbidden" }
        }
      }
    },
    "/api/cron/dashboard": {
      "get": {
        "operationId": "cronDashboard",
        "summary": "Cron: refresh dashboard cache",
        "description": "Fetches all dashboard indicators and caches in KV. Requires CRON_SECRET bearer token.",
        "tags": ["Cron"],
        "security": [{ "cronSecret": [] }],
        "responses": {
          "200": { "description": "Refreshed" },
          "401": { "description": "Unauthorized" }
        }
      },
      "post": {
        "operationId": "cronDashboardPost",
        "summary": "Cron: refresh dashboard cache (POST)",
        "tags": ["Cron"],
        "security": [{ "cronSecret": [] }],
        "responses": {
          "200": { "description": "Refreshed" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/cron/briefing": {
      "get": {
        "operationId": "cronBriefing",
        "summary": "Cron: generate AI briefing",
        "description": "Generates AI market briefing, stores in KV, sends personalized Telegram briefings to premium subscribers.",
        "tags": ["Cron"],
        "security": [{ "cronSecret": [] }],
        "responses": {
          "200": { "description": "Generated" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/cron/check-alerts": {
      "get": {
        "operationId": "cronCheckAlerts",
        "summary": "Cron: check macro alerts",
        "description": "Checks all user alert rules against current indicator values and sends notifications for triggered alerts.",
        "tags": ["Cron"],
        "security": [{ "cronSecret": [] }],
        "responses": {
          "200": { "description": "Checked" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/cron/daily-summary": {
      "get": {
        "operationId": "cronDailySummary",
        "summary": "Cron: send daily summary",
        "description": "Sends daily market summary to Telegram subscribers.",
        "tags": ["Cron"],
        "security": [{ "cronSecret": [] }],
        "responses": {
          "200": { "description": "Sent" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/cron/event-impact": {
      "get": {
        "operationId": "cronEventImpact",
        "summary": "Cron: compute event impacts",
        "description": "Computes historical market reactions to economic event releases and stores statistics.",
        "tags": ["Cron"],
        "security": [{ "cronSecret": [] }],
        "responses": {
          "200": { "description": "Computed" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/cron/world-events": {
      "get": {
        "operationId": "cronWorldEvents",
        "summary": "Cron: fetch world events",
        "description": "Fetches and processes geopolitical events from news sources using AI.",
        "tags": ["Cron"],
        "security": [{ "cronSecret": [] }],
        "responses": {
          "200": { "description": "Fetched" },
          "401": { "description": "Unauthorized" }
        }
      }
    },
    "/api/telegram/webhook": {
      "post": {
        "operationId": "telegramWebhook",
        "summary": "Telegram bot webhook",
        "description": "Handles incoming Telegram bot messages. Supports /start, /stop, /alert, /portfolio, /briefing commands.",
        "tags": ["Webhook"],
        "responses": {
          "200": { "description": "Processed" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "session": {
        "type": "apiKey",
        "in": "cookie",
        "name": "next-auth.session-token",
        "description": "NextAuth.js session cookie (Google OAuth)"
      },
      "cronSecret": {
        "type": "http",
        "scheme": "bearer",
        "description": "CRON_SECRET environment variable as Bearer token"
      }
    }
  },
  "tags": [
    { "name": "Public", "description": "Publicly accessible endpoints, no authentication required" },
    { "name": "Auth Required", "description": "Requires user authentication via Google OAuth session" },
    { "name": "AI Agent", "description": "MCP-compatible endpoints designed for AI agent consumption" },
    { "name": "Admin", "description": "Admin-only endpoints, requires admin email" },
    { "name": "Cron", "description": "Cron job endpoints, requires CRON_SECRET bearer token" },
    { "name": "Webhook", "description": "External service webhook handlers" },
    { "name": "Internal", "description": "Internal tracking endpoints" }
  ]
}
