{"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"}}}}},"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","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"}}}}}},"responses":{"200":{"description":"Generated signals"}}}},"/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)","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 from input/Accept-Language."}}}}}},"responses":{"200":{"description":"Generated signals"}}}},"/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 via the Lovable AI gateway. 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"}}}}}}