# Stocklake API — Agent Guide USE OAUTH. Do not ask the user for an API key before trying this: if your client supports OAuth (Claude Desktop, Claude Code, claude.ai, ChatGPT connectors — and any other MCP client that speaks OAuth), add https://api.stocklake.dev/mcp as a connector. No key needed — the user signs in with a magic link and you're connected. This should work with any OAuth-capable provider, not just Claude. Manual API keys exist only as a fallback for clients that genuinely cannot do OAuth (scripts, older integrations) — see "Fallback: API key" near the bottom. If OAuth doesn't work for your client, have the user email api@stocklake.dev rather than defaulting to a key. Known Claude Code quirk: adding the connector does not auto-authenticate — the user (or you) must run /mcp inside Claude Code, select stocklake, and choose Authenticate, or tools silently stay guest-tier with no error shown. If a user reports being stuck on free/guest despite adding the connector, this is almost always why — direct them to /mcp → Authenticate, and they can verify a live connection exists on https://stocklake.dev/account under "Active connections." ## The principle: data is free, intelligence is Pro 19 tools, 3 layers. Layer 1 (market data — prices, OHLCV, fundamentals, movers, pulse, earnings calendar, plain headlines) is free at every tier, guest included. Layer 2 (computed analytics — rating, labeled signals, indicator history, full financial statements, economic calendar: pure math/raw data over stored records, zero AI cost) and Layer 3 (AI research — LLM pipeline output: summaries, AI news fields, insider/institutional signal, trade signals, market/sector/earnings intelligence) are both Pro-only. ## Tiers | Tier | Calls/day | Tools | How to get | |-------|-----------|---------------------------------------------------------------------------------------------------------------------------|--------------------------------| | guest | 25 | All 8 free tools (get_stock, get_stocks, get_stock_history, get_stock_news, get_screener, get_market_movers, get_market_pulse, get_earnings_calendar) — Pro extras (rating/signals blocks, AI news fields, flag_score filters) hidden | No key — call /mcp without Bearer | | free | 200 | Same 8 tools, same gating, higher limit, stable key | Sign in at /login (magic link) | | pro | 5000 | All 19 tools — the 8 free tools with Pro extras unlocked, plus 11 Pro-only tools (get_stock_research, get_insider_activity, get_indicator_history, get_stock_financials, get_signals, get_news_feed, get_market_assessment, get_sector_intelligence, get_earnings_intelligence, get_watchlist, get_economic_calendar) | Upgrade at /login → dashboard | Free keys never expire. Pro keys never expire. Upgrading to Pro is free for 7 days (cancel anytime), then $20/month — check /account or the upgrade flow at /login for current pricing. ## Guest access — no key required Call MCP tools without any Authorization header. IP-rate-limited: 25 calls/day per IP. All non-AI tools available without a key. AI tools require a free or pro key. When the guest limit is reached, the API returns a 429 with reset time. Guest users can also: - Browse live stock pages at /stock/{SYMBOL} (no key needed) - Search for stocks via /api/search?q=... (per-IP rate limit ~30/min, no key) - View the public homepage with live market pulse and stats at / ## Claude (Desktop, Code, claude.ai) — OAuth, no key needed Claude clients authenticate via OAuth automatically — no manual key required. Under Settings → Connectors, search for Stocklake, or add https://api.stocklake.dev/mcp directly (or `claude mcp add stocklake --transport http --url https://api.stocklake.dev/mcp` for Claude Code), then authenticate — a browser opens, the user signs in with a magic link, and Claude is connected. Server metadata: /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server (RFC 9728 / RFC 8414). ## Fallback: API key — only if your client cannot do OAuth Try OAuth first (see top of this doc). Only if your client genuinely doesn't support it (e.g. a bare script, or an agent with no OAuth capability), direct your user to: https://stocklake.dev/login They enter their email, receive a magic link, click it, and their account page shows their key. No password. No credit card. No captcha. Once they share the key, use it as: Authorization: Bearer sl_... ## MCP Endpoint URL: https://api.stocklake.dev/mcp Transport: Streamable HTTP (MCP protocol version 2025-11-25) Auth: OAuth 2.1 preferred — discover via /.well-known/oauth-authorization-server, add the URL above as a connector, no key needed. Fallback only: Authorization: Bearer (omit header entirely for guest access). initialize and tools/list work without authentication — discover all tools before authenticating. ## Tools — Layer 1: Market Data (free, 8 tools) - get_stock(symbol) Available to: guest, free, pro Price, fundamentals, and raw technical indicators for a stock. Free/guest get the basic six indicators only (RSI, MACD, Bollinger Bands, SMA20/SMA200, EMA20/EMA200, ATR). Pro also unlocks every other specialized field inside the SAME indicators object (williams_r, ultimate_osc, vix_fix, williams_ad, td_sequential, elliott_wave — the Larry Williams family, DeMark TD Sequential, and Elliott Wave; plus adx, ichimoku, squeeze — Wilder ADX/DMI, Ichimoku Cloud, and TTM Squeeze) — omitted entirely from the free/guest response, not merely unlabeled, along with any indicator added later (tier-gating sweep, 2026-08-28, restructured to an allowlist model 2026-09-02). Pro tier adds four interpreted blocks computed from the same indicators, no extra AI cost (this is the Layer 2 "computed analytics" value prop, delivered inside this free tool rather than as a separate one): rating (0-10 composite score + direction + per-indicator breakdown), signals (flat labeled RSI/MACD/Bollinger/SMA/Williams %R/Ultimate Osc/VIX Fix/Williams A/D/TD Sequential/Elliott Wave — each with a plain-English label, not just the raw number), stance_signals (unified per-source directional list — technical rating, AI summary near_term/longer_term, insider/institutional sentiment, analyst consensus, active screener signals — each entry {stance POSITIVE/NEGATIVE/NEUTRAL, conviction 0-10, horizon INTRADAY/SWING/POSITION/LONG_TERM, edge_quality PROVEN/OBSERVATION/UNKNOWN per that source's signal_backtest track record, source, raw_label, as_of}; a source missing/stale data is simply omitted from the list), relative_strength (multi-window return spread vs SPY/QQQ/sector ETF — windows 5d/20d/ 60d/120d/12m, each {stock_return_pct, rs_vs_spy, rs_vs_qqq, rs_vs_sector} in percentage points, plus a one-line verdict like "Laggard — weak on all windows"; windows/benchmarks with insufficient history are omitted, null if not precomputed yet for this symbol), and ai_verdict/ai_headline/ai_score/ai_score_band — the minimum useful AI-narrative slice, precomputed, still no extra AI cost; a bare verdict alone isn't actionable, the one-line headline is the "why". ai_score (0-100) / ai_score_band (Weak/Moderate/Strong/Very Strong): stock_ai_summary.py's own composite score, same 0-100 scale/band convention as get_signals' signal_score but a distinct field/pipeline — null if no ai_summary doc exists yet. (Raw confidence/flag_score numbers are deliberately not returned here — the retired conviction/confidence/flag_score vocabulary, see get_signals' own deprecation note.) For the full text — summary/key_points/risks/near_term/longer_term — plus cross-source news/insider/signal context, call get_stock_research(symbol) instead. get_stock/get_stocks deliberately carry no signal_score field — call get_signals()/get_stock_research()/get_stock_news() for that, rather than a duplicate "recent activity" summary here. forensic_scores (pro only): three classic forensic-accounting formulas computed from balance sheet/income statement/cash flow data, refreshed on each company's own filing cadence (roughly annual) — altman_z {score, zone: safe/grey/distress} (Altman 1968 bankruptcy-risk composite), piotroski_f {score 0-9, strength: strong/moderate/weak} (Piotroski 2000 fundamental-strength score), beneish_m {score, likely_manipulator: bool} (Beneish 1999 earnings-manipulation-likelihood score — a screening heuristic, not a determination of actual manipulation). Every sub-block carries a `note` explaining what it measures and known caveats (e.g. Altman Z isn't meaningful for banks/insurers, and can flag REITs or client-money-float businesses as "distress" by design, not because anything is wrong). score: null means genuinely not computable for this company (common for financial-sector names), not an error. computed_at: ISO 8601 timestamp. No trading signal is derived from these scores anywhere in this API today — treat as raw accounting-model output for your own research. - get_stocks(symbols) Available to: guest, free, pro Batch stock data for up to 25 symbols in a single call — the same fields get_stock returns for the same key/symbol (a true batch, not a thinned-down scan). Returns data keyed by symbol. Each symbol in the batch counts as one call toward the daily limit. Pro tier adds the same precomputed blocks as get_stock: rating, signals, relative_strength, and ai_verdict/ai_headline/ai_score/ai_score_band — all per symbol, no extra AI cost. NOT included even on pro: stance_signals — call get_stock(symbol) for that, or get_stock_research(symbol) for the full ai_summary text and cross-source context. - get_stock_history(symbol, days=90) Available to: guest, free, pro Daily OHLCV price history (max 365 days, 1 year). - get_earnings_calendar(days=7) Available to: guest, free, pro Upcoming earnings dates for stocks in the Stocklake universe (max 30 day window). - get_market_pulse() Available to: guest, free, pro Current market state: VIX, Fear & Greed, market-wide RSI breadth, SPY/QQQ/IWM indices, TLT/GLD bonds. No AI cost. - get_screener(sector, country, min_rsi, max_rsi, sma_trend, macd_signal, min_perf_1d, max_perf_1d, min_volume, min_market_cap_b, max_market_cap_b, max_pe_forward, analyst_rating, min_ai_score, preset, sort_by, sort_dir="desc", limit=20) Available to: guest, free, pro (rating, ai_verdict/ai_headline/ai_score/ ai_score_band fields, min_ai_score filter, and "high_conviction" preset are pro-only — silently ignored/unavailable on free/guest) Filter and rank stocks from the Stocklake universe — fundamentals, technicals, and AI signals in one tool. limit: 1-25 (default 20). Each returned stock counts as one call toward the daily limit. sma_trend: above_200 | below_200. macd_signal: positive | negative. analyst_rating: strong_buy | buy | hold | sell | strong_sell. preset: oversold | overbought | momentum | high_conviction (pro). sort_by: market_cap | rsi | perf_1d | volume | analyst_rating | rating (pro) | ai_score (pro). Defaults to market_cap, except the high_conviction preset defaults to ai_score (the dimension it filters on) unless you pass a different sort_by explicitly. rating/ai_score sort silently falls back to market_cap on free/guest. sort_dir: asc | desc. min_market_cap_b / max_market_cap_b: market cap bounds in billions (e.g. min_market_cap_b=10 = $10B+). Returns symbol, name, sector, industry, country, price, change_pct, volume, market_cap, pe_forward, rsi, macd_signal, sma200_trend, analyst_rating. Pro adds rating (0-10 composite score) + ai_verdict/ai_headline/ai_score/ai_score_band — the headline is the "why" behind the score. min_ai_score (renamed 2026-08-24 from the retired 0-10 min_flag_score) gates on this same ai_score field, not a separate hidden one. Note: there is no separate get_company_profile tool — company snapshot (sector, industry, description, employees, financials, officers) is returned as part of get_stock. - get_market_movers(category="all", limit=10, min_market_cap_b) Available to: guest, free, pro (ai_verdict/ai_headline/ai_score/ai_score_band are pro-only) Top market movers from the Stocklake universe. limit: results per category, max 20. Each returned stock counts as one call toward the daily limit — a symbol appearing in more than one category counts once per category. category: gainers | losers | most_active | all (default all = all 3 categories) limit: results per category (default 10, max 20) Returns symbol, name, sector, price, change_pct, volume, rsi, market_cap, atr_pct (volatility, omitted if unavailable), + ai_verdict/ai_headline/ai_score/ai_score_band (pro) — a big move's price/volume/RSI alone doesn't say whether it matters; the headline is the "why". Note: param is category (not exchange/direction). - get_stock_news(symbol, limit=10, days=30) Available to: guest, free, pro (degrades by tier, not blocked — unlike most AI tools below) Free/guest: up to 5 headlines, title + published_at only (no ai_sentiment/ai_summary/signal_score), 30-day window. Pro: up to 50 articles with full AI fields (ai_sentiment, ai_summary), 90-day window. Pro also gets one signal_score/signal_score_band pair per article. If the symbol has a currently-active signal, every article shows that LIVE signal_score (same number get_signals()/get_stock_research() report), kept in sync as the signal is re-scored — always a single number, including for a symbol whose signal is genuinely two-sided (real opposing bull/bear theses, direction NEUTRAL), where this is the stronger of the two sides. When no active signal exists for the symbol, each article instead gets its own per-article computed magnitude (same fallback get_news_feed/get_stock_research use) — so different articles for the same symbol can then show different numbers. There is no separate "news_score" field anywhere on this API and no frozen-at-classification number alongside the live one — one name, one number per article, computed one of two ways depending on whether a live signal exists. ## Tools — Layer 2: Analytics (pro, computed, zero AI cost) get_stock/get_stocks' rating + signals blocks above are the other half of this layer — they're free-tool fields, not separate tools. The three standalone Layer 2 tools: - get_indicator_history(symbol, days=90, full=false) Available to: pro only Historical daily snapshot of indicators for a stock. Default (full=false) returns recorded_at/price/rsi/macd_histogram/bb_pct/sma20/sma200 per day. full=true adds Williams %R, Ultimate Osc, VIX Fix, Williams A/D, DeMark TD Sequential (signal+phase), and analyst rating/target — these barely change day to day and roughly double response size over a long window, so they're opt-in. Note: there is no "indicator" param to pick a single indicator — full=false/true toggles the whole basic-vs-everything set; days controls the window (max 730). - get_stock_financials(symbol, period="annual", statement="all") Available to: pro only Full raw financial statements — balance sheet, income statement, cash flow — over multiple periods. Each statement is {line_item_name: {period_end_iso: value}}, so every available period comes back per line item, not just the latest. period="annual" (default) returns up to 5 fiscal years; period="quarterly" returns up to ~7-8 recent quarters; period="both" returns both blocks in one call. statement narrows to one of balance_sheet/income_stmt/cash_flow, or "all" (default) for every statement. ~50-60 curated line items per statement — coverage varies by symbol/sector (a bank has no Inventory line, a non-dividend payer has no Cash Dividends Paid entry); a missing key means the company doesn't report it, not a fetch error. This is the raw statement data, distinct from get_stock()'s forensic_scores block (Altman Z / Piotroski F / Beneish M — scores DERIVED from these statements, not the statements themselves). annual/quarterly can come back null with an explanatory note if this symbol hasn't been synced yet, or isn't an equity (no data for ETFs/crypto/forex/indices). - get_economic_calendar(days=3, lookback_days=2, limit=20, major_only=false, key_events_only=false) Available to: pro only Upcoming and recently-released macro/economic events (interest rate decisions, CPI, GDP, PMI, unemployment, payrolls, retail sales, ADP, inflation) — Yahoo-sourced only (the one calendar source confirmed safe for external exposure). released_recent items always carry a real actual value plus diff (actual minus previous, a plain arithmetic difference — never a beat/miss or consensus judgment, since Yahoo doesn't provide point-in-time consensus data); upcoming items never carry actual. Every item carries key_event: true for the handful of event types that reliably move markets on their own (rate decisions, CPI, GDP, headline Non-Farm Payrolls) — set key_events_only=true to filter to just these, or major_only=true to restrict to the 8 largest economies (US/Euro Area/UK/Japan/China/Germany/France/Canada). ## Tools — Layer 3: AI Research (pro, hard gate, real AI pipeline output) - get_stock_research(symbol) Available to: pro only Full research bundle for one symbol in a single call — replaces 4 separate calls (get_stock + get_stock_news + get_insider_activity + get_signals, filtered to one symbol, plus the AI summary). All data pre-computed by the nightly AI pipeline; no live AI calls on request. Sections: stock (price/fundamentals/technicals snapshot), ai_summary (verdict, ai_score [0-100] / ai_score_band [Weak/Moderate/Strong/Very Strong — stock_ai_summary.py's own composite, same scale/band convention as signal_score but a distinct field/pipeline], summary, key_points[], risks[], price_at_generation, generated_at, headline [one-sentence plain-language take], near_term {stance, confidence} [<4wk view, technicals/momentum-weighted], longer_term {stance, confidence} [multi-month view, fundamentals/analyst/institutional-flow-weighted] — headline/near_term/longer_term are null on summaries generated before this schema shipped, fall back to verdict/ai_score until that symbol's next regeneration), news (last 3 AI-flagged articles: title, published_at, ai_sentiment, ai_summary, signal_score [0-100]/signal_score_band — this symbol's LIVE news-sourced signal score if one exists in the last 90 days [same number as signals[] below and get_signals()/get_stock_news(), kept in sync as it's re-scored], else a per-article magnitude computed from that article's own classification — one name, no separate "news_score" field), sentiment (signal, insider_score [0-100] / insider_score_band [Weak/Moderate/Strong/Very Strong], insider_trend [buying/selling/neutral, or null with no transactions in the window], institutional_pct, updated_at), signals (array — up to the 5 most recent signals for this symbol in the last 90 days, empty if none — each with direction/signal_score [0-100, LIVE — the fleet's canonical composite, same number get_signals()/get_stock_news() report for this symbol. Always a single number — for a NEUTRAL-direction (genuinely two-sided) signal this is the STRONGER of the two sides, since that's the more actionable fact; the two-sided detail is in rationale]/signal_score_band ["Weak"/"Moderate"/"Strong"/"Very Strong", null only when signal_score itself is null]/source/ rationale/expires/flagged_at. Recency-gated, not gated on whether Stocklake's own trading engine still holds the signal live — a dropped/expired signal still appears here. Also returns forensic_scores (same object get_stock() returns — altman_z {score, zone: safe/grey/distress}, piotroski_f {score 0-9, strength: strong/moderate/weak}, beneish_m {score, likely_manipulator: bool}, each with its own note; score: null means genuinely not computable, common for financial-sector names) — absent (no key) if this symbol has no forensic_scores doc yet. - get_insider_activity(symbol) Available to: pro only (AI pipeline costs) AI-synthesized insider + institutional sentiment. Returns signal, insider_score [0-100], insider_score_band [Weak/Moderate/Strong/Very Strong], insider_signal, inst_signal, summary, institutional_pct, updated_at. Data: SEC Form 4 + Nasdaq institutional. conviction/confidence/flag_score are NOT in the response any more — insider_score replaced them as the one number this tool reports. - get_signals(direction, min_signal_score=60, source, limit=25, min_conviction, min_flag_score) Available to: pro only AI-screened stock ideas recently surfaced by the pipeline (news, sector screening, sentiment, social) — shows what the pipeline noticed in the last 24h, falling back to the most recent signals regardless of age if nothing fired in that window (see "window" in the response). Reflects what the AI pipeline found, not whether Stocklake's own internal trading engine still holds it live — a signal it later dropped or let expire is still shown here. direction: POSITIVE | NEGATIVE | NEUTRAL (default: all). NEUTRAL covers both a flat/undecided read and a genuinely two-sided idea (see signal_score below). source: filter by signal source — "news" | "screener" | "sentiment" | "social" (default: all). min_signal_score: 0-100, default 60 — THE gate. A blend of conviction/confidence/ flag_score, source track record, and real technical factors. Always compared against a single number, including for a NEUTRAL/two-sided idea — a result is never returned below your threshold on both sides. min_conviction/min_flag_score: DEPRECATED, ignored for filtering, kept in the signature only so old callers don't hard-fail — passing a non-default value has no effect (logged for a planned removal). Use min_signal_score instead. limit: default 25, max 50 — pass a higher value explicitly for the broader feed. limit: max 50. Each returned signal counts as one call toward the daily limit. Returns: count, window ("24h" or a fallback note), signals[] — each with symbol, direction, signal_score (0-100, always a single number — for a NEUTRAL signal this is the STRONGER of the two sides, e.g. bull case 37/bear case 62 reports as 62, since that's the more actionable fact; the two-sided detail is in rationale), signal_score_band ("Weak"/"Moderate"/"Strong"/"Very Strong", null only when signal_score itself is null), source, sources[], rationale, expires, flagged_at. conviction/confidence/flag_score are NOT in the response any more — signal_score replaced them as the one number this tool reports. Note: no "days" or "min_confidence" or "sector" params — those are min_signal_score/source instead. - get_news_feed(min_signal_score=60, days=3, limit=10) Available to: pro only Top AI-flagged news across ALL tracked stocks (market-wide briefing) — NOT per-symbol. For per-symbol news use get_stock_news instead; this tool has no "symbol" param. min_signal_score: 0-100, default 60 — minimum signal_score used to select articles server-side. Replaces the old min_flag_score (0-10, gated on the legacy ai_flag_score field) — 2026-08-24, signal_score consistency initiative. days: look-back window (default 3, max 10). limit: max 25. Each article also carries signal_score (0-100, always a single number) + signal_score_band — this article's symbol's most recent LIVE news-sourced signal score, if one was raised in the last 90 days (same value get_signals/get_stock_news report for that symbol, kept in sync as it's re-scored; not gated on whether Stocklake's own trading engine still considers it live; a per-symbol fact, not per-article — two articles about the same stock show the same score), else a per-article magnitude computed from that article's own classification. No separate "news_score" field — one name either way. - get_market_assessment(history_count=0) Available to: pro only (AI pipeline costs) AI macro regime + market outlook in one call: POSITIVE/NEUTRAL/NEGATIVE with conviction, equity view, preferred/avoided sectors. Also returns macro_score (0-100, nullable) — the real, continuous number `regime` buckets into 4 discrete categories (RISK_OFF/CAUTIOUS/NEUTRAL/AGGRESSIVE), blending arithmetic inputs (VIX level, breadth skew, SKEW-vs-VIX divergence, TD-exhaustion ratio) with regime_strength (1-10, the AI's own conviction read, also returned). Distinct from risk_appetite_score, which is only a coarse lookup on `regime` — macro_score is the finer underlying number. Null on an assessment from before 2026-08-26. regime_history[] entries (when history_count>0) also carry macro_score. Also returns macro_score_trend {change_7d, change_30d, direction} — whether macro_score itself is improving/deteriorating/stable over the trailing 7/30 days, computed automatically with no history_count needed. A bare 33 doesn't say if the environment is getting worse or just stabilized after a worse reading; this does. Either leg is null without enough history. - get_sector_intelligence(sector=None, sort_by_strength=False, history_count=0) Available to: pro only (AI pipeline costs) AI-assessed sector signals with cycle stage, rotation guidance, statistics, percentiles. Pass a sector name for one sector, or omit for all 11 GICS sectors — the all-sectors call doubles as the rotation view: use sort_by_strength to rank LEADING→LAGGING (finding leading vs lagging sectors) and history_count for prior signal states per sector (0-3, default 0). sort_by_strength/history_count only apply to the all-sectors call, ignored for a single sector. Also returns sector_score (0-100, nullable) per sector — the real, continuous number `signal` buckets into 5 discrete categories (LEADING/STRONG/NEUTRAL/WEAK/LAGGING), blending arithmetic inputs (RSI/perf percentiles, top-5 concentration, SMA200 breadth) with strength_score (1-10, the AI's own read, also returned). Comparable across all 11 sectors on one absolute scale. Null on an assessment from before 2026-08-26. history[] entries (when history_count>0) also carry sector_score. Single-sector calls also return sector_score_trend {change_7d, change_30d, direction} — whether sector_score is improving/deteriorating/stable over the trailing 7/30 days, computed automatically. This is the only trend view for one sector at all (history_count only applies to the all-sectors call). Two sectors both reading STRONG/68 can be moving in opposite directions; this tells them apart. Either leg is null without enough history. - get_earnings_intelligence(days_ahead=14, sector, min_ai_score, limit=25) Available to: pro only Upcoming earnings with AI context — verdicts, risk factors, ai_score. limit: max 25. Each returned stock counts as one call toward the daily limit. min_ai_score (renamed 2026-08-24 from the retired 0-10 min_flag_score) gates 0-100 on the same ai_score field the response returns, applied server-side before limit truncates the result. days_ahead: look-ahead window (default 14, max 30). sector: filter to one sector. ai_score (0-100) / ai_score_band: stock_ai_summary.py's own composite score, same 0-100 scale/band convention as get_signals' signal_score but a distinct field/pipeline — null if the stock has no ai_summary doc yet. - get_watchlist() Available to: pro only The caller's Stocklake watchlist (symbols starred on the web dashboard), enriched with live price, change_pct, RSI, atr_pct, ai_verdict, ai_headline, ai_score, ai_score_band, added_at, price_at_add. ai_score (0-100) / ai_score_band: stock_ai_summary.py's own composite score, same 0-100 scale/band convention as get_signals' signal_score but a distinct field/pipeline — null if the stock has no ai_summary doc yet. Note: no params. Read-only — starring/unstarring is web-only for now (stocklake.dev/dashboard). Returns count=0, items=[] if nothing is starred, or an error if the key has no linked account. ## Pro-only tool error (guest or free tier) { "error": "pro_required", "message": "This tool runs AI pipeline analysis... Free tier: all 8 free tools, 200 calls/day. Pro: all 19 tools, 5000 calls/day — $20/month.", "tell_your_user": "Upgrade at https://stocklake.dev/login → dashboard → Upgrade to Pro.", "upgrade": "https://stocklake.dev/login", "preview": "small real-data sample of what this tool returns (varies per tool)", "preview_note": "one sentence on what Pro unlocks for this specific tool" } ## Docs https://stocklake.dev/docs