{
  "components": {
    "responses": {
      "BadRequest": {
        "content": {
          "application/json": {
            "example": {
              "error": "symbol is required"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Missing or malformed parameter."
      },
      "Conflict": {
        "content": {
          "application/json": {
            "example": {
              "error": "alert rule limit exceeded"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Rule limit exceeded, duplicate, or conflicting state."
      },
      "Forbidden": {
        "content": {
          "application/json": {
            "example": {
              "error": "pro access required"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Authenticated but not Pro/Admin on a Pro-only endpoint, or no verified email for alerts."
      },
      "NotFound": {
        "content": {
          "application/json": {
            "example": {
              "error": "not found"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "No such resource, or not owned by the caller."
      },
      "RateLimited": {
        "content": {
          "application/json": {
            "example": {
              "error": "rate limit exceeded"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Per-IP or per-user rate budget exhausted. Back off exponentially."
      },
      "Unauthorized": {
        "content": {
          "application/json": {
            "example": {
              "error": "unauthorized"
            },
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Missing or invalid bearer token / session cookie."
      }
    },
    "schemas": {
      "AIInsight": {
        "properties": {
          "aboveSMA20": {
            "example": true,
            "type": "boolean"
          },
          "aboveSMA50": {
            "example": true,
            "type": "boolean"
          },
          "analysis": {
            "example": "Constructive setup: price holds above both moving averages…",
            "type": "string"
          },
          "atr": {
            "example": 0.084,
            "type": "number"
          },
          "confidence": {
            "example": 78,
            "maximum": 100,
            "minimum": 0,
            "type": "integer"
          },
          "fairValue": {
            "example": 1.43,
            "type": "number"
          },
          "rsi": {
            "example": 62.4,
            "type": "number"
          },
          "sampleSize": {
            "example": 62,
            "type": "integer"
          },
          "sentiment": {
            "example": 0.71,
            "type": "number"
          },
          "sentimentLabel": {
            "example": "Positive",
            "type": "string"
          },
          "signal": {
            "enum": [
              "BULLISH",
              "BEARISH",
              "NEUTRAL"
            ],
            "example": "BULLISH",
            "type": "string"
          },
          "suggestedPrice": {
            "example": 1.48,
            "type": "number"
          },
          "symbol": {
            "example": "MTNGH",
            "type": "string"
          }
        },
        "required": [
          "symbol",
          "signal"
        ],
        "type": "object"
      },
      "APIKey": {
        "properties": {
          "createdAt": {
            "example": "2026-03-22T10:12:00Z",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "example": 3,
            "format": "int64",
            "type": "integer"
          },
          "lastUsedAt": {
            "example": "2026-04-14T09:40:00Z",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "name": {
            "example": "my-laptop",
            "type": "string"
          },
          "prefix": {
            "description": "First few characters of the key",
            "example": "ges_live_aZ8k",
            "shown for recognition.": null,
            "type": "string"
          }
        },
        "required": [
          "id",
          "prefix"
        ],
        "type": "object"
      },
      "APIKeyWithSecret": {
        "allOf": [
          {
            "$ref": "#/components/schemas/APIKey"
          },
          {
            "properties": {
              "key": {
                "description": "The raw secret. Returned **only** from this endpoint, **only** at creation time.",
                "example": "ges_live_qR4v9n8xK2pLm1dC5wY3zH7vE6tB0sJ4uA8iO",
                "type": "string"
              }
            },
            "required": [
              "key"
            ],
            "type": "object"
          }
        ]
      },
      "AlertRule": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AlertRuleInput"
          },
          {
            "properties": {
              "enabled": {
                "example": false,
                "type": "boolean"
              },
              "fireCount": {
                "example": 1,
                "type": "integer"
              },
              "id": {
                "example": 142,
                "format": "int64",
                "type": "integer"
              },
              "lastFiredAt": {
                "example": "2026-04-14T15:31:02Z",
                "format": "date-time",
                "nullable": true,
                "type": "string"
              },
              "userId": {
                "example": 9,
                "format": "int64",
                "type": "integer"
              }
            },
            "required": [
              "id",
              "userId",
              "enabled",
              "fireCount"
            ],
            "type": "object"
          }
        ]
      },
      "AlertRuleInput": {
        "properties": {
          "metric": {
            "enum": [
              "price",
              "rsi",
              "pct_change"
            ],
            "example": "price",
            "type": "string"
          },
          "op": {
            "enum": [
              "\u003e",
              "\u003c",
              "\u003e=",
              "\u003c="
            ],
            "example": "\u003e",
            "type": "string"
          },
          "symbol": {
            "example": "MTNGH",
            "type": "string"
          },
          "threshold": {
            "example": 1.5,
            "type": "number"
          }
        },
        "required": [
          "symbol",
          "metric",
          "op",
          "threshold"
        ],
        "type": "object"
      },
      "Briefing": {
        "properties": {
          "averageSentiment": {
            "example": 0.31,
            "type": "number"
          },
          "insights": {
            "items": {
              "properties": {
                "rsi": {
                  "type": "number"
                },
                "sentiment": {
                  "type": "number"
                },
                "symbol": {
                  "type": "string"
                },
                "verdict": {
                  "enum": [
                    "Oversold",
                    "Bearish",
                    "Neutral",
                    "Bullish",
                    "Overbought",
                    "Extreme overbought"
                  ],
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "summary": {
            "example": "Financials extended a three-session advance as MTNGH tagged a fresh YTD high…",
            "nullable": true,
            "type": "string"
          },
          "tradingDate": {
            "example": "2026-04-14",
            "format": "date",
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "Error": {
        "properties": {
          "error": {
            "description": "Human-readable error message.",
            "type": "string"
          }
        },
        "required": [
          "error"
        ],
        "type": "object"
      },
      "MarketSnapshot": {
        "properties": {
          "lastPrice": {
            "example": 1.54,
            "type": "number"
          },
          "openPrice": {
            "example": 1.5,
            "type": "number"
          },
          "percentChange": {
            "example": 2.67,
            "type": "number"
          },
          "priceChange": {
            "example": 0.04,
            "type": "number"
          },
          "symbol": {
            "example": "MTNGH",
            "type": "string"
          },
          "volume": {
            "example": 211800,
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "symbol",
          "lastPrice"
        ],
        "type": "object"
      },
      "MeResponse": {
        "properties": {
          "availableProviders": {
            "items": {
              "properties": {
                "displayName": {
                  "example": "Google",
                  "type": "string"
                },
                "name": {
                  "example": "google",
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "email": {
            "example": "trader@example.com",
            "format": "email",
            "type": "string"
          },
          "emailVerified": {
            "example": true,
            "type": "boolean"
          },
          "hasPassword": {
            "example": true,
            "type": "boolean"
          },
          "isAdmin": {
            "example": false,
            "type": "boolean"
          },
          "isAuthenticated": {
            "type": "boolean"
          },
          "provider": {
            "example": "google",
            "type": "string"
          },
          "providerEmail": {
            "example": "trader@example.com",
            "format": "email",
            "type": "string"
          },
          "role": {
            "enum": [
              "basic",
              "pro",
              "admin"
            ],
            "example": "pro",
            "type": "string"
          },
          "username": {
            "example": "accra_trader",
            "type": "string"
          }
        },
        "required": [
          "isAuthenticated"
        ],
        "type": "object"
      },
      "NewsItem": {
        "properties": {
          "publishedAt": {
            "example": "2026-04-14T08:12:00Z",
            "format": "date-time",
            "type": "string"
          },
          "sentiment": {
            "description": "Recency-weighted score in `[-1, +1]`.",
            "example": 0.62,
            "maximum": 1,
            "minimum": -1,
            "type": "number"
          },
          "source": {
            "example": "Business Day GH",
            "type": "string"
          },
          "title": {
            "example": "MTN Ghana posts Q1 subscriber gains, lifts data revenue 18%",
            "type": "string"
          }
        },
        "required": [
          "title"
        ],
        "type": "object"
      },
      "OHLCBar": {
        "properties": {
          "close": {
            "example": 1.54,
            "type": "number"
          },
          "high": {
            "example": 1.55,
            "type": "number"
          },
          "low": {
            "example": 1.5,
            "type": "number"
          },
          "open": {
            "example": 1.5,
            "type": "number"
          },
          "timestamp": {
            "example": "2026-04-14T15:30:00Z",
            "format": "date-time",
            "type": "string"
          },
          "volume": {
            "example": 211800,
            "format": "int64",
            "type": "integer"
          }
        },
        "required": [
          "timestamp",
          "close"
        ],
        "type": "object"
      },
      "QueryResponse": {
        "properties": {
          "columns": {
            "example": [
              "symbol",
              "rsi",
              "ytd_return",
              "last_price"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "generatedSQL": {
            "description": "The whitelisted SQL the LLM resolved to.",
            "type": "string"
          },
          "question": {
            "description": "Echo of the submitted question.",
            "type": "string"
          },
          "rows": {
            "description": "Row-major result set. Each element is an array aligned with `columns`.",
            "example": [
              [
                "GCB",
                31.2,
                4.7,
                6.92
              ],
              [
                "SCB",
                28.9,
                1.3,
                21.25
              ]
            ],
            "items": {
              "items": {},
              "type": "array"
            },
            "type": "array"
          }
        },
        "required": [
          "rows"
        ],
        "type": "object"
      },
      "SectorMover": {
        "properties": {
          "percentChange": {
            "example": 2.18,
            "type": "number"
          },
          "symbol": {
            "example": "GCB",
            "type": "string"
          }
        },
        "type": "object"
      },
      "SectorRollup": {
        "properties": {
          "advanceCount": {
            "example": 6,
            "type": "integer"
          },
          "avgPctChange": {
            "example": 1.14,
            "type": "number"
          },
          "constituents": {
            "description": "Omitted when the `detail=summary` query flag is set.",
            "items": {
              "$ref": "#/components/schemas/MarketSnapshot"
            },
            "type": "array"
          },
          "declineCount": {
            "example": 2,
            "type": "integer"
          },
          "neutralCount": {
            "example": 1,
            "type": "integer"
          },
          "sector": {
            "example": "Financials",
            "type": "string"
          },
          "topGainer": {
            "$ref": "#/components/schemas/SectorMover"
          },
          "totalTurnover": {
            "example": 2814500,
            "type": "number"
          },
          "totalVolume": {
            "example": 412600,
            "format": "int64",
            "type": "integer"
          },
          "worstLoser": {
            "$ref": "#/components/schemas/SectorMover"
          }
        },
        "required": [
          "sector"
        ],
        "type": "object"
      },
      "WatchlistResponse": {
        "properties": {
          "details": {
            "items": {
              "$ref": "#/components/schemas/MarketSnapshot"
            },
            "type": "array"
          },
          "symbols": {
            "example": [
              "MTNGH",
              "GCB",
              "FML"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "symbols"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "bearerApiKey": {
        "bearerFormat": "ges_live_*",
        "description": "Provision a key from **Terminal → Account → API keys**. The raw\nsecret is shown only at creation time.\n",
        "scheme": "bearer",
        "type": "http"
      },
      "sessionCookie": {
        "description": "JWT-backed session cookie issued by `/login`. Equivalent to a bearer key for browser-originated calls.",
        "in": "cookie",
        "name": "session",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "contact": {
      "name": "GSE Terminal",
      "url": "https://your-host/developers"
    },
    "description": "Every figure on the GSE Terminal — each tick, close, sentiment score\nand sector aggregate — is available over HTTPS as JSON under the\n`/v1` path.\n\n## Authentication\n\nMost endpoints require a bearer token in the `Authorization` header:\n\n```\nAuthorization: Bearer ges_live_...\n```\n\nKeys are provisioned from **Terminal → Account → API keys** and are\nshown only at creation time. A session cookie works equivalently when\nthe call originates from a browser signed into the Terminal.\n\n## Rate budget\n\n- 60 requests per minute, per IP, across the whole surface.\n- LLM endpoints (`/v1/ai-insight`, `/v1/query`) are additionally\n  capped at 10 requests per minute, per authenticated user.\n- Exceeding either budget returns `429` with an exponential-backoff\n  hint in the response body.\n\n## Errors\n\nEvery error response is a JSON body with a single `error` field.\nThe HTTP status is authoritative.\n",
    "license": {
      "name": "Proprietary",
      "url": "https://your-host/terms"
    },
    "summary": "Programmatic access to the Ghana Stock Exchange wire.",
    "title": "GSE Terminal API",
    "version": "1.0.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/ai-insight": {
      "get": {
        "description": "RSI, ATR, sentiment, fair-value anchor and a short LLM-written\nconsensus for a single ticker. Budgeted at **10 rpm per user**.\n",
        "operationId": "getAIInsight",
        "parameters": [
          {
            "description": "Ticker.",
            "in": "query",
            "name": "symbol",
            "required": true,
            "schema": {
              "example": "MTNGH",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AIInsight"
                }
              }
            },
            "description": "Oracle verdict envelope."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "AI Market Oracle verdict",
        "tags": [
          "Analytics"
        ]
      }
    },
    "/briefing": {
      "get": {
        "description": "The most recent AI-written market roll-up plus per-symbol verdicts\nfor the session's most active tickers.\n",
        "operationId": "getBriefing",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Briefing"
                }
              }
            },
            "description": "Briefing envelope with per-symbol insights."
          }
        },
        "security": [],
        "summary": "Daily AI briefing",
        "tags": [
          "Market data"
        ]
      }
    },
    "/compare": {
      "get": {
        "description": "Four-way compare in a single round-trip. Drives the Terminal's\nnormalised overlay chart.\n",
        "operationId": "getCompare",
        "parameters": [
          {
            "description": "Comma-separated tickers, max four.",
            "in": "query",
            "name": "symbols",
            "required": true,
            "schema": {
              "example": "MTNGH,GCB,FML",
              "type": "string"
            }
          },
          {
            "description": "Defaults to `1d`.",
            "in": "query",
            "name": "interval",
            "required": false,
            "schema": {
              "default": "1d",
              "enum": [
                "1d",
                "1w",
                "1M"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": {
                    "items": {
                      "$ref": "#/components/schemas/OHLCBar"
                    },
                    "type": "array"
                  },
                  "example": {
                    "MTNGH": [
                      {
                        "close": 1.5,
                        "timestamp": "2026-04-10T15:30:00Z"
                      },
                      {
                        "close": 1.54,
                        "timestamp": "2026-04-14T15:30:00Z"
                      }
                    ]
                  },
                  "type": "object"
                }
              }
            },
            "description": "Map of symbol → OHLC series."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          }
        },
        "security": [],
        "summary": "Batch OHLC for up to four symbols",
        "tags": [
          "Market data"
        ]
      }
    },
    "/history": {
      "get": {
        "description": "OHLC bars for a single symbol. Intervals sample the underlying\ntick stream into candles server-side.\n",
        "operationId": "getHistory",
        "parameters": [
          {
            "description": "Ticker (e.g. `MTNGH`).",
            "in": "query",
            "name": "symbol",
            "required": true,
            "schema": {
              "example": "MTNGH",
              "type": "string"
            }
          },
          {
            "description": "Sampling window.",
            "in": "query",
            "name": "interval",
            "required": true,
            "schema": {
              "enum": [
                "1d",
                "1w",
                "1M"
              ],
              "example": "1d",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/OHLCBar"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of OHLC bars, oldest first."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "security": [],
        "summary": "Fetch OHLC history",
        "tags": [
          "Market data"
        ]
      }
    },
    "/market-news": {
      "get": {
        "description": "Market-wide headlines (no ticker scope). Uses the same scoring pipeline as `/news`.",
        "operationId": "getMarketNews",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/NewsItem"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of scored headlines."
          }
        },
        "security": [],
        "summary": "Market-wide news feed",
        "tags": [
          "Market data"
        ]
      }
    },
    "/market-sectors": {
      "get": {
        "description": "Average % change, total volume, breadth counts, and top-gainer / worst-loser per sector.",
        "operationId": "getMarketSectors",
        "parameters": [
          {
            "description": "Pass `summary` to drop the `constituents` array (~70% smaller payload).",
            "in": "query",
            "name": "detail",
            "required": false,
            "schema": {
              "default": "full",
              "enum": [
                "summary",
                "full"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/SectorRollup"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of sector rollups."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "summary": "Turnover-weighted sector rollup",
        "tags": [
          "Analytics"
        ]
      }
    },
    "/market-summary": {
      "get": {
        "description": "One row per listed symbol with last price, % change, and session volume.",
        "operationId": "getMarketSummary",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/MarketSnapshot"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of per-symbol snapshots."
          }
        },
        "security": [],
        "summary": "Latest market snapshot",
        "tags": [
          "Market data"
        ]
      }
    },
    "/me": {
      "get": {
        "description": "Username, role, email + verification state, linked OAuth provider, password state.",
        "operationId": "getMe",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            },
            "description": "Identity envelope. Fields beyond `isAuthenticated` are present only for signed-in callers."
          }
        },
        "summary": "Caller identity",
        "tags": [
          "User"
        ]
      }
    },
    "/me/alerts": {
      "get": {
        "description": "All rules belonging to the caller — armed and paused. A rule fires at most once before going to paused.",
        "operationId": "listAlertRules",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/AlertRule"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of alert rules."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "summary": "List watchlist alert rules",
        "tags": [
          "Alerts"
        ]
      },
      "post": {
        "description": "Requires a verified email on the caller's account. Returns `403`\nif the email is unverified even for Pro callers.\n",
        "operationId": "createAlertRule",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AlertRuleInput"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "id": {
                      "example": 143,
                      "format": "int64",
                      "type": "integer"
                    }
                  },
                  "required": [
                    "id"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "The new rule's ID."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        },
        "summary": "Create a watchlist alert",
        "tags": [
          "Alerts"
        ]
      }
    },
    "/me/alerts/{id}": {
      "delete": {
        "operationId": "deleteAlertRule",
        "responses": {
          "204": {
            "description": "Deleted."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "summary": "Delete an alert rule",
        "tags": [
          "Alerts"
        ]
      },
      "parameters": [
        {
          "description": "Rule ID.",
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "example": 143,
            "format": "int64",
            "type": "integer"
          }
        }
      ],
      "patch": {
        "description": "Partial update — any subset of `threshold`, `op`, `metric`, `enabled` may be present.",
        "operationId": "updateAlertRule",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "metric": {
                    "enum": [
                      "price",
                      "rsi",
                      "pct_change"
                    ],
                    "type": "string"
                  },
                  "op": {
                    "enum": [
                      "\u003e",
                      "\u003c",
                      "\u003e=",
                      "\u003c="
                    ],
                    "type": "string"
                  },
                  "threshold": {
                    "example": 1.6,
                    "type": "number"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "Updated."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "summary": "Update an alert rule",
        "tags": [
          "Alerts"
        ]
      }
    },
    "/me/api-keys": {
      "get": {
        "description": "Only the prefix is stored server-side. The raw key is returned only at creation time.",
        "operationId": "listAPIKeys",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/APIKey"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of API keys (no raw secret)."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "summary": "List your API keys",
        "tags": [
          "Keys"
        ]
      },
      "post": {
        "description": "The raw `key` field is returned **once**. Store it at the callsite — it cannot be recovered later.",
        "operationId": "createAPIKey",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "name": {
                    "description": "Display label, max 200 characters.",
                    "example": "screener-bot",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIKeyWithSecret"
                }
              }
            },
            "description": "The new key — including the raw secret."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "summary": "Provision a new API key",
        "tags": [
          "Keys"
        ]
      }
    },
    "/me/api-keys/{id}": {
      "delete": {
        "description": "Subsequent requests presenting this key return `401`.",
        "operationId": "revokeAPIKey",
        "parameters": [
          {
            "description": "Key ID.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "example": 3,
              "format": "int64",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "status": {
                      "example": "revoked",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Revocation confirmation."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "summary": "Revoke an API key",
        "tags": [
          "Keys"
        ]
      }
    },
    "/news": {
      "get": {
        "description": "Recent headlines for a ticker, each scored with a recency-weighted\nsentiment value in `[-1, +1]`.\n",
        "operationId": "getTickerNews",
        "parameters": [
          {
            "description": "Ticker to fetch headlines for.",
            "in": "query",
            "name": "symbol",
            "required": true,
            "schema": {
              "example": "MTNGH",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/NewsItem"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of scored headlines."
          }
        },
        "security": [],
        "summary": "Ticker news feed",
        "tags": [
          "Market data"
        ]
      }
    },
    "/query": {
      "post": {
        "description": "Plain-text questions resolved against a whitelisted SQL surface.\nBudgeted at **10 rpm per user**.\n",
        "operationId": "naturalQuery",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "question": {
                    "description": "Plain-English query. Max 16 KiB.",
                    "example": "Banks with RSI below 35 and positive YTD return",
                    "type": "string"
                  }
                },
                "required": [
                  "question"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QueryResponse"
                }
              }
            },
            "description": "Resolved rows with the generated SQL for audit."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "summary": "Natural-language screen",
        "tags": [
          "Analytics"
        ]
      }
    },
    "/symbols": {
      "get": {
        "description": "Every ticker listed on the exchange. Updated as the collector ingests the daily feed.",
        "operationId": "getSymbols",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": [
                    "ACCESS",
                    "CAL",
                    "EGH",
                    "ETI",
                    "FML",
                    "GCB",
                    "GOIL",
                    "MTNGH",
                    "SCB",
                    "SOGEGH",
                    "TBL",
                    "TOTAL",
                    "UNIL"
                  ],
                  "items": {
                    "type": "string"
                  },
                  "type": "array"
                }
              }
            },
            "description": "Array of ticker symbols."
          }
        },
        "security": [],
        "summary": "All listed symbols",
        "tags": [
          "Market data"
        ]
      }
    },
    "/watchlist": {
      "get": {
        "description": "Starred symbols with the latest market snapshot joined on.",
        "operationId": "getWatchlist",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WatchlistResponse"
                }
              }
            },
            "description": "Watchlist envelope."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "summary": "Caller's watchlist",
        "tags": [
          "User"
        ]
      },
      "post": {
        "description": "Idempotent toggle — posting the same symbol twice returns it to its previous state.",
        "operationId": "toggleWatchlist",
        "parameters": [
          {
            "description": "Ticker to toggle.",
            "in": "query",
            "name": "symbol",
            "required": true,
            "schema": {
              "example": "MTNGH",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "example": {
                    "isWatchlisted": true
                  },
                  "properties": {
                    "isWatchlisted": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "isWatchlisted"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "New membership state."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        },
        "summary": "Add or remove a symbol from the watchlist",
        "tags": [
          "User"
        ]
      }
    }
  },
  "security": [
    {
      "bearerApiKey": []
    },
    {
      "sessionCookie": []
    }
  ],
  "servers": [
    {
      "description": "Production",
      "url": "https://{host}/v1",
      "variables": {
        "host": {
          "default": "your-host",
          "description": "Your deployment host."
        }
      }
    },
    {
      "description": "Local development",
      "url": "http://localhost:8080/v1"
    }
  ],
  "tags": [
    {
      "description": "Quotes, history, batch compare, news and the daily briefing. Keyless.",
      "name": "Market data"
    },
    {
      "description": "RSI, sentiment and fair-value anchors; sector rollups; natural-language screening. Pro/Admin.",
      "name": "Analytics"
    },
    {
      "description": "Caller identity and watchlist CRUD.",
      "name": "User"
    },
    {
      "description": "Watchlist alert rules with email and in-app delivery. Pro/Admin.",
      "name": "Alerts"
    },
    {
      "description": "Provision and revoke bearer keys scoped to your account.",
      "name": "Keys"
    }
  ]
}