{"openapi":"3.0.3","info":{"title":"Trade AI API","version":"2.0.0","description":"\n## Two API audiences\n\nTrade AI exposes the same intelligence to two kinds of callers:\n\n1. **Direct user (Legacy)** — a single retail user creates an account on Trade AI,\n   subscribes to the **Free** plan, and calls endpoints like `/signals`,\n   `/portfolio/summary`, `/charts/analyze-symbol` directly. Their API key\n   has no `platform_id`.\n2. **Platform (B2B2C)** — a company subscribes to a paid plan (**Grow / Pro / Ultra**),\n   onboards its own end-users via the API, and calls per-user endpoints under\n   `/users/{user_id}/...`. Their API key carries a `platform_id` and acts on\n   behalf of any user the platform created.\n\n```\n       ┌────────────────┐                   ┌──────────────────────┐\n       │  Trade AI API  │ <──── Bearer ───  │  Platform backend    │\n       └───────┬────────┘                   │  (your company)      │\n               │                            └──────────┬───────────┘\n               │  per-user data                        │ provisions / queries\n               ▼                                       ▼\n       ┌────────────────┐                   ┌──────────────────────┐\n       │ platform_users │                   │   End users / IBs    │\n       └───────┬────────┘                   └──────────┬───────────┘\n               │ Telegram link-code                    │\n               └──────────► Trade AI Master Bot ◄──────┘\n                                  ▲\n                                  │ fan-out\n                              child bots\n```\n\n## Plans\n\n| Plan       | Audience          | Free | Requests / min |\n|------------|-------------------|------|----------------|\n| Free       | Direct retail user| ✅    | 30             |\n| Grow       | Platform          | ❌    | 60             |\n| Pro        | Platform          | ❌    | 240            |\n| Ultra      | Platform          | ❌    | 600            |\n\nPlatforms cannot subscribe to Free. There is **no hard cap on the number of\nend-users** a Platform can provision; rate limiting is enforced per platform\nAPI key.\n\n## Auth\n\nEvery request: `Authorization: Bearer tsk_live_...`\n\nThe key resolves either to a Platform (then `/platforms/me`, `/users/...`,\n`/users/{id}/...` are available) or to a legacy direct user (then only the\nunscoped legacy endpoints).\n"},"servers":[{"url":"/api/public/v1","description":"Production"}],"tags":[{"name":"Platforms","description":"Register and manage your Platform account."},{"name":"Users","description":"Provision and manage end-users under your Platform."},{"name":"Per-User Portfolio","description":"Per-user portfolio pricing & insights."},{"name":"Per-User Signals","description":"Per-user trading signals."},{"name":"Per-User Charts","description":"Per-user AI chart analysis."},{"name":"Per-User Telegram","description":"Bind users to Telegram and push alerts."},{"name":"Legacy","description":"Direct-user endpoints (no platform scoping)."},{"name":"AI Functions","description":"Generic AI functions (news, sentiment, translate, summarize, explain-symbol)."},{"name":"Meta","description":"Health, usage, OpenAPI spec."}],"components":{"securitySchemes":{"ApiKeyAuth":{"type":"http","scheme":"bearer","bearerFormat":"tsk_live_xxx"}},"schemas":{"HoldingsBody":{"type":"object","required":["holdings"],"properties":{"holdings":{"type":"array","minItems":1,"items":{"type":"object","required":["symbol","asset_class","quantity"],"properties":{"symbol":{"type":"string","example":"AAPL"},"asset_class":{"type":"string","enum":["equities","crypto","forex","commodities"]},"quantity":{"type":"number"},"cost_basis":{"type":"number","description":"Avg cost per unit (optional)"}}}},"language":{"type":"string","description":"ISO 639-1 (e.g. ar, fr, en) or 'auto'. Used by /portfolio/insights to localize the AI insights."}}},"PlatformRegisterRequest":{"type":"object","required":["owner_user_id","company_name","contact_email","country","activity","user_size_band","plan_slug"],"properties":{"owner_user_id":{"type":"string","format":"uuid","description":"Trade AI auth user id who owns the platform."},"company_name":{"type":"string","minLength":2,"maxLength":120},"contact_email":{"type":"string","format":"email"},"country":{"type":"string"},"activity":{"type":"string","enum":["trading_platform","data_platform","it_services","consulting","other"]},"activity_other":{"type":"string","nullable":true},"user_size_band":{"type":"string","enum":["0_500","501_10k","10k_100k","above_100k"]},"plan_slug":{"type":"string","enum":["grow_monthly","pro_monthly","ultra_monthly"]}}},"Platform":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"company_name":{"type":"string"},"contact_email":{"type":"string"},"country":{"type":"string"},"activity":{"type":"string"},"user_size_band":{"type":"string"},"status":{"type":"string","enum":["pending","active","suspended"]},"created_at":{"type":"string","format":"date-time"}}},"CreatePlatformUserRequest":{"type":"object","required":["external_user_id","first_name","last_name"],"properties":{"external_user_id":{"type":"string","description":"Your platform's stable user id."},"first_name":{"type":"string"},"last_name":{"type":"string"},"mobile":{"type":"string","nullable":true},"email":{"type":"string","format":"email","nullable":true},"country":{"type":"string","nullable":true},"trading_platform":{"type":"string","nullable":true,"description":"e.g. MT5, cTrader, Binance"},"type":{"type":"string","enum":["user","agent","ib"],"default":"user"}}},"PlatformUser":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"external_user_id":{"type":"string"},"first_name":{"type":"string"},"last_name":{"type":"string"},"mobile":{"type":"string","nullable":true},"email":{"type":"string","nullable":true},"country":{"type":"string","nullable":true},"trading_platform":{"type":"string","nullable":true},"type":{"type":"string","enum":["user","agent","ib"]},"status":{"type":"string","enum":["active","archived"]},"created_at":{"type":"string","format":"date-time"}}},"TelegramLinkCode":{"type":"object","properties":{"link_code":{"type":"string","example":"A4F2C9D1"},"expires_at":{"type":"string","format":"date-time"}}},"GeneratedSignal":{"type":"object","description":"AI-generated signal. Trend bias and support/resistance are computed deterministically from classical floor-trader pivots and indicator stack; AI is used only for the trade narrative.","properties":{"source":{"type":"string","enum":["ai"]},"symbol":{"type":"string","example":"BTC/USD"},"asset_class":{"type":"string","enum":["equities","crypto","forex","commodities"]},"timeframe":{"type":"string","enum":["1Min","5Min","15Min","1Hour","1Day"]},"side":{"type":"string","enum":["long","short","none"]},"entry":{"type":"number","nullable":true},"stop":{"type":"number","nullable":true,"description":"Primary stop-loss (equals `sl1`). Kept for backward compatibility."},"stops":{"type":"array","items":{"type":"number"},"description":"Three stop-loss levels [SL1, SL2, SL3] from tightest to widest."},"stop_losses":{"type":"object","description":"Structured 3-level stop-loss object derived from ATR × sl_mults.","properties":{"sl1":{"type":"number","nullable":true,"description":"Tight stop — scalp-style invalidation."},"sl2":{"type":"number","nullable":true,"description":"Standard stop — primary risk level."},"sl3":{"type":"number","nullable":true,"description":"Wide stop — disaster / final invalidation."}}},"targets":{"type":"array","items":{"type":"number"},"description":"Three take-profit levels [TP1, TP2, TP3]."},"take_profits":{"type":"object","description":"Structured 3-level take-profit object derived from ATR × tp_mults.","properties":{"tp1":{"type":"number","nullable":true,"description":"First scale-out target."},"tp2":{"type":"number","nullable":true,"description":"Primary profit target."},"tp3":{"type":"number","nullable":true,"description":"Extended / runner target."}}},"confidence":{"type":"integer","minimum":0,"maximum":100},"bias":{"type":"string","enum":["bullish","bearish","neutral"]},"rationale":{"type":"string"},"risks":{"type":"array","items":{"type":"string"}},"summary":{"type":"string","description":"Localized to the requested `language`."},"support":{"type":"array","items":{"type":"number"},"description":"Classical floor-trader S1..S3 (computed locally, no AI cost)."},"resistance":{"type":"array","items":{"type":"number"},"description":"Classical floor-trader R1..R3 (computed locally, no AI cost)."},"indicators":{"type":"object","description":"Latest indicator snapshot used by the engine. Keys depend on the selected `indicator_set`.","additionalProperties":true},"indicator_set":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorKey"},"description":"Which indicators were actually computed for this signal."},"preset":{"$ref":"#/components/schemas/SignalPreset"},"patterns":{"type":"array","items":{"type":"string"},"description":"Detected price-action patterns (engulfing, pin bar, BOS, …) when `price_action` is in the indicator set."},"bars_count":{"type":"integer"},"generated_at":{"type":"string","format":"date-time"},"chart_url":{"type":"string","format":"uri","description":"Annotated candlestick chart (EMA20/50, BB, VWAP, S/R, entry/stop/TPs). Embed directly with <img src>. Omitted when `include_chart=false`."},"analysis":{"type":"object","description":"Nested view of the same analysis for backward compatibility.","additionalProperties":true}}},"GenerateSignalsResponse":{"type":"object","properties":{"signals":{"type":"array","items":{"$ref":"#/components/schemas/GeneratedSignal"}}}},"IndicatorKey":{"type":"string","enum":["ema","sma","rsi","macd","bbands","atr","adx","stoch","psar","ichimoku","vwap","volume_profile","fib","pivots","price_action"],"description":"Supported indicators. Pass one or more in `indicators` to drive signal generation. Defaults to the chosen `preset`."},"SignalPreset":{"type":"string","enum":["scalping","swing","price_action","trend","mean_reversion","custom"],"description":"Bundled indicator+param presets. `swing` is the default and matches legacy behavior. Use `custom` to drive everything from `indicators` + `params`."},"SignalStrategy":{"type":"string","enum":["aggressive_scalping","moderate_scalping","swing_trading","forex_aggressive","forex_moderate","forex_conservative","commodities_aggressive","commodities_moderate","commodities_conservative","crypto_aggressive","crypto_moderate","crypto_conservative","indices_aggressive","indices_moderate","indices_conservative"],"description":"Trading category profile with fixed SL/TP distances (overrides ATR-based levels). Units are pips (forex/commodities), ticks/points (indices/scalping), or percent (crypto). Profiles: `aggressive_scalping` (M5–M15, TP 5/7/10, SL 5), `moderate_scalping` (M15–M30, TP 7/12/16, SL 10), `swing_trading` (M30–H4, TP 15/25/35, SL 12), `forex_aggressive` (H4–D1, TP 25/35/45, SL 16), `forex_moderate` (H4–D1, TP 40/60/80, SL 25), `forex_conservative` (H4–D1, TP 60/100/140, SL 40), plus commodities/crypto/indices tiers."},"IndicatorParams":{"type":"object","description":"Per-indicator overrides (periods, thresholds, multipliers). Any field is optional; missing fields fall back to the preset defaults.","properties":{"ema":{"type":"object","properties":{"fast":{"type":"integer"},"slow":{"type":"integer"},"trend":{"type":"integer"}}},"sma":{"type":"object","properties":{"periods":{"type":"array","items":{"type":"integer"}}}},"rsi":{"type":"object","properties":{"period":{"type":"integer"},"overbought":{"type":"number"},"oversold":{"type":"number"}}},"macd":{"type":"object","properties":{"fast":{"type":"integer"},"slow":{"type":"integer"},"signal":{"type":"integer"}}},"bbands":{"type":"object","properties":{"period":{"type":"integer"},"stddev":{"type":"number"}}},"atr":{"type":"object","properties":{"period":{"type":"integer"},"sl_mult":{"type":"number","description":"Legacy single-stop ATR multiplier (used for primary `stop`)."},"sl_mults":{"type":"array","items":{"type":"number"},"description":"Three ATR multipliers for SL1/SL2/SL3 (tight → wide). Defaults to [1, 1.5, 2.5] for swing."},"tp_mults":{"type":"array","items":{"type":"number"},"description":"Three ATR multipliers for TP1/TP2/TP3. Defaults to [1, 2, 3] for swing."}}},"adx":{"type":"object","properties":{"period":{"type":"integer"},"trend_threshold":{"type":"number"}}},"stoch":{"type":"object","properties":{"k":{"type":"integer"},"d":{"type":"integer"},"smooth":{"type":"integer"},"overbought":{"type":"number"},"oversold":{"type":"number"}}},"psar":{"type":"object","properties":{"step":{"type":"number"},"max":{"type":"number"}}},"ichimoku":{"type":"object","properties":{"tenkan":{"type":"integer"},"kijun":{"type":"integer"},"senkou":{"type":"integer"}}},"fib":{"type":"object","properties":{"lookback":{"type":"integer"},"levels":{"type":"array","items":{"type":"number"}}}},"vwap":{"type":"object","properties":{"session":{"type":"string","enum":["rolling"]}}},"volume_profile":{"type":"object","properties":{"bins":{"type":"integer"}}},"pivots":{"type":"object","properties":{"type":{"type":"string","enum":["classic","fibonacci","camarilla"]}}}}}}},"security":[{"ApiKeyAuth":[]}],"paths":{"/health":{"get":{"tags":["Meta"],"summary":"Health check","security":[],"responses":{"200":{"description":"OK"}}}},"/usage":{"get":{"tags":["Meta"],"summary":"Current month usage for the calling key","responses":{"200":{"description":"Usage summary"}}}},"/platforms/register":{"post":{"tags":["Platforms"],"summary":"Register a new Platform and mint its first API key","description":"Public — no API key required. Returns the Platform id and a Bearer key. **Save the key immediately, it is shown only once.** New platforms start in `pending` status until activated.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformRegisterRequest"}}}},"responses":{"201":{"description":"Platform created"},"400":{"description":"Invalid input or plan not platform-eligible"}}}},"/platforms/me":{"get":{"tags":["Platforms"],"summary":"Get the calling Platform's profile, plan, and usage","responses":{"200":{"description":"Platform + plan + usage meter"},"403":{"description":"Not a platform key"}}},"patch":{"tags":["Platforms"],"summary":"Update Platform profile","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"company_name":{"type":"string"},"contact_email":{"type":"string"},"country":{"type":"string"},"activity_other":{"type":"string","nullable":true}}}}}},"responses":{"200":{"description":"Updated"}}}},"/platforms/me/signals":{"get":{"tags":["Platforms"],"summary":"List signals published by this platform (private to its end-users)","parameters":[{"name":"asset_class","in":"query","schema":{"type":"string","enum":["equities","crypto","forex","commodities"]}},{"name":"symbol","in":"query","schema":{"type":"string"}},{"name":"side","in":"query","schema":{"type":"string","enum":["long","short"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Array of platform-published signals (source: 'platform')"}}},"post":{"tags":["Platforms"],"summary":"Publish a signal to this platform's end-users only","description":"Creates a signal scoped to your platform. It is NOT visible to other Trade AI users or other platforms. Triggers a `signal.published` webhook to your registered endpoints. Per-user `/users/{id}/signals` will include it for any of your end-users.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["asset_class","symbol","side","entry"],"properties":{"asset_class":{"type":"string","enum":["equities","crypto","forex","commodities"]},"symbol":{"type":"string"},"exchange":{"type":"string","nullable":true},"side":{"type":"string","enum":["long","short"]},"entry":{"type":"number"},"stop":{"type":"number","nullable":true},"targets":{"type":"array","items":{"type":"number"},"maxItems":10},"confidence":{"type":"integer","minimum":0,"maximum":100,"default":50},"rationale":{"type":"string","nullable":true}}}}}},"responses":{"201":{"description":"Signal created"},"400":{"description":"BadRequest"}}}},"/platforms/me/signals/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"delete":{"tags":["Platforms"],"summary":"Delete one of this platform's published signals","responses":{"200":{"description":"Deleted"}}}},"/platforms/me/telegram/bot":{"get":{"tags":["Platforms"],"summary":"Get the platform's currently configured Telegram child bot (no token returned)","responses":{"200":{"description":"Bot info or { configured: false }"}}},"post":{"tags":["Platforms"],"summary":"Register or rotate the platform's Telegram child bot token","description":"Pass the token issued by @BotFather. We validate it via getMe, encrypt it with AES-GCM, and store the ciphertext. After this, /users/{id}/telegram/send routes through your bot and the master poller fans out updates from your bot too.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["bot_token"],"properties":{"bot_token":{"type":"string","example":"123456789:AAFExampleBotTokenFromBotFather"}}}}}},"responses":{"200":{"description":"Bot configured"},"400":{"description":"Invalid bot token"},"409":{"description":"Bot already linked to another platform"}}},"delete":{"tags":["Platforms"],"summary":"Disconnect the platform's Telegram bot","responses":{"200":{"description":"Disconnected"}}}},"/users":{"get":{"tags":["Users"],"summary":"List end-users under the calling Platform","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0}},{"name":"type","in":"query","schema":{"type":"string","enum":["user","agent","ib"]}},{"name":"email","in":"query","schema":{"type":"string","format":"email"}},{"name":"mobile","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"Paginated users","content":{"application/json":{"schema":{"type":"object","properties":{"users":{"type":"array","items":{"$ref":"#/components/schemas/PlatformUser"}},"total":{"type":"integer"}}}}}}}},"post":{"tags":["Users"],"summary":"Create an end-user under the calling Platform","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePlatformUserRequest"}}}},"responses":{"201":{"description":"User created"},"409":{"description":"external_user_id already used in this platform"}}}},"/users/{user_id}":{"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Users"],"summary":"Fetch one user","responses":{"200":{"description":"User","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlatformUser"}}}},"404":{"description":"Not found"}}},"patch":{"tags":["Users"],"summary":"Update a user","responses":{"200":{"description":"Updated"}}},"delete":{"tags":["Users"],"summary":"Soft-delete (archive) a user","responses":{"200":{"description":"Archived"}}}},"/users/{user_id}/portfolio":{"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Per-User Portfolio"],"summary":"Latest portfolio run for the user","responses":{"200":{"description":"Latest run or null"}}}},"/users/{user_id}/portfolio/summary":{"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Per-User Portfolio"],"summary":"Re-price holdings for the user","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldingsBody"}}}},"responses":{"200":{"description":"Valuation summary"}}}},"/users/{user_id}/portfolio/insights":{"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Per-User Portfolio"],"summary":"AI risk + diversification insights for the user","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldingsBody"}}}},"responses":{"200":{"description":"Summary + insights"}}}},"/users/{user_id}/signals":{"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Per-User Signals"],"summary":"Pull signals for the user (curated push + on-demand AI)","description":"Returns platform-published curated signals (push) and any cached AI signals for this end-user. When `symbol` and `asset_class` are provided, an AI signal is auto-generated on demand (cached for 10 min) for this user.","parameters":[{"name":"asset_class","in":"query","schema":{"type":"string","enum":["equities","crypto","forex","commodities"]}},{"name":"symbol","in":"query","schema":{"type":"string"}},{"name":"timeframe","in":"query","schema":{"type":"string","enum":["1Min","5Min","15Min","1Hour","1Day"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Array of signals"}}}},"/users/{user_id}/signals/generate":{"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Per-User Signals"],"summary":"Generate ad-hoc AI signal(s) for the user","description":"Returns one signal per requested symbol. Each signal includes entry / stop / targets, confidence, locally-computed bias and pivot support/resistance, an indicators snapshot, a localized summary, and an annotated `chart_url` you can embed.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["asset_class"],"properties":{"symbol":{"type":"string","example":"BTC/USD"},"symbols":{"type":"array","items":{"type":"string"},"maxItems":10},"asset_class":{"type":"string","enum":["equities","crypto","forex","commodities"]},"timeframe":{"type":"string","enum":["1Min","5Min","15Min","1Hour","1Day"],"default":"1Hour"},"language":{"type":"string","description":"ISO 639-1 (e.g. ar, fr, en) or 'auto'. Defaults to auto-detect."},"include_chart":{"type":"boolean","default":true,"description":"Set false to skip the annotated chart URL."},"force_narrative":{"type":"boolean","default":false,"description":"AI narrative is skipped on low-confidence neutral setups for latency. Set true to always run it."},"preset":{"$ref":"#/components/schemas/SignalPreset"},"strategy":{"$ref":"#/components/schemas/SignalStrategy"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorKey"},"maxItems":8,"description":"Pick a subset of indicators to drive bias + S/R. Overrides the preset's default set."},"params":{"$ref":"#/components/schemas/IndicatorParams"},"weights":{"type":"object","additionalProperties":{"type":"number","minimum":0,"maximum":10},"description":"Optional per-indicator weight (default 1) used in the bias score."}}}}}},"responses":{"200":{"description":"Generated signals","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateSignalsResponse"}}}}}}},"/users/{user_id}/telegram":{"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Per-User Telegram"],"summary":"Get the user's current Telegram linking status","description":"Returns whether the user has linked Telegram, the current pending link code (if any), and your platform's child bot username so you can render `Open @bot` instructions in your own UI. End-users never see a Trade AI page.","responses":{"200":{"description":"Status","content":{"application/json":{"schema":{"type":"object","properties":{"user_id":{"type":"string","format":"uuid"},"linked":{"type":"boolean"},"telegram_username":{"type":"string","nullable":true},"telegram_chat_id":{"type":"integer","nullable":true},"link_code":{"type":"string","nullable":true,"example":"A4F2C9D1"},"link_code_expires_at":{"type":"string","format":"date-time","nullable":true},"linked_at":{"type":"string","format":"date-time","nullable":true},"bot_username":{"type":"string","nullable":true,"description":"Your platform's child bot username (without the @)."}}}}}}}},"delete":{"tags":["Per-User Telegram"],"summary":"Unlink the user's Telegram account","description":"Clears the linked Telegram chat AND any pending link code. Use this when the end-user asks (in YOUR app) to disconnect Telegram.","responses":{"200":{"description":"Unlinked"}}}},"/users/{user_id}/telegram/link-code":{"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Per-User Telegram"],"summary":"Mint a one-time code for the user to bind their Telegram account","description":"Returns a 15-minute, single-use code. Show it in YOUR app along with `Open @YourBot in Telegram and send /start CODE`. Call this again to rotate.","responses":{"200":{"description":"Link code","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TelegramLinkCode"}}}}}}},"/users/{user_id}/telegram/send":{"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["Per-User Telegram"],"summary":"Push a message to the user via Telegram (user must be linked)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["text"],"properties":{"text":{"type":"string","maxLength":4000}}}}}},"responses":{"200":{"description":"Sent"},"409":{"description":"User has not linked Telegram yet"}}}},"/signals":{"get":{"tags":["Legacy"],"summary":"Pull signals (curated push + on-demand AI)","description":"Returns curated signals published by admins (push). When `symbol` and `asset_class` are provided, an AI signal is auto-generated on demand (cached for 10 min) and returned alongside curated results. Counts against the caller's quota.","parameters":[{"name":"symbol","in":"query","schema":{"type":"string"}},{"name":"asset_class","in":"query","schema":{"type":"string","enum":["equities","crypto","forex","commodities"]}},{"name":"side","in":"query","schema":{"type":"string","enum":["long","short"]}},{"name":"timeframe","in":"query","schema":{"type":"string","enum":["1Min","5Min","15Min","1Hour","1Day"]}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}}],"responses":{"200":{"description":"Signals"}}}},"/signals/generate":{"post":{"tags":["Legacy"],"summary":"Generate AI signals (direct-user)","description":"Returns one signal per requested symbol. Each signal includes entry / stop / targets, confidence, locally-computed bias and pivot support/resistance, an indicators snapshot, a localized summary, and an annotated `chart_url`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["asset_class"],"properties":{"symbol":{"type":"string"},"symbols":{"type":"array","items":{"type":"string"},"maxItems":10},"asset_class":{"type":"string","enum":["equities","crypto","forex","commodities"]},"timeframe":{"type":"string"},"language":{"type":"string","description":"ISO 639-1 (e.g. ar, fr, en) or 'auto'. Defaults to auto-detect."},"include_chart":{"type":"boolean","default":true},"force_narrative":{"type":"boolean","default":false},"preset":{"$ref":"#/components/schemas/SignalPreset"},"strategy":{"$ref":"#/components/schemas/SignalStrategy"},"indicators":{"type":"array","items":{"$ref":"#/components/schemas/IndicatorKey"},"maxItems":8},"params":{"$ref":"#/components/schemas/IndicatorParams"},"weights":{"type":"object","additionalProperties":{"type":"number"}}}}}}},"responses":{"200":{"description":"Generated signals","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateSignalsResponse"}}}}}}},"/signals/{id}":{"get":{"tags":["Legacy"],"summary":"Get a single signal by id","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Signal"},"404":{"description":"Not found"}}}},"/charts/analyze-image":{"post":{"tags":["Legacy"],"summary":"Analyze a chart screenshot with AI vision (direct-user)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["image"],"properties":{"image":{"type":"string"},"symbol":{"type":"string"},"timeframe":{"type":"string"},"notes":{"type":"string"},"language":{"type":"string","description":"ISO 639-1 or 'auto'. Defaults to auto-detect."}}}}}},"responses":{"200":{"description":"Analysis"}}}},"/charts/analyze-symbol":{"post":{"tags":["Legacy"],"summary":"Fetch OHLCV and run AI technical analysis (direct-user)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["symbol","asset_class"],"properties":{"symbol":{"type":"string","description":"US equities use the bare ticker (e.g. AAPL, MSFT). International equities use TICKER.EXCHANGE (e.g. BMW.DE, 7203.T, RELIANCE.NSE, 005930.KS, BHP.AX, PETR4.SA). Crypto: BTC/USD or BTCUSD. Forex: EUR/USD. Commodities: XAU/USD, WTI, BRENT, NATGAS."},"asset_class":{"type":"string","enum":["equities","crypto","forex","commodities"]},"timeframe":{"type":"string"},"language":{"type":"string","description":"ISO 639-1 or 'auto'. Defaults to auto-detect."}}}}}},"responses":{"200":{"description":"Analysis"}}}},"/portfolio/summary":{"post":{"tags":["Legacy"],"summary":"Price holdings (direct-user)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldingsBody"}}}},"responses":{"200":{"description":"Summary"}}}},"/portfolio/insights":{"post":{"tags":["Legacy"],"summary":"Portfolio AI insights (direct-user)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HoldingsBody"}}}},"responses":{"200":{"description":"Summary + insights"}}}},"/summarize":{"post":{"tags":["Legacy"],"summary":"AI page summarizer","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url","text"],"properties":{"url":{"type":"string"},"title":{"type":"string"},"text":{"type":"string","minLength":20,"maxLength":200000},"mode":{"type":"string","enum":["summarize","eli5","takeaways","actions"],"default":"summarize"},"language":{"type":"string","description":"ISO 639-1 or 'auto'. Defaults to auto-detect."}}}}}},"responses":{"200":{"description":"Summary"}}}},"/ai/functions":{"get":{"tags":["AI Functions"],"summary":"List available AI functions","description":"Returns the catalog of curated AI functions with input schemas and required plan tier. Includes `news`, `explain-symbol`, `sentiment`, `summarize-text`, `translate`, and `analyze-chart-image` (multimodal: pass `input.image` as a `data:image/...` URL). All AI endpoints honor a `language` field (ISO 639-1 like `ar`, `fr`, `en`) or `auto` (default) — when omitted, language is auto-detected from the input text or the `Accept-Language` header.","responses":{"200":{"description":"Function catalog"}}}},"/ai/run":{"post":{"tags":["AI Functions"],"summary":"Run an AI function","description":"Run a curated AI function. Pass `language` to force the response language (ISO 639-1, e.g. `ar`, `fr`, `en`) or use `auto` to detect from the input text or `Accept-Language` header.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["function"],"properties":{"function":{"type":"string","example":"news"},"input":{"type":"object","example":{"symbol":"AAPL"}},"language":{"type":"string","description":"ISO code or 'auto'","example":"auto"}}}}}},"responses":{"200":{"description":"Function output"},"403":{"description":"Plan tier insufficient"}}}},"/users/{user_id}/ai/run":{"parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"post":{"tags":["AI Functions"],"summary":"Run an AI function on behalf of a platform end-user","responses":{"200":{"description":"Function output"}}}}}}